Date: Sun, 1 May 2022 12:56:25 +0200
Subject: [PATCH 33/51] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index f4d28288..95fb7c60 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
-
+
From a1c34421488b8eb4c5776cbf457f0719c79d742e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Sun, 15 May 2022 13:32:09 +0200
Subject: [PATCH 34/51] Resolve #854
---
src/Database/Concerns/BelongsToTenant.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/Database/Concerns/BelongsToTenant.php b/src/Database/Concerns/BelongsToTenant.php
index 5410758d..fc899411 100644
--- a/src/Database/Concerns/BelongsToTenant.php
+++ b/src/Database/Concerns/BelongsToTenant.php
@@ -16,7 +16,7 @@ trait BelongsToTenant
public function tenant()
{
- return $this->belongsTo(config('tenancy.tenant_model'), BelongsToTenant::$tenantIdColumn);
+ return $this->belongsTo(config('tenancy.tenant_model'), static::$tenantIdColumn);
}
public static function bootBelongsToTenant()
@@ -24,9 +24,9 @@ trait BelongsToTenant
static::addGlobalScope(new TenantScope);
static::creating(function ($model) {
- if (! $model->getAttribute(BelongsToTenant::$tenantIdColumn) && ! $model->relationLoaded('tenant')) {
+ if (! $model->getAttribute(static::$tenantIdColumn) && ! $model->relationLoaded('tenant')) {
if (tenancy()->initialized) {
- $model->setAttribute(BelongsToTenant::$tenantIdColumn, tenant()->getTenantKey());
+ $model->setAttribute(static::$tenantIdColumn, tenant()->getTenantKey());
$model->setRelation('tenant', tenant());
}
}
From 4d95e88e272d5c3f4beebd10a8e549d17259a079 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Sun, 15 May 2022 13:45:54 +0200
Subject: [PATCH 35/51] Revert "Resolve #854"
This reverts commit a1c34421488b8eb4c5776cbf457f0719c79d742e.
---
src/Database/Concerns/BelongsToTenant.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/Database/Concerns/BelongsToTenant.php b/src/Database/Concerns/BelongsToTenant.php
index fc899411..5410758d 100644
--- a/src/Database/Concerns/BelongsToTenant.php
+++ b/src/Database/Concerns/BelongsToTenant.php
@@ -16,7 +16,7 @@ trait BelongsToTenant
public function tenant()
{
- return $this->belongsTo(config('tenancy.tenant_model'), static::$tenantIdColumn);
+ return $this->belongsTo(config('tenancy.tenant_model'), BelongsToTenant::$tenantIdColumn);
}
public static function bootBelongsToTenant()
@@ -24,9 +24,9 @@ trait BelongsToTenant
static::addGlobalScope(new TenantScope);
static::creating(function ($model) {
- if (! $model->getAttribute(static::$tenantIdColumn) && ! $model->relationLoaded('tenant')) {
+ if (! $model->getAttribute(BelongsToTenant::$tenantIdColumn) && ! $model->relationLoaded('tenant')) {
if (tenancy()->initialized) {
- $model->setAttribute(static::$tenantIdColumn, tenant()->getTenantKey());
+ $model->setAttribute(BelongsToTenant::$tenantIdColumn, tenant()->getTenantKey());
$model->setRelation('tenant', tenant());
}
}
From 51228defc68a6362e31f486a04e3117105abf3e0 Mon Sep 17 00:00:00 2001
From: Vincent GS
Date: Thu, 26 May 2022 04:51:27 -0500
Subject: [PATCH 36/51] [3.x][Filesystem] Provide an additional argument for
tenant name path (#817)
* Let the user pass the tenant suffix by %tenant%
In this PR we let the user pass an additional parameter using `%tenant%` so the user can additionally pass the folder corresponding to each tenant.
This is my proposal, because if I try to use %storage_path% within Linux, I get the full path to the project when I use Google Cloud Storage
* Missing missing updates
Moving from $subject to $root when %storage_path% has been replaced
---
src/Bootstrappers/FilesystemTenancyBootstrapper.php | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/Bootstrappers/FilesystemTenancyBootstrapper.php b/src/Bootstrappers/FilesystemTenancyBootstrapper.php
index da1e5e2a..346892b3 100644
--- a/src/Bootstrappers/FilesystemTenancyBootstrapper.php
+++ b/src/Bootstrappers/FilesystemTenancyBootstrapper.php
@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Bootstrappers;
use Illuminate\Contracts\Foundation\Application;
-use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
use Stancl\Tenancy\Contracts\Tenant;
@@ -61,8 +60,8 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
$this->originalPaths['disks'][$disk] = $originalRoot;
$finalPrefix = str_replace(
- '%storage_path%',
- storage_path(),
+ ['%storage_path%', '%tenant%'],
+ [storage_path(), $tenant->getTenantKey()],
$this->app['config']["tenancy.filesystem.root_override.{$disk}"] ?? '',
);
From d0de09aa5360b17033b81726d38b18272462f6b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Wed, 1 Jun 2022 15:36:46 +0200
Subject: [PATCH 37/51] remove old versions from CI
---
.github/workflows/ci.yml | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index cc8ad985..81d37af5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,9 +5,9 @@ env:
on:
push:
- branches: [ 3.x, 2.x, master ]
+ branches: [ master ]
pull_request:
- branches: [ 3.x, 2.x, master ]
+ branches: [ master ]
jobs:
tests:
@@ -15,11 +15,8 @@ jobs:
strategy:
matrix:
- php: ["7.3", "8.0"]
- laravel: ["^6.0", "^8.0", "^9.0"]
- exclude:
- - laravel: "^9.0"
- php: "7.3"
+ php: ["8.1"]
+ laravel: ["^9.0"]
steps:
- uses: actions/checkout@v2
From 7d98ebb5d177a1ac30ec0a7d248c4cd254abce4a Mon Sep 17 00:00:00 2001
From: Victor R <39545521+viicslen@users.noreply.github.com>
Date: Wed, 1 Jun 2022 10:12:59 -0400
Subject: [PATCH 38/51] [4.x] Add tenant schema dump command (#807)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Add tenant dump command
* Register tenant schema dump command
* Added tests for tenant schema dump command
* remove docblocks, fix tenant() logic
* trigger ci
* Install mysql-client
* mysql-client -> mariadb-client
* add tenant-schema-test.dump to .gitignore
Co-authored-by: Samuel Å tancl
Co-authored-by: Samuel Å tancl
---
.gitignore | 1 +
Dockerfile | 2 +-
src/Commands/TenantDump.php | 54 ++++++++++++++++++++++++++++
src/TenancyServiceProvider.php | 1 +
tests/CommandsTest.php | 32 +++++++++++++++++
tests/Etc/tenant-schema.dump | 66 ++++++++++++++++++++++++++++++++++
6 files changed, 155 insertions(+), 1 deletion(-)
create mode 100644 src/Commands/TenantDump.php
create mode 100644 tests/Etc/tenant-schema.dump
diff --git a/.gitignore b/.gitignore
index f470ba75..95522c34 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,5 +8,6 @@ psysh
phpunit_var_*.xml
coverage/
clover.xml
+tenant-schema-test.dump
tests/Etc/tmp/queuetest.json
docker-compose.override.yml
diff --git a/Dockerfile b/Dockerfile
index 36f52d6a..a636bc45 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -24,7 +24,7 @@ ENV LANG=en_GB.UTF-8
# install some OS packages we need
RUN apt-get update
-RUN apt-get install -y --no-install-recommends libfreetype6-dev libjpeg62-turbo-dev libpng-dev libgmp-dev libldap2-dev netcat curl sqlite3 libsqlite3-dev libpq-dev libzip-dev unzip vim-tiny gosu git
+RUN apt-get install -y --no-install-recommends libfreetype6-dev libjpeg62-turbo-dev libpng-dev libgmp-dev libldap2-dev netcat curl mariadb-client sqlite3 libsqlite3-dev libpq-dev libzip-dev unzip vim-tiny gosu git
# install php extensions
RUN docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \
# && if [ "${PHP_VERSION}" = "7.4" ]; then docker-php-ext-configure gd --with-freetype --with-jpeg; else docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/; fi \
diff --git a/src/Commands/TenantDump.php b/src/Commands/TenantDump.php
new file mode 100644
index 00000000..557c6975
--- /dev/null
+++ b/src/Commands/TenantDump.php
@@ -0,0 +1,54 @@
+setName('tenants:dump');
+ $this->specifyParameters();
+ }
+
+
+ public function handle(ConnectionResolverInterface $connections, Dispatcher $dispatcher): int
+ {
+ $this->tenant()->run(fn() => parent::handle($connections, $dispatcher));
+
+ return Command::SUCCESS;
+ }
+
+ public function tenant(): Tenant
+ {
+ $tenant = $this->option('tenant')
+ ?? tenant()
+ ?? $this->ask('What tenant do you want to dump the schema for?')
+ ?? tenancy()->query()->first();
+
+ if (! $tenant instanceof Tenant) {
+ $tenant = tenancy()->find($tenant);
+ }
+
+ throw_if(! $tenant, 'Could not identify the tenant to use for dumping the schema.');
+
+ return $tenant;
+ }
+
+ protected function getOptions(): array
+ {
+ return array_merge([
+ ['tenant', null, InputOption::VALUE_OPTIONAL, '', null],
+ ], parent::getOptions());
+ }
+}
diff --git a/src/TenancyServiceProvider.php b/src/TenancyServiceProvider.php
index 4faaccf3..dd061af3 100644
--- a/src/TenancyServiceProvider.php
+++ b/src/TenancyServiceProvider.php
@@ -88,6 +88,7 @@ class TenancyServiceProvider extends ServiceProvider
Commands\Migrate::class,
Commands\Rollback::class,
Commands\TenantList::class,
+ Commands\TenantDump::class,
Commands\MigrateFresh::class,
]);
diff --git a/tests/CommandsTest.php b/tests/CommandsTest.php
index d7da0cab..145a93c5 100644
--- a/tests/CommandsTest.php
+++ b/tests/CommandsTest.php
@@ -91,6 +91,38 @@ class CommandsTest extends TestCase
$this->assertTrue(Schema::hasTable('users'));
}
+ /** @test */
+ public function migrate_command_loads_schema_state()
+ {
+ $tenant = Tenant::create();
+
+ $this->assertFalse(Schema::hasTable('schema_users'));
+ $this->assertFalse(Schema::hasTable('users'));
+
+ Artisan::call('tenants:migrate --schema-path="tests/Etc/tenant-schema.dump"');
+
+ $this->assertFalse(Schema::hasTable('schema_users'));
+ $this->assertFalse(Schema::hasTable('users'));
+
+ tenancy()->initialize($tenant);
+
+ // Check for both tables to see if missing migrations also get executed
+ $this->assertTrue(Schema::hasTable('schema_users'));
+ $this->assertTrue(Schema::hasTable('users'));
+ }
+
+ /** @test */
+ public function dump_command_works()
+ {
+ $tenant = Tenant::create();
+ Artisan::call('tenants:migrate');
+
+ tenancy()->initialize($tenant);
+
+ Artisan::call('tenants:dump --path="tests/Etc/tenant-schema-test.dump"');
+ $this->assertFileExists('tests/Etc/tenant-schema-test.dump');
+ }
+
/** @test */
public function rollback_command_works()
{
diff --git a/tests/Etc/tenant-schema.dump b/tests/Etc/tenant-schema.dump
new file mode 100644
index 00000000..6af9f019
--- /dev/null
+++ b/tests/Etc/tenant-schema.dump
@@ -0,0 +1,66 @@
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+DROP TABLE IF EXISTS `failed_jobs`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `failed_jobs` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `uuid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `connection` text COLLATE utf8mb4_unicode_ci NOT NULL,
+ `queue` text COLLATE utf8mb4_unicode_ci NOT NULL,
+ `payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
+ `exception` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
+ `failed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `migrations`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `migrations` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `migration` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `batch` int(11) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `password_resets`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `password_resets` (
+ `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `created_at` timestamp NULL DEFAULT NULL,
+ KEY `password_resets_email_index` (`email`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `users`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `schema_users` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `email_verified_at` timestamp NULL DEFAULT NULL,
+ `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+ `created_at` timestamp NULL DEFAULT NULL,
+ `updated_at` timestamp NULL DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `users_email_unique` (`email`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+INSERT INTO `migrations` VALUES (2,'2014_10_12_100000_testbench_create_password_resets_table',1);
+INSERT INTO `migrations` VALUES (3,'2019_08_19_000000_testbench_create_failed_jobs_table',1);
From 72c41ea9938eef4964ef9469d829c16536f69e43 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Mon, 13 Jun 2022 19:16:35 +0200
Subject: [PATCH 39/51] Discord link
---
SUPPORT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/SUPPORT.md b/SUPPORT.md
index b7caaa5c..24be468b 100644
--- a/SUPPORT.md
+++ b/SUPPORT.md
@@ -1,5 +1,5 @@
# Get Support
-If you need help with implementing the package, you can join our community [Discord server](https://discord.gg/7cpgPxv) and ask in `#help`.
+If you need help with implementing the package, you can join our community [Discord server](https://archte.ch/discord) and ask in `#help`.
If you're interested in paid consulting from the maintainer, see the [contact page](https://tenancyforlaravel.com/contact/) on our website.
From cc6d4fe0ddcec405f645fbd2d8e90ab71afb1bec Mon Sep 17 00:00:00 2001
From: Nick Kitchen <61636526+nickakitch@users.noreply.github.com>
Date: Thu, 23 Jun 2022 21:04:53 +1000
Subject: [PATCH 40/51] [4.x] Added support for Microsoft Sql Server (#715)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* added support for microsoft sql server database
* added support for microsoft sql server database
* trigger ci
* revert change
* trigger ci
* Try installing pdo_sqlsrv
* different approach for installing sqlsrv via pecl
* add dependencies
* add gnupg2
* Update Dockerfile
* try skipping msodbcsql17
* Update Dockerfile
* add dependency back
* update before installing
* try to add mssql
* mssql host
* TENANCY_TEST_MSSQL_HOST env var
* add env vars for mssql
* add sqlsrv vars to TestCase
* rename vars to SQLSRV [skip ci]
* MSSQL -> SQLSRV
Co-authored-by: Samuel Å tancl
Co-authored-by: Samuel Å tancl
---
Dockerfile | 21 +++++--
assets/config.php | 1 +
docker-compose.yml | 11 ++++
.../MicrosoftSQLDatabaseManager.php | 57 +++++++++++++++++++
tests/TenantDatabaseManagerTest.php | 2 +
tests/TestCase.php | 4 ++
6 files changed, 90 insertions(+), 6 deletions(-)
create mode 100644 src/TenantDatabaseManagers/MicrosoftSQLDatabaseManager.php
diff --git a/Dockerfile b/Dockerfile
index a636bc45..fb63afe3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
ARG PHP_VERSION=7.4
ARG PHP_TARGET=php:${PHP_VERSION}-cli
-FROM ${PHP_TARGET}
+FROM --platform=linux/amd64 ${PHP_TARGET}
ARG COMPOSER_TARGET=2.0.3
@@ -22,10 +22,16 @@ ENV LANG=en_GB.UTF-8
# Dockerfile _and pin the versions_! Eg:
# RUN pecl install memcached-2.2.0 && docker-php-ext-enable memcached
-# install some OS packages we need
-RUN apt-get update
-RUN apt-get install -y --no-install-recommends libfreetype6-dev libjpeg62-turbo-dev libpng-dev libgmp-dev libldap2-dev netcat curl mariadb-client sqlite3 libsqlite3-dev libpq-dev libzip-dev unzip vim-tiny gosu git
- # install php extensions
+
+RUN apt-get update \
+ && apt-get install -y gnupg2 \
+ && curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \
+ && curl https://packages.microsoft.com/config/debian/11/prod.list > /etc/apt/sources.list.d/mssql-release.list \
+ && apt-get update \
+ && ACCEPT_EULA=Y apt-get install -y unixodbc-dev msodbcsql17
+
+RUN apt-get install -y --no-install-recommends locales apt-transport-https libfreetype6-dev libjpeg62-turbo-dev libpng-dev libgmp-dev libldap2-dev netcat curl mariadb-client sqlite3 libsqlite3-dev libpq-dev libzip-dev unzip vim-tiny gosu git
+
RUN docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \
# && if [ "${PHP_VERSION}" = "7.4" ]; then docker-php-ext-configure gd --with-freetype --with-jpeg; else docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/; fi \
&& docker-php-ext-install -j$(nproc) gd pdo pdo_mysql pdo_pgsql pdo_sqlite pgsql zip gmp bcmath pcntl ldap sysvmsg exif \
@@ -35,7 +41,10 @@ RUN docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \
# install the pcov extention
&& pecl install pcov \
&& docker-php-ext-enable pcov \
- && echo "pcov.enabled = 1" > /usr/local/etc/php/conf.d/pcov.ini
+ && echo "pcov.enabled = 1" > /usr/local/etc/php/conf.d/pcov.ini \
+ # install sqlsrv
+ && pecl install sqlsrv pdo_sqlsrv \
+ && docker-php-ext-enable sqlsrv pdo_sqlsrv
# clear the apt cache
RUN rm -rf /var/lib/apt/lists/* \
&& rm -rf /var/lib/apt/lists/* \
diff --git a/assets/config.php b/assets/config.php
index 85592d14..e1c82e6b 100644
--- a/assets/config.php
+++ b/assets/config.php
@@ -61,6 +61,7 @@ return [
'sqlite' => Stancl\Tenancy\TenantDatabaseManagers\SQLiteDatabaseManager::class,
'mysql' => Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager::class,
'pgsql' => Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLDatabaseManager::class,
+ 'sqlsrv' => Stancl\Tenancy\TenantDatabaseManagers\MicrosoftSQLDatabaseManager::class,
/**
* Use this database manager for MySQL to have a DB user created for each tenant database.
diff --git a/docker-compose.yml b/docker-compose.yml
index e8e8d418..7b635637 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -22,6 +22,9 @@ services:
TENANCY_TEST_REDIS_HOST: redis
TENANCY_TEST_MYSQL_HOST: mysql
TENANCY_TEST_PGSQL_HOST: postgres
+ TENANCY_TEST_SQLSRV_HOST: mssql
+ TENANCY_TEST_SQLSRV_USERNAME: sa
+ TENANCY_TEST_SQLSRV_PASSWORD: P@ssword
stdin_open: true
tty: true
mysql:
@@ -64,3 +67,11 @@ services:
interval: 1s
timeout: 3s
retries: 30
+ mssql:
+ image: mcr.microsoft.com/mssql/server:2019-latest
+ ports:
+ - 1433:1433
+ environment:
+ - ACCEPT_EULA=Y
+ - SA_PASSWORD=P@ssword # todo reuse values from env above
+ # todo missing health check
diff --git a/src/TenantDatabaseManagers/MicrosoftSQLDatabaseManager.php b/src/TenantDatabaseManagers/MicrosoftSQLDatabaseManager.php
new file mode 100644
index 00000000..0bc34623
--- /dev/null
+++ b/src/TenantDatabaseManagers/MicrosoftSQLDatabaseManager.php
@@ -0,0 +1,57 @@
+connection === null) {
+ throw new NoConnectionSetException(static::class);
+ }
+
+ return DB::connection($this->connection);
+ }
+
+ public function setConnection(string $connection): void
+ {
+ $this->connection = $connection;
+ }
+
+ public function createDatabase(TenantWithDatabase $tenant): bool
+ {
+ $database = $tenant->database()->getName();
+ $charset = $this->database()->getConfig('charset');
+ $collation = $this->database()->getConfig('collation');
+
+ return $this->database()->statement("CREATE DATABASE [{$database}]");
+ }
+
+ public function deleteDatabase(TenantWithDatabase $tenant): bool
+ {
+ return $this->database()->statement("DROP DATABASE [{$tenant->database()->getName()}]");
+ }
+
+ public function databaseExists(string $name): bool
+ {
+ return (bool) $this->database()->select("SELECT name FROM master.sys.databases WHERE name = '$name'");
+ }
+
+ public function makeConnectionConfig(array $baseConfig, string $databaseName): array
+ {
+ $baseConfig['database'] = $databaseName;
+
+ return $baseConfig;
+ }
+}
diff --git a/tests/TenantDatabaseManagerTest.php b/tests/TenantDatabaseManagerTest.php
index 3d45d96f..0e1464c0 100644
--- a/tests/TenantDatabaseManagerTest.php
+++ b/tests/TenantDatabaseManagerTest.php
@@ -19,6 +19,7 @@ use Stancl\Tenancy\Exceptions\TenantDatabaseAlreadyExistsException;
use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
+use Stancl\Tenancy\TenantDatabaseManagers\MicrosoftSQLDatabaseManager;
use Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager;
use Stancl\Tenancy\TenantDatabaseManagers\PermissionControlledMySQLDatabaseManager;
use Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLDatabaseManager;
@@ -103,6 +104,7 @@ class TenantDatabaseManagerTest extends TestCase
['sqlite', SQLiteDatabaseManager::class],
['pgsql', PostgreSQLDatabaseManager::class],
['pgsql', PostgreSQLSchemaManager::class],
+ ['sqlsrv', MicrosoftSQLDatabaseManager::class]
];
}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index cea669a1..75fe51fd 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -81,6 +81,10 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
],
'database.connections.sqlite.database' => ':memory:',
'database.connections.mysql.host' => env('TENANCY_TEST_MYSQL_HOST', '127.0.0.1'),
+ 'database.connections.sqlsrv.username' => env('TENANCY_TEST_SQLSRV_USERNAME', 'sa'),
+ 'database.connections.sqlsrv.password' => env('TENANCY_TEST_SQLSRV_PASSWORD', 'P@ssword'),
+ 'database.connections.sqlsrv.host' => env('TENANCY_TEST_SQLSRV_HOST', '127.0.0.1'),
+ 'database.connections.sqlsrv.database' => null,
'database.connections.pgsql.host' => env('TENANCY_TEST_PGSQL_HOST', '127.0.0.1'),
'tenancy.filesystem.disks' => [
'local',
From 4aec6bfda20e659c4e62d2527ead99fbf44926cd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Tue, 28 Jun 2022 21:47:33 +0200
Subject: [PATCH 41/51] add phpunit dependency
---
composer.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/composer.json b/composer.json
index 8e932658..8123944a 100644
--- a/composer.json
+++ b/composer.json
@@ -20,6 +20,7 @@
"require-dev": {
"laravel/framework": "^6.0|^7.0|^8.0|^9.0",
"orchestra/testbench": "^4.0|^5.0|^6.0|^7.0",
+ "phpunit/phpunit": "*",
"league/flysystem-aws-s3-v3": "^1.0|^3.0",
"doctrine/dbal": "^2.10",
"spatie/valuestore": "^1.2.5"
From 627233d07aa173893cc2afd6484d44f8455c7362 Mon Sep 17 00:00:00 2001
From: Abrar Ahmad
Date: Wed, 20 Jul 2022 16:02:33 +0500
Subject: [PATCH 42/51] [4.x] Don't use onDeleteCascade in the migrations
(#883)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* removed `cascade` on delete for domains
* removed only `onDelete` cascade
* keep impersonation migrations unchanged
* domains set null on delete
* Update 2019_09_15_000020_create_domains_table.php
* Added DeleteDomain Job while deleting tenant.
* Update assets/TenancyServiceProvider.stub.php
Co-authored-by: Samuel Å tancl
* renamed class
* Update DeleteDomains.php
* onDelete restrict
* revert nullable
* removed `shouldQueue` interface
* Update TenancyServiceProvider.stub.php
* fetch and delete domains individually
* Update DeleteDomains.php
* tests for `DeleteDomains` job
Co-authored-by: Samuel Å tancl
---
assets/TenancyServiceProvider.stub.php | 8 +++-
...2019_09_15_000020_create_domains_table.php | 2 +-
src/Jobs/DeleteDomains.php | 35 ++++++++++++++++
tests/DeleteDomainsJobTest.php | 42 +++++++++++++++++++
4 files changed, 85 insertions(+), 2 deletions(-)
create mode 100644 src/Jobs/DeleteDomains.php
create mode 100644 tests/DeleteDomainsJobTest.php
diff --git a/assets/TenancyServiceProvider.stub.php b/assets/TenancyServiceProvider.stub.php
index 1d15f418..865bb93d 100644
--- a/assets/TenancyServiceProvider.stub.php
+++ b/assets/TenancyServiceProvider.stub.php
@@ -40,7 +40,13 @@ class TenancyServiceProvider extends ServiceProvider
Events\TenantSaved::class => [],
Events\UpdatingTenant::class => [],
Events\TenantUpdated::class => [],
- Events\DeletingTenant::class => [],
+ Events\DeletingTenant::class => [
+ JobPipeline::make([
+ Jobs\DeleteDomains::class,
+ ])->send(function (Events\DeletingTenant $event) {
+ return $event->tenant;
+ })->shouldBeQueued(false),
+ ],
Events\TenantDeleted::class => [
JobPipeline::make([
Jobs\DeleteDatabase::class,
diff --git a/assets/migrations/2019_09_15_000020_create_domains_table.php b/assets/migrations/2019_09_15_000020_create_domains_table.php
index 77c1b88a..17f706c2 100644
--- a/assets/migrations/2019_09_15_000020_create_domains_table.php
+++ b/assets/migrations/2019_09_15_000020_create_domains_table.php
@@ -21,7 +21,7 @@ class CreateDomainsTable extends Migration
$table->string('tenant_id');
$table->timestamps();
- $table->foreign('tenant_id')->references('id')->on('tenants')->onUpdate('cascade')->onDelete('cascade');
+ $table->foreign('tenant_id')->references('id')->on('tenants')->onUpdate('cascade');
});
}
diff --git a/src/Jobs/DeleteDomains.php b/src/Jobs/DeleteDomains.php
new file mode 100644
index 00000000..fac60e43
--- /dev/null
+++ b/src/Jobs/DeleteDomains.php
@@ -0,0 +1,35 @@
+tenant = $tenant;
+ }
+
+ public function handle()
+ {
+ $this->tenant->domains->each->delete();
+ }
+}
diff --git a/tests/DeleteDomainsJobTest.php b/tests/DeleteDomainsJobTest.php
new file mode 100644
index 00000000..7fce9cf3
--- /dev/null
+++ b/tests/DeleteDomainsJobTest.php
@@ -0,0 +1,42 @@
+ DatabaseAndDomainTenant::class]);
+ }
+
+ /** @test */
+ public function job_delete_domains_successfully()
+ {
+ $tenant = DatabaseAndDomainTenant::create();
+
+ $tenant->domains()->create([
+ 'domain' => 'foo.localhost',
+ ]);
+ $tenant->domains()->create([
+ 'domain' => 'bar.localhost',
+ ]);
+
+ $this->assertSame($tenant->domains()->count(), 2);
+
+ (new DeleteDomains($tenant))->handle();
+
+ $this->assertSame($tenant->refresh()->domains()->count(), 0);
+ }
+}
+
+class DatabaseAndDomainTenant extends Etc\Tenant
+{
+ use HasDomains;
+}
From 97ab483173b38d9233082b349a9f73f268784449 Mon Sep 17 00:00:00 2001
From: Abrar Ahmad
Date: Wed, 20 Jul 2022 18:28:45 +0500
Subject: [PATCH 43/51] Completing PR #881 (#902)
* install PHP CS Fixer
* Fix styling
* remove StyleCI config
* use config from archtechx/template
* Fix styling
* added `php-cs-fixer`
* Update .php-cs-fixer.php
* added GitHub token
* Update ci.yml
* Update ci.yml
* Update ci.yml
* php-cs-fixer workflow same as template
Co-authored-by: Erik Gaal
Co-authored-by: erikgaal
---
.github/workflows/ci.yml | 18 +++
.gitignore | 1 +
.php-cs-fixer.php | 141 ++++++++++++++++++
.styleci.yml | 7 -
.../FilesystemTenancyBootstrapper.php | 2 +-
.../QueueTenancyBootstrapper.php | 9 +-
src/CacheManager.php | 3 +-
src/Commands/Install.php | 2 -
src/Commands/Migrate.php | 2 -
src/Commands/MigrateFresh.php | 2 -
src/Commands/Rollback.php | 2 -
src/Commands/Run.php | 2 -
src/Commands/Seed.php | 2 -
src/Commands/TenantDump.php | 3 +-
src/Commands/TenantList.php | 2 -
src/Concerns/ExtendsLaravelCommand.php | 2 +
src/Concerns/HasATenantsOption.php | 2 +-
src/Contracts/TenantDatabaseManager.php | 10 --
src/Database/Concerns/TenantRun.php | 3 -
src/Database/Models/ImpersonationToken.php | 5 +
src/Database/Models/Tenant.php | 2 +
src/DatabaseConfig.php | 5 +-
src/Features/UserImpersonation.php | 1 -
src/Listeners/UpdateSyncedResource.php | 3 +-
.../CheckTenantForMaintenanceMode.php | 2 +-
src/Middleware/InitializeTenancyByDomain.php | 6 +-
.../InitializeTenancyByDomainOrSubdomain.php | 2 -
src/Middleware/InitializeTenancyByPath.php | 4 +-
.../InitializeTenancyByRequestData.php | 2 -
.../InitializeTenancyBySubdomain.php | 2 -
.../Contracts/CachedTenantResolver.php | 1 -
src/Tenancy.php | 5 -
src/TenancyServiceProvider.php | 4 -
tests/BootstrapperTest.php | 26 ++--
tests/Etc/ExampleSeeder.php | 2 +-
tests/EventListenerTest.php | 1 -
tests/MaintenanceModeTest.php | 4 +-
tests/QueueTest.php | 36 +++--
tests/ResourceSyncingTest.php | 4 +
tests/SingleDatabaseTenancyTest.php | 3 +
tests/TenantDatabaseManagerTest.php | 4 +-
tests/TenantModelTest.php | 1 +
tests/TenantUserImpersonationTest.php | 2 +
43 files changed, 231 insertions(+), 111 deletions(-)
create mode 100644 .php-cs-fixer.php
delete mode 100644 .styleci.yml
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 81d37af5..3cbda814 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,6 +2,8 @@ name: CI
env:
COMPOSE_INTERACTIVE_NO_CLI: 1
+ PHP_CS_FIXER_IGNORE_ENV: 1
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
on:
push:
@@ -26,3 +28,19 @@ jobs:
run: docker-compose exec -T test composer require --no-interaction "laravel/framework:${{ matrix.laravel }}"
- name: Run tests
run: ./test
+
+ php-cs-fixer:
+ name: Code style (php-cs-fixer)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install php-cs-fixer
+ run: composer global require friendsofphp/php-cs-fixer
+ - name: Run php-cs-fixer
+ run: $HOME/.composer/vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php
+ - name: Commit changes from php-cs-fixer
+ uses: EndBug/add-and-commit@v5
+ with:
+ author_name: "PHP CS Fixer"
+ author_email: "phpcsfixer@example.com"
+ message: Fix code style (php-cs-fixer)
diff --git a/.gitignore b/.gitignore
index 95522c34..64d9dc21 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@ clover.xml
tenant-schema-test.dump
tests/Etc/tmp/queuetest.json
docker-compose.override.yml
+.php-cs-fixer.cache
diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php
new file mode 100644
index 00000000..589838bc
--- /dev/null
+++ b/.php-cs-fixer.php
@@ -0,0 +1,141 @@
+ ['syntax' => 'short'],
+ 'binary_operator_spaces' => [
+ 'default' => 'single_space',
+ 'operators' => [
+ '=>' => null,
+ '|' => 'no_space',
+ ]
+ ],
+ 'blank_line_after_namespace' => true,
+ 'blank_line_after_opening_tag' => true,
+ 'no_superfluous_phpdoc_tags' => true,
+ 'blank_line_before_statement' => [
+ 'statements' => ['return']
+ ],
+ 'braces' => true,
+ 'cast_spaces' => true,
+ 'class_definition' => true,
+ 'concat_space' => [
+ 'spacing' => 'one'
+ ],
+ 'declare_equal_normalize' => true,
+ 'elseif' => true,
+ 'encoding' => true,
+ 'full_opening_tag' => true,
+ 'declare_strict_types' => true,
+ 'fully_qualified_strict_types' => true, // added by Shift
+ 'function_declaration' => true,
+ 'function_typehint_space' => true,
+ 'heredoc_to_nowdoc' => true,
+ 'include' => true,
+ 'increment_style' => ['style' => 'post'],
+ 'indentation_type' => true,
+ 'linebreak_after_opening_tag' => true,
+ 'line_ending' => true,
+ 'lowercase_cast' => true,
+ 'constant_case' => true,
+ 'lowercase_keywords' => true,
+ 'lowercase_static_reference' => true, // added from Symfony
+ 'magic_method_casing' => true, // added from Symfony
+ 'magic_constant_casing' => true,
+ 'method_argument_space' => true,
+ 'native_function_casing' => true,
+ 'no_alias_functions' => true,
+ 'no_extra_blank_lines' => [
+ 'tokens' => [
+ 'extra',
+ 'throw',
+ 'use',
+ 'use_trait',
+ ]
+ ],
+ 'no_blank_lines_after_class_opening' => true,
+ 'no_blank_lines_after_phpdoc' => true,
+ 'no_closing_tag' => true,
+ 'no_empty_phpdoc' => true,
+ 'no_empty_statement' => true,
+ 'no_leading_import_slash' => true,
+ 'no_leading_namespace_whitespace' => true,
+ 'no_mixed_echo_print' => [
+ 'use' => 'echo'
+ ],
+ 'no_multiline_whitespace_around_double_arrow' => true,
+ 'multiline_whitespace_before_semicolons' => [
+ 'strategy' => 'no_multi_line'
+ ],
+ 'no_short_bool_cast' => true,
+ 'no_singleline_whitespace_before_semicolons' => true,
+ 'no_spaces_after_function_name' => true,
+ 'no_spaces_around_offset' => true,
+ 'no_spaces_inside_parenthesis' => true,
+ 'no_trailing_comma_in_list_call' => true,
+ 'no_trailing_comma_in_singleline_array' => true,
+ 'no_trailing_whitespace' => true,
+ 'no_trailing_whitespace_in_comment' => true,
+ 'no_unneeded_control_parentheses' => true,
+ 'no_unreachable_default_argument_value' => true,
+ 'no_useless_return' => true,
+ 'no_whitespace_before_comma_in_array' => true,
+ 'no_whitespace_in_blank_line' => true,
+ 'normalize_index_brace' => true,
+ 'not_operator_with_successor_space' => true,
+ 'object_operator_without_whitespace' => true,
+ 'ordered_imports' => ['sort_algorithm' => 'alpha'],
+ 'phpdoc_indent' => true,
+ 'general_phpdoc_tag_rename' => true,
+ 'phpdoc_no_access' => true,
+ 'phpdoc_no_package' => true,
+ 'phpdoc_no_useless_inheritdoc' => true,
+ 'phpdoc_scalar' => true,
+ 'phpdoc_single_line_var_spacing' => true,
+ 'phpdoc_summary' => true,
+ 'phpdoc_to_comment' => false,
+ 'phpdoc_trim' => true,
+ 'phpdoc_types' => true,
+ 'phpdoc_var_without_name' => true,
+ 'psr_autoloading' => true,
+ 'self_accessor' => true,
+ 'short_scalar_cast' => true,
+ 'simplified_null_return' => false, // disabled by Shift
+ 'single_blank_line_at_eof' => true,
+ 'single_blank_line_before_namespace' => true,
+ 'single_class_element_per_statement' => true,
+ 'single_import_per_statement' => true,
+ 'single_line_after_imports' => true,
+ 'no_unused_imports' => true,
+ 'single_line_comment_style' => [
+ 'comment_types' => ['hash']
+ ],
+ 'single_quote' => true,
+ 'space_after_semicolon' => true,
+ 'standardize_not_equals' => true,
+ 'switch_case_semicolon_to_colon' => true,
+ 'switch_case_space' => true,
+ 'ternary_operator_spaces' => true,
+ 'trailing_comma_in_multiline' => true,
+ 'trim_array_spaces' => true,
+ 'unary_operator_spaces' => true,
+ 'whitespace_after_comma_in_array' => true,
+];
+
+$project_path = getcwd();
+$finder = Finder::create()
+ ->in([
+ $project_path . '/src',
+ ])
+ ->name('*.php')
+ ->notName('*.blade.php')
+ ->ignoreDotFiles(true)
+ ->ignoreVCS(true);
+
+return (new Config())
+ ->setFinder($finder)
+ ->setRules($rules)
+ ->setRiskyAllowed(true)
+ ->setUsingCache(true);
diff --git a/.styleci.yml b/.styleci.yml
deleted file mode 100644
index e6d2b2c1..00000000
--- a/.styleci.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-risky: true
-preset: laravel
-enabled:
-- declare_strict_types
-disabled:
-- concat_without_spaces
-- ternary_operator_spaces
diff --git a/src/Bootstrappers/FilesystemTenancyBootstrapper.php b/src/Bootstrappers/FilesystemTenancyBootstrapper.php
index 2b4f8dfe..6f720e7c 100644
--- a/src/Bootstrappers/FilesystemTenancyBootstrapper.php
+++ b/src/Bootstrappers/FilesystemTenancyBootstrapper.php
@@ -69,7 +69,7 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
if (! $finalPrefix) {
$finalPrefix = $originalRoot
- ? rtrim($originalRoot, '/') . '/'. $suffix
+ ? rtrim($originalRoot, '/') . '/' . $suffix
: $suffix;
}
diff --git a/src/Bootstrappers/QueueTenancyBootstrapper.php b/src/Bootstrappers/QueueTenancyBootstrapper.php
index 790e1344..2f859ecd 100644
--- a/src/Bootstrappers/QueueTenancyBootstrapper.php
+++ b/src/Bootstrappers/QueueTenancyBootstrapper.php
@@ -4,18 +4,17 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Bootstrappers;
-use Illuminate\Support\Str;
use Illuminate\Config\Repository;
-use Illuminate\Queue\QueueManager;
-use Stancl\Tenancy\Contracts\Tenant;
+use Illuminate\Contracts\Events\Dispatcher;
+use Illuminate\Contracts\Foundation\Application;
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobProcessing;
-use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Queue\Events\JobRetryRequested;
+use Illuminate\Queue\QueueManager;
use Illuminate\Support\Testing\Fakes\QueueFake;
-use Illuminate\Contracts\Foundation\Application;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
+use Stancl\Tenancy\Contracts\Tenant;
class QueueTenancyBootstrapper implements TenancyBootstrapper
{
diff --git a/src/CacheManager.php b/src/CacheManager.php
index 88428353..09581201 100644
--- a/src/CacheManager.php
+++ b/src/CacheManager.php
@@ -13,7 +13,6 @@ class CacheManager extends BaseCacheManager
*
* @param string $method
* @param array $parameters
- * @return mixed
*/
public function __call($method, $parameters)
{
@@ -21,7 +20,7 @@ class CacheManager extends BaseCacheManager
if ($method === 'tags') {
$count = count($parameters);
-
+
if ($count !== 1) {
throw new \Exception("Method tags() takes exactly 1 argument. $count passed.");
}
diff --git a/src/Commands/Install.php b/src/Commands/Install.php
index dd2dd280..41492b26 100644
--- a/src/Commands/Install.php
+++ b/src/Commands/Install.php
@@ -24,8 +24,6 @@ class Install extends Command
/**
* Execute the console command.
- *
- * @return mixed
*/
public function handle()
{
diff --git a/src/Commands/Migrate.php b/src/Commands/Migrate.php
index c67d3598..52ecd47f 100644
--- a/src/Commands/Migrate.php
+++ b/src/Commands/Migrate.php
@@ -33,8 +33,6 @@ class Migrate extends MigrateCommand
/**
* Execute the console command.
- *
- * @return mixed
*/
public function handle()
{
diff --git a/src/Commands/MigrateFresh.php b/src/Commands/MigrateFresh.php
index 283d70b0..63860153 100644
--- a/src/Commands/MigrateFresh.php
+++ b/src/Commands/MigrateFresh.php
@@ -31,8 +31,6 @@ final class MigrateFresh extends Command
/**
* Execute the console command.
- *
- * @return mixed
*/
public function handle()
{
diff --git a/src/Commands/Rollback.php b/src/Commands/Rollback.php
index e60d974b..1c434189 100644
--- a/src/Commands/Rollback.php
+++ b/src/Commands/Rollback.php
@@ -42,8 +42,6 @@ class Rollback extends RollbackCommand
/**
* Execute the console command.
- *
- * @return mixed
*/
public function handle()
{
diff --git a/src/Commands/Run.php b/src/Commands/Run.php
index aa518d7a..2b20d9c3 100644
--- a/src/Commands/Run.php
+++ b/src/Commands/Run.php
@@ -27,8 +27,6 @@ class Run extends Command
/**
* Execute the console command.
- *
- * @return mixed
*/
public function handle()
{
diff --git a/src/Commands/Seed.php b/src/Commands/Seed.php
index dc97ae71..8c525208 100644
--- a/src/Commands/Seed.php
+++ b/src/Commands/Seed.php
@@ -35,8 +35,6 @@ class Seed extends SeedCommand
/**
* Execute the console command.
- *
- * @return mixed
*/
public function handle()
{
diff --git a/src/Commands/TenantDump.php b/src/Commands/TenantDump.php
index 557c6975..9c8698c6 100644
--- a/src/Commands/TenantDump.php
+++ b/src/Commands/TenantDump.php
@@ -21,10 +21,9 @@ class TenantDump extends DumpCommand
$this->specifyParameters();
}
-
public function handle(ConnectionResolverInterface $connections, Dispatcher $dispatcher): int
{
- $this->tenant()->run(fn() => parent::handle($connections, $dispatcher));
+ $this->tenant()->run(fn () => parent::handle($connections, $dispatcher));
return Command::SUCCESS;
}
diff --git a/src/Commands/TenantList.php b/src/Commands/TenantList.php
index d01afcb9..13775676 100644
--- a/src/Commands/TenantList.php
+++ b/src/Commands/TenantList.php
@@ -25,8 +25,6 @@ class TenantList extends Command
/**
* Execute the console command.
- *
- * @return mixed
*/
public function handle()
{
diff --git a/src/Concerns/ExtendsLaravelCommand.php b/src/Concerns/ExtendsLaravelCommand.php
index bdafc8f7..d08ad6b6 100644
--- a/src/Concerns/ExtendsLaravelCommand.php
+++ b/src/Concerns/ExtendsLaravelCommand.php
@@ -1,5 +1,7 @@
manager()->makeConnectionConfig(
- array_merge($templateConnection, $this->tenantConfig()), $this->getName()
+ array_merge($templateConnection, $this->tenantConfig()),
+ $this->getName()
);
}
diff --git a/src/Features/UserImpersonation.php b/src/Features/UserImpersonation.php
index 48d65bb9..f96465ff 100644
--- a/src/Features/UserImpersonation.php
+++ b/src/Features/UserImpersonation.php
@@ -33,7 +33,6 @@ class UserImpersonation implements Feature
*
* @param string|ImpersonationToken $token
* @param int $ttl
- * @return RedirectResponse
*/
public static function makeResponse($token, int $ttl = null): RedirectResponse
{
diff --git a/src/Listeners/UpdateSyncedResource.php b/src/Listeners/UpdateSyncedResource.php
index 40d4d644..9be290f0 100644
--- a/src/Listeners/UpdateSyncedResource.php
+++ b/src/Listeners/UpdateSyncedResource.php
@@ -48,8 +48,7 @@ class UpdateSyncedResource extends QueueableListener
protected function updateResourceInCentralDatabaseAndGetTenants($event, $syncedAttributes)
{
/** @var Model|SyncMaster $centralModel */
- $centralModel = $event->model->getCentralModelName()
- ::where($event->model->getGlobalIdentifierKeyName(), $event->model->getGlobalIdentifierKey())
+ $centralModel = $event->model->getCentralModelName()::where($event->model->getGlobalIdentifierKeyName(), $event->model->getGlobalIdentifierKey())
->first();
// We disable events for this call, to avoid triggering this event & listener again.
diff --git a/src/Middleware/CheckTenantForMaintenanceMode.php b/src/Middleware/CheckTenantForMaintenanceMode.php
index 8e29a31e..c1c734f5 100644
--- a/src/Middleware/CheckTenantForMaintenanceMode.php
+++ b/src/Middleware/CheckTenantForMaintenanceMode.php
@@ -5,10 +5,10 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Middleware;
use Closure;
-use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
use Stancl\Tenancy\Exceptions\TenancyNotInitializedException;
use Symfony\Component\HttpFoundation\IpUtils;
+use Symfony\Component\HttpKernel\Exception\HttpException;
class CheckTenantForMaintenanceMode extends CheckForMaintenanceMode
{
diff --git a/src/Middleware/InitializeTenancyByDomain.php b/src/Middleware/InitializeTenancyByDomain.php
index 24a1abb7..5a07112d 100644
--- a/src/Middleware/InitializeTenancyByDomain.php
+++ b/src/Middleware/InitializeTenancyByDomain.php
@@ -29,13 +29,13 @@ class InitializeTenancyByDomain extends IdentificationMiddleware
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
- * @param \Closure $next
- * @return mixed
*/
public function handle($request, Closure $next)
{
return $this->initializeTenancy(
- $request, $next, $request->getHost()
+ $request,
+ $next,
+ $request->getHost()
);
}
}
diff --git a/src/Middleware/InitializeTenancyByDomainOrSubdomain.php b/src/Middleware/InitializeTenancyByDomainOrSubdomain.php
index 94217bba..9b153db3 100644
--- a/src/Middleware/InitializeTenancyByDomainOrSubdomain.php
+++ b/src/Middleware/InitializeTenancyByDomainOrSubdomain.php
@@ -13,8 +13,6 @@ class InitializeTenancyByDomainOrSubdomain
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
- * @param \Closure $next
- * @return mixed
*/
public function handle($request, Closure $next)
{
diff --git a/src/Middleware/InitializeTenancyByPath.php b/src/Middleware/InitializeTenancyByPath.php
index 6289199b..e66400c5 100644
--- a/src/Middleware/InitializeTenancyByPath.php
+++ b/src/Middleware/InitializeTenancyByPath.php
@@ -38,7 +38,9 @@ class InitializeTenancyByPath extends IdentificationMiddleware
// simply injected into some route controller action.
if ($route->parameterNames()[0] === PathTenantResolver::$tenantParameterName) {
return $this->initializeTenancy(
- $request, $next, $route
+ $request,
+ $next,
+ $route
);
} else {
throw new RouteIsMissingTenantParameterException;
diff --git a/src/Middleware/InitializeTenancyByRequestData.php b/src/Middleware/InitializeTenancyByRequestData.php
index de75d8c5..4e1d33ff 100644
--- a/src/Middleware/InitializeTenancyByRequestData.php
+++ b/src/Middleware/InitializeTenancyByRequestData.php
@@ -36,8 +36,6 @@ class InitializeTenancyByRequestData extends IdentificationMiddleware
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
- * @param \Closure $next
- * @return mixed
*/
public function handle($request, Closure $next)
{
diff --git a/src/Middleware/InitializeTenancyBySubdomain.php b/src/Middleware/InitializeTenancyBySubdomain.php
index 55d76b05..76389df7 100644
--- a/src/Middleware/InitializeTenancyBySubdomain.php
+++ b/src/Middleware/InitializeTenancyBySubdomain.php
@@ -28,8 +28,6 @@ class InitializeTenancyBySubdomain extends InitializeTenancyByDomain
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
- * @param \Closure $next
- * @return mixed
*/
public function handle($request, Closure $next)
{
diff --git a/src/Resolvers/Contracts/CachedTenantResolver.php b/src/Resolvers/Contracts/CachedTenantResolver.php
index 968ac794..e84f1fb1 100644
--- a/src/Resolvers/Contracts/CachedTenantResolver.php
+++ b/src/Resolvers/Contracts/CachedTenantResolver.php
@@ -75,7 +75,6 @@ abstract class CachedTenantResolver implements TenantResolver
/**
* Get all the arg combinations for resolve() that can be used to find this tenant.
*
- * @param Tenant $tenant
* @return array[]
*/
abstract public function getArgsForTenant(Tenant $tenant): array;
diff --git a/src/Tenancy.php b/src/Tenancy.php
index 30f138e3..6873f93b 100644
--- a/src/Tenancy.php
+++ b/src/Tenancy.php
@@ -27,7 +27,6 @@ class Tenancy
/**
* Initializes the tenant.
* @param Tenant|int|string $tenant
- * @return void
*/
public function initialize($tenant): void
{
@@ -106,9 +105,6 @@ class Tenancy
/**
* Run a callback in the central context.
* Atomic, safely reverts to previous context.
- *
- * @param callable $callback
- * @return mixed
*/
public function central(callable $callback)
{
@@ -132,7 +128,6 @@ class Tenancy
* More performant than running $tenant->run() one by one.
*
* @param Tenant[]|\Traversable|string[]|null $tenants
- * @param callable $callback
* @return void
*/
public function runForMultiple($tenants, callable $callback)
diff --git a/src/TenancyServiceProvider.php b/src/TenancyServiceProvider.php
index dd061af3..e23200a6 100644
--- a/src/TenancyServiceProvider.php
+++ b/src/TenancyServiceProvider.php
@@ -15,8 +15,6 @@ class TenancyServiceProvider extends ServiceProvider
{
/**
* Register services.
- *
- * @return void
*/
public function register(): void
{
@@ -76,8 +74,6 @@ class TenancyServiceProvider extends ServiceProvider
/**
* Bootstrap services.
- *
- * @return void
*/
public function boot(): void
{
diff --git a/tests/BootstrapperTest.php b/tests/BootstrapperTest.php
index 588fadd8..a0320282 100644
--- a/tests/BootstrapperTest.php
+++ b/tests/BootstrapperTest.php
@@ -5,26 +5,26 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Tests;
use Illuminate\Filesystem\FilesystemAdapter;
-use ReflectionObject;
-use ReflectionProperty;
-use Illuminate\Support\Str;
-use Illuminate\Support\Facades\DB;
-use Stancl\JobPipeline\JobPipeline;
-use Stancl\Tenancy\Tests\Etc\Tenant;
use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Storage;
-use Stancl\Tenancy\Events\TenancyEnded;
-use Stancl\Tenancy\Jobs\CreateDatabase;
-use Stancl\Tenancy\Events\TenantCreated;
-use Stancl\Tenancy\Events\TenancyInitialized;
-use Stancl\Tenancy\Listeners\BootstrapTenancy;
-use Stancl\Tenancy\Listeners\RevertToCentralContext;
+use Illuminate\Support\Str;
+use ReflectionObject;
+use ReflectionProperty;
+use Stancl\JobPipeline\JobPipeline;
use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper;
-use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
+use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
+use Stancl\Tenancy\Events\TenancyEnded;
+use Stancl\Tenancy\Events\TenancyInitialized;
+use Stancl\Tenancy\Events\TenantCreated;
+use Stancl\Tenancy\Jobs\CreateDatabase;
+use Stancl\Tenancy\Listeners\BootstrapTenancy;
+use Stancl\Tenancy\Listeners\RevertToCentralContext;
+use Stancl\Tenancy\Tests\Etc\Tenant;
class BootstrapperTest extends TestCase
{
diff --git a/tests/Etc/ExampleSeeder.php b/tests/Etc/ExampleSeeder.php
index a3e36123..2f97787e 100644
--- a/tests/Etc/ExampleSeeder.php
+++ b/tests/Etc/ExampleSeeder.php
@@ -19,7 +19,7 @@ class ExampleSeeder extends Seeder
{
DB::table('users')->insert([
'name' => Str::random(10),
- 'email' => Str::random(10).'@gmail.com',
+ 'email' => Str::random(10) . '@gmail.com',
'password' => bcrypt('password'),
]);
}
diff --git a/tests/EventListenerTest.php b/tests/EventListenerTest.php
index 4a45205c..02ed8b3b 100644
--- a/tests/EventListenerTest.php
+++ b/tests/EventListenerTest.php
@@ -20,7 +20,6 @@ use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\Jobs\MigrateDatabase;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\QueueableListener;
-use Stancl\Tenancy\Tenancy;
use Stancl\Tenancy\Tests\Etc\Tenant;
class EventListenerTest extends TestCase
diff --git a/tests/MaintenanceModeTest.php b/tests/MaintenanceModeTest.php
index 4a8d8d0c..90232932 100644
--- a/tests/MaintenanceModeTest.php
+++ b/tests/MaintenanceModeTest.php
@@ -4,14 +4,12 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Tests;
-use Symfony\Component\HttpKernel\Exception\HttpException;
-use Illuminate\Foundation\Http\Exceptions\MaintenanceModeException;
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Database\Concerns\MaintenanceMode;
use Stancl\Tenancy\Middleware\CheckTenantForMaintenanceMode;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Tests\Etc\Tenant;
-use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
+use Symfony\Component\HttpKernel\Exception\HttpException;
class MaintenanceModeTest extends TestCase
{
diff --git a/tests/QueueTest.php b/tests/QueueTest.php
index a3df9cd7..fe34ba92 100644
--- a/tests/QueueTest.php
+++ b/tests/QueueTest.php
@@ -4,33 +4,31 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Tests;
-use Closure;
use Exception;
-use Illuminate\Support\Str;
use Illuminate\Bus\Queueable;
-use Spatie\Valuestore\Valuestore;
-use Illuminate\Support\Facades\DB;
-use Stancl\Tenancy\Tests\Etc\User;
-use Stancl\JobPipeline\JobPipeline;
-use Stancl\Tenancy\Tests\Etc\Tenant;
-use Illuminate\Support\Facades\Event;
-use Illuminate\Queue\SerializesModels;
-use Illuminate\Support\Facades\Schema;
-use Stancl\Tenancy\Events\TenancyEnded;
-use Stancl\Tenancy\Jobs\CreateDatabase;
-use Illuminate\Queue\InteractsWithQueue;
-use Stancl\Tenancy\Events\TenantCreated;
+use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobProcessing;
-use Illuminate\Contracts\Queue\ShouldQueue;
-use Illuminate\Foundation\Bus\Dispatchable;
-use PDO;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Queue\SerializesModels;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Event;
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Support\Str;
+use Spatie\Valuestore\Valuestore;
+use Stancl\JobPipeline\JobPipeline;
+use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
+use Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper;
+use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Events\TenancyInitialized;
+use Stancl\Tenancy\Events\TenantCreated;
+use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
-use Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper;
-use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
+use Stancl\Tenancy\Tests\Etc\Tenant;
+use Stancl\Tenancy\Tests\Etc\User;
class QueueTest extends TestCase
{
diff --git a/tests/ResourceSyncingTest.php b/tests/ResourceSyncingTest.php
index 570448d1..0ff95a52 100644
--- a/tests/ResourceSyncingTest.php
+++ b/tests/ResourceSyncingTest.php
@@ -589,7 +589,9 @@ class CentralUser extends Model implements SyncMaster
use ResourceSyncing, CentralConnection;
protected $guarded = [];
+
public $timestamps = false;
+
public $table = 'users';
public function tenants(): BelongsToMany
@@ -633,7 +635,9 @@ class ResourceUser extends Model implements Syncable
use ResourceSyncing;
protected $table = 'users';
+
protected $guarded = [];
+
public $timestamps = false;
public function getGlobalIdentifierKey()
diff --git a/tests/SingleDatabaseTenancyTest.php b/tests/SingleDatabaseTenancyTest.php
index b64478cc..d0425dd9 100644
--- a/tests/SingleDatabaseTenancyTest.php
+++ b/tests/SingleDatabaseTenancyTest.php
@@ -329,6 +329,7 @@ class Post extends Model
use BelongsToTenant;
protected $guarded = [];
+
public $timestamps = false;
public function comments()
@@ -345,6 +346,7 @@ class Post extends Model
class Comment extends Model
{
protected $guarded = [];
+
public $timestamps = false;
public function post()
@@ -368,5 +370,6 @@ class ScopedComment extends Comment
class GlobalResource extends Model
{
protected $guarded = [];
+
public $timestamps = false;
}
diff --git a/tests/TenantDatabaseManagerTest.php b/tests/TenantDatabaseManagerTest.php
index 0e1464c0..12273c85 100644
--- a/tests/TenantDatabaseManagerTest.php
+++ b/tests/TenantDatabaseManagerTest.php
@@ -104,7 +104,7 @@ class TenantDatabaseManagerTest extends TestCase
['sqlite', SQLiteDatabaseManager::class],
['pgsql', PostgreSQLDatabaseManager::class],
['pgsql', PostgreSQLSchemaManager::class],
- ['sqlsrv', MicrosoftSQLDatabaseManager::class]
+ ['sqlsrv', MicrosoftSQLDatabaseManager::class],
];
}
@@ -196,7 +196,7 @@ class TenantDatabaseManagerTest extends TestCase
]);
tenancy()->initialize($tenant);
- $schemaConfig = version_compare(app()->version(), '9.0', '>=') ?
+ $schemaConfig = version_compare(app()->version(), '9.0', '>=') ?
config('database.connections.' . config('database.default') . '.search_path') :
config('database.connections.' . config('database.default') . '.schema');
diff --git a/tests/TenantModelTest.php b/tests/TenantModelTest.php
index 46dc6a00..2d46c233 100644
--- a/tests/TenantModelTest.php
+++ b/tests/TenantModelTest.php
@@ -172,6 +172,7 @@ class MyTenant extends Tenant
class AnotherTenant extends Model implements Contracts\Tenant
{
protected $guarded = [];
+
protected $table = 'tenants';
public function getTenantKeyName(): string
diff --git a/tests/TenantUserImpersonationTest.php b/tests/TenantUserImpersonationTest.php
index c5e83853..b50db84b 100644
--- a/tests/TenantUserImpersonationTest.php
+++ b/tests/TenantUserImpersonationTest.php
@@ -274,11 +274,13 @@ class TenantUserImpersonationTest extends TestCase
class ImpersonationUser extends Authenticable
{
protected $guarded = [];
+
protected $table = 'users';
}
class AnotherImpersonationUser extends Authenticable
{
protected $guarded = [];
+
protected $table = 'users';
}
From c0f97fa04ea2c7fb2ab6e353842d5e30dcd545e2 Mon Sep 17 00:00:00 2001
From: PHP CS Fixer
Date: Wed, 20 Jul 2022 13:29:11 +0000
Subject: [PATCH 44/51] Fix code style (php-cs-fixer)
---
src/Jobs/DeleteDomains.php | 6 ------
1 file changed, 6 deletions(-)
diff --git a/src/Jobs/DeleteDomains.php b/src/Jobs/DeleteDomains.php
index fac60e43..4ea92b7f 100644
--- a/src/Jobs/DeleteDomains.php
+++ b/src/Jobs/DeleteDomains.php
@@ -5,16 +5,10 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Jobs;
use Illuminate\Bus\Queueable;
-use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
-use Stancl\Tenancy\Database\Models\Domain;
-use Stancl\Tenancy\Events\DatabaseDeleted;
-use Stancl\Tenancy\Events\DeletingDatabase;
-use Stancl\Tenancy\Events\DeletingDomain;
-use Stancl\Tenancy\Events\DomainDeleted;
class DeleteDomains
{
From 69de181b7def49be614ab3a67ec76544c32284d6 Mon Sep 17 00:00:00 2001
From: Abrar Ahmad
Date: Fri, 22 Jul 2022 22:22:33 +0500
Subject: [PATCH 45/51] removed PHP_CS_FIXER_IGNORE_ENV (#904)
---
.github/workflows/ci.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3cbda814..f0fe927a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,7 +2,6 @@ name: CI
env:
COMPOSE_INTERACTIVE_NO_CLI: 1
- PHP_CS_FIXER_IGNORE_ENV: 1
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
on:
From b47c5549ef86486a993482ce10b973dd7ed50e53 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Fri, 22 Jul 2022 19:26:59 +0200
Subject: [PATCH 46/51] [4.x] Migrate tests to Pest (#884)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Add Pest dependencies
* Add base Pest file
* Convert test cases
* Remove non-compound imports
* Adopt expectation API
* Optimize uses
* Shift cleanup
* phpunit -> pest
* Fix tests in PR #884 PHPUnit to Pest Converter (#885)
* fixed tests, remove method duplications, restore necessary inner classes
* Update CommandsTest.php
* temporary checks run on `shift-64622` on branch.
* fixed `TestSeeder` class not resolved
* fixed messed up names
* removed `uses` from individual files and add it in `Pest`
* extract tests to helpers
* use pest dataset
* Update AutomaticModeTest.php
* newline
* todo convention
* resolve reviews
* added `// todo@tests`
* remove shift branch from CI workflow
Co-authored-by: Samuel Å tancl
* check if I have write permission
* Convert newly added tests to Pest
Co-authored-by: Shift
Co-authored-by: Abrar Ahmad
---
composer.json | 11 +-
test | 2 +-
tests/AutomaticModeTest.php | 194 ++--
tests/BootstrapperTest.php | 388 ++++----
tests/CacheManagerTest.php | 170 ++--
tests/CachedTenantResolverTest.php | 149 ++-
...edDomainAndSubdomainIdentificationTest.php | 92 +-
tests/CommandsTest.php | 403 ++++----
tests/DatabasePreparationTest.php | 145 ++-
tests/DatabaseUsersTest.php | 160 ++-
tests/DeleteDomainsJobTest.php | 44 +-
tests/DomainTest.php | 146 ++-
tests/Etc/TestSeeder.php | 23 +
tests/EventListenerTest.php | 267 +++--
tests/Features/RedirectTest.php | 54 +-
tests/Features/TenantConfigTest.php | 131 ++-
tests/GlobalCacheTest.php | 71 +-
tests/MaintenanceModeTest.php | 43 +-
tests/PathIdentificationTest.php | 195 ++--
tests/Pest.php | 3 +
tests/QueueTest.php | 438 ++++-----
tests/RequestDataIdentificationTest.php | 85 +-
tests/ResourceSyncingTest.php | 920 +++++++++---------
tests/ScopeSessionsTest.php | 98 +-
tests/SingleDatabaseTenancyTest.php | 554 +++++------
tests/SubdomainTest.php | 204 ++--
tests/TenantAssetTest.php | 201 ++--
tests/TenantAwareCommandTest.php | 37 +-
tests/TenantDatabaseManagerTest.php | 473 +++++----
tests/TenantModelTest.php | 221 ++---
tests/TenantUserImpersonationTest.php | 461 +++++----
tests/UniversalRouteTest.php | 105 +-
32 files changed, 3010 insertions(+), 3478 deletions(-)
create mode 100644 tests/Etc/TestSeeder.php
create mode 100644 tests/Pest.php
diff --git a/composer.json b/composer.json
index 8123944a..3143175a 100644
--- a/composer.json
+++ b/composer.json
@@ -1,7 +1,12 @@
{
"name": "stancl/tenancy",
"description": "Automatic multi-tenancy for your Laravel application.",
- "keywords": ["laravel", "multi-tenancy", "multi-database", "tenancy"],
+ "keywords": [
+ "laravel",
+ "multi-tenancy",
+ "multi-database",
+ "tenancy"
+ ],
"license": "MIT",
"authors": [
{
@@ -20,10 +25,10 @@
"require-dev": {
"laravel/framework": "^6.0|^7.0|^8.0|^9.0",
"orchestra/testbench": "^4.0|^5.0|^6.0|^7.0",
- "phpunit/phpunit": "*",
"league/flysystem-aws-s3-v3": "^1.0|^3.0",
"doctrine/dbal": "^2.10",
- "spatie/valuestore": "^1.2.5"
+ "spatie/valuestore": "^1.2.5",
+ "pestphp/pest": "^1.21"
},
"autoload": {
"psr-4": {
diff --git a/test b/test
index 49535a7a..d8de021e 100755
--- a/test
+++ b/test
@@ -1,3 +1,3 @@
#!/bin/bash
-docker-compose exec -T test vendor/bin/phpunit "$@"
+docker-compose exec -T test vendor/bin/pest "$@"
diff --git a/tests/AutomaticModeTest.php b/tests/AutomaticModeTest.php
index 714092c3..7b5d5ded 100644
--- a/tests/AutomaticModeTest.php
+++ b/tests/AutomaticModeTest.php
@@ -2,8 +2,6 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
use Stancl\Tenancy\Events\TenancyEnded;
@@ -12,113 +10,99 @@ use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Tests\Etc\Tenant;
-class AutomaticModeTest extends TestCase
+beforeEach(function () {
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+ Event::listen(TenancyEnded::class, RevertToCentralContext::class);
+});
+
+test('context is switched when tenancy is initialized', function () {
+ contextIsSwitchedWhenTenancyInitialized();
+});
+
+test('context is reverted when tenancy is ended', function () {
+ contextIsSwitchedWhenTenancyInitialized();
+
+ tenancy()->end();
+
+ expect(app('tenancy_ended'))->toBe(true);
+});
+
+test('context is switched when tenancy is reinitialized', function () {
+ config(['tenancy.bootstrappers' => [
+ MyBootstrapper::class,
+ ]]);
+
+ $tenant = Tenant::create([
+ 'id' => 'acme',
+ ]);
+
+ tenancy()->initialize($tenant);
+
+ expect(app('tenancy_initialized_for_tenant'))->toBe('acme');
+
+ $tenant2 = Tenant::create([
+ 'id' => 'foobar',
+ ]);
+
+ tenancy()->initialize($tenant2);
+
+ expect(app('tenancy_initialized_for_tenant'))->toBe('foobar');
+});
+
+test('central helper runs callbacks in the central state', function () {
+ tenancy()->initialize($tenant = Tenant::create());
+
+ tenancy()->central(function () {
+ expect(tenant())->toBe(null);
+ });
+
+ expect(tenant())->toBe($tenant);
+});
+
+test('central helper returns the value from the callback', function () {
+ tenancy()->initialize(Tenant::create());
+
+ $this->assertSame('foo', tenancy()->central(function () {
+ return 'foo';
+ }));
+});
+
+test('central helper reverts back to tenant context', function () {
+ tenancy()->initialize($tenant = Tenant::create());
+
+ tenancy()->central(function () {
+ //
+ });
+
+ expect(tenant())->toBe($tenant);
+});
+
+test('central helper doesnt change tenancy state when called in central context', function () {
+ expect(tenancy()->initialized)->toBeFalse();
+ expect(tenant())->toBeNull();
+
+ tenancy()->central(function () {
+ //
+ });
+
+ expect(tenancy()->initialized)->toBeFalse();
+ expect(tenant())->toBeNull();
+});
+
+// todo@tests
+function contextIsSwitchedWhenTenancyInitialized()
{
- public function setUp(): void
- {
- parent::setUp();
+ config(['tenancy.bootstrappers' => [
+ MyBootstrapper::class,
+ ]]);
- Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
- Event::listen(TenancyEnded::class, RevertToCentralContext::class);
- }
+ $tenant = Tenant::create([
+ 'id' => 'acme',
+ ]);
- /** @test */
- public function context_is_switched_when_tenancy_is_initialized()
- {
- config(['tenancy.bootstrappers' => [
- MyBootstrapper::class,
- ]]);
+ tenancy()->initialize($tenant);
- $tenant = Tenant::create([
- 'id' => 'acme',
- ]);
-
- tenancy()->initialize($tenant);
-
- $this->assertSame('acme', app('tenancy_initialized_for_tenant'));
- }
-
- /** @test */
- public function context_is_reverted_when_tenancy_is_ended()
- {
- $this->context_is_switched_when_tenancy_is_initialized();
-
- tenancy()->end();
-
- $this->assertSame(true, app('tenancy_ended'));
- }
-
- /** @test */
- public function context_is_switched_when_tenancy_is_reinitialized()
- {
- config(['tenancy.bootstrappers' => [
- MyBootstrapper::class,
- ]]);
-
- $tenant = Tenant::create([
- 'id' => 'acme',
- ]);
-
- tenancy()->initialize($tenant);
-
- $this->assertSame('acme', app('tenancy_initialized_for_tenant'));
-
- $tenant2 = Tenant::create([
- 'id' => 'foobar',
- ]);
-
- tenancy()->initialize($tenant2);
-
- $this->assertSame('foobar', app('tenancy_initialized_for_tenant'));
- }
-
- /** @test */
- public function central_helper_runs_callbacks_in_the_central_state()
- {
- tenancy()->initialize($tenant = Tenant::create());
-
- tenancy()->central(function () {
- $this->assertSame(null, tenant());
- });
-
- $this->assertSame($tenant, tenant());
- }
-
- /** @test */
- public function central_helper_returns_the_value_from_the_callback()
- {
- tenancy()->initialize(Tenant::create());
-
- $this->assertSame('foo', tenancy()->central(function () {
- return 'foo';
- }));
- }
-
- /** @test */
- public function central_helper_reverts_back_to_tenant_context()
- {
- tenancy()->initialize($tenant = Tenant::create());
-
- tenancy()->central(function () {
- //
- });
-
- $this->assertSame($tenant, tenant());
- }
-
- /** @test */
- public function central_helper_doesnt_change_tenancy_state_when_called_in_central_context()
- {
- $this->assertFalse(tenancy()->initialized);
- $this->assertNull(tenant());
-
- tenancy()->central(function () {
- //
- });
-
- $this->assertFalse(tenancy()->initialized);
- $this->assertNull(tenant());
- }
+ expect(app('tenancy_initialized_for_tenant'))->toBe('acme');
}
class MyBootstrapper implements TenancyBootstrapper
diff --git a/tests/BootstrapperTest.php b/tests/BootstrapperTest.php
index a0320282..929c4e47 100644
--- a/tests/BootstrapperTest.php
+++ b/tests/BootstrapperTest.php
@@ -2,226 +2,204 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Filesystem\FilesystemAdapter;
-use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;
+use Stancl\JobPipeline\JobPipeline;
+use Stancl\Tenancy\Tests\Etc\Tenant;
+use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Storage;
-use Illuminate\Support\Str;
-use ReflectionObject;
-use ReflectionProperty;
-use Stancl\JobPipeline\JobPipeline;
-use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper;
-use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
-use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
-use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
use Stancl\Tenancy\Events\TenancyEnded;
-use Stancl\Tenancy\Events\TenancyInitialized;
-use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Jobs\CreateDatabase;
+use Stancl\Tenancy\Events\TenantCreated;
+use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
-use Stancl\Tenancy\Tests\Etc\Tenant;
+use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper;
+use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
+use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
+use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
-class BootstrapperTest extends TestCase
+beforeEach(function () {
+ Event::listen(
+ TenantCreated::class,
+ JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener()
+ );
+
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+ Event::listen(TenancyEnded::class, RevertToCentralContext::class);
+});
+
+test('database data is separated', function () {
+ config(['tenancy.bootstrappers' => [
+ DatabaseTenancyBootstrapper::class,
+ ]]);
+
+ $tenant1 = Tenant::create();
+ $tenant2 = Tenant::create();
+
+ $this->artisan('tenants:migrate');
+
+ tenancy()->initialize($tenant1);
+
+ // Create Foo user
+ DB::table('users')->insert(['name' => 'Foo', 'email' => 'foo@bar.com', 'password' => 'secret']);
+ expect(DB::table('users')->get())->toHaveCount(1);
+
+ tenancy()->initialize($tenant2);
+
+ // Assert Foo user is not in this DB
+ expect(DB::table('users')->get())->toHaveCount(0);
+ // Create Bar user
+ DB::table('users')->insert(['name' => 'Bar', 'email' => 'bar@bar.com', 'password' => 'secret']);
+ expect(DB::table('users')->get())->toHaveCount(1);
+
+ tenancy()->initialize($tenant1);
+
+ // Assert Bar user is not in this DB
+ expect(DB::table('users')->get())->toHaveCount(1);
+ expect(DB::table('users')->first()->name)->toBe('Foo');
+});
+
+test('cache data is separated', function () {
+ config([
+ 'tenancy.bootstrappers' => [
+ CacheTenancyBootstrapper::class,
+ ],
+ 'cache.default' => 'redis',
+ ]);
+
+ $tenant1 = Tenant::create();
+ $tenant2 = Tenant::create();
+
+ cache()->set('foo', 'central');
+ expect(Cache::get('foo'))->toBe('central');
+
+ tenancy()->initialize($tenant1);
+
+ // Assert central cache doesn't leak to tenant context
+ expect(Cache::has('foo'))->toBeFalse();
+
+ cache()->set('foo', 'bar');
+ expect(Cache::get('foo'))->toBe('bar');
+
+ tenancy()->initialize($tenant2);
+
+ // Assert one tenant's data doesn't leak to another tenant
+ expect(Cache::has('foo'))->toBeFalse();
+
+ cache()->set('foo', 'xyz');
+ expect(Cache::get('foo'))->toBe('xyz');
+
+ tenancy()->initialize($tenant1);
+
+ // Asset data didn't leak to original tenant
+ expect(Cache::get('foo'))->toBe('bar');
+
+ tenancy()->end();
+
+ // Asset central is still the same
+ expect(Cache::get('foo'))->toBe('central');
+});
+
+test('redis data is separated', function () {
+ config(['tenancy.bootstrappers' => [
+ RedisTenancyBootstrapper::class,
+ ]]);
+
+ $tenant1 = Tenant::create();
+ $tenant2 = Tenant::create();
+
+ tenancy()->initialize($tenant1);
+ Redis::set('foo', 'bar');
+ expect(Redis::get('foo'))->toBe('bar');
+
+ tenancy()->initialize($tenant2);
+ expect(Redis::get('foo'))->toBe(null);
+ Redis::set('foo', 'xyz');
+ Redis::set('abc', 'def');
+ expect(Redis::get('foo'))->toBe('xyz');
+ expect(Redis::get('abc'))->toBe('def');
+
+ tenancy()->initialize($tenant1);
+ expect(Redis::get('foo'))->toBe('bar');
+ expect(Redis::get('abc'))->toBe(null);
+
+ $tenant3 = Tenant::create();
+ tenancy()->initialize($tenant3);
+ expect(Redis::get('foo'))->toBe(null);
+ expect(Redis::get('abc'))->toBe(null);
+});
+
+test('filesystem data is separated', function () {
+ config(['tenancy.bootstrappers' => [
+ FilesystemTenancyBootstrapper::class,
+ ]]);
+
+ $old_storage_path = storage_path();
+ $old_storage_facade_roots = [];
+ foreach (config('tenancy.filesystem.disks') as $disk) {
+ $old_storage_facade_roots[$disk] = config("filesystems.disks.{$disk}.root");
+ }
+
+ $tenant1 = Tenant::create();
+ $tenant2 = Tenant::create();
+
+ tenancy()->initialize($tenant1);
+
+ Storage::disk('public')->put('foo', 'bar');
+ expect(Storage::disk('public')->get('foo'))->toBe('bar');
+
+ tenancy()->initialize($tenant2);
+ expect(Storage::disk('public')->exists('foo'))->toBeFalse();
+ Storage::disk('public')->put('foo', 'xyz');
+ Storage::disk('public')->put('abc', 'def');
+ expect(Storage::disk('public')->get('foo'))->toBe('xyz');
+ expect(Storage::disk('public')->get('abc'))->toBe('def');
+
+ tenancy()->initialize($tenant1);
+ expect(Storage::disk('public')->get('foo'))->toBe('bar');
+ expect(Storage::disk('public')->exists('abc'))->toBeFalse();
+
+ $tenant3 = Tenant::create();
+ tenancy()->initialize($tenant3);
+ expect(Storage::disk('public')->exists('foo'))->toBeFalse();
+ expect(Storage::disk('public')->exists('abc'))->toBeFalse();
+
+ $expected_storage_path = $old_storage_path . '/tenant' . tenant('id'); // /tenant = suffix base
+
+ // Check that disk prefixes respect the root_override logic
+ expect(getDiskPrefix('local'))->toBe($expected_storage_path . '/app/');
+ expect(getDiskPrefix('public'))->toBe($expected_storage_path . '/app/public/');
+ $this->assertSame('tenant' . tenant('id') . '/', getDiskPrefix('s3'), '/');
+
+ // Check suffixing logic
+ $new_storage_path = storage_path();
+ expect($new_storage_path)->toEqual($expected_storage_path);
+});
+
+function getDiskPrefix(string $disk): string
{
- public $mockConsoleOutput = false;
+ /** @var FilesystemAdapter $disk */
+ $disk = Storage::disk($disk);
+ $adapter = $disk->getAdapter();
- public function setUp(): void
- {
- parent::setUp();
-
- Event::listen(
- TenantCreated::class,
- JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener()
- );
-
- Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
- Event::listen(TenancyEnded::class, RevertToCentralContext::class);
+ if (! Str::startsWith(app()->version(), '9.')) {
+ return $adapter->getPathPrefix();
}
- /** @test */
- public function database_data_is_separated()
- {
- config(['tenancy.bootstrappers' => [
- DatabaseTenancyBootstrapper::class,
- ]]);
+ $prefixer = (new ReflectionObject($adapter))->getProperty('prefixer');
+ $prefixer->setAccessible(true);
- $tenant1 = Tenant::create();
- $tenant2 = Tenant::create();
+ // reflection -> instance
+ $prefixer = $prefixer->getValue($adapter);
- $this->artisan('tenants:migrate');
+ $prefix = (new ReflectionProperty($prefixer, 'prefix'));
+ $prefix->setAccessible(true);
- tenancy()->initialize($tenant1);
-
- // Create Foo user
- DB::table('users')->insert(['name' => 'Foo', 'email' => 'foo@bar.com', 'password' => 'secret']);
- $this->assertCount(1, DB::table('users')->get());
-
- tenancy()->initialize($tenant2);
-
- // Assert Foo user is not in this DB
- $this->assertCount(0, DB::table('users')->get());
- // Create Bar user
- DB::table('users')->insert(['name' => 'Bar', 'email' => 'bar@bar.com', 'password' => 'secret']);
- $this->assertCount(1, DB::table('users')->get());
-
- tenancy()->initialize($tenant1);
-
- // Assert Bar user is not in this DB
- $this->assertCount(1, DB::table('users')->get());
- $this->assertSame('Foo', DB::table('users')->first()->name);
- }
-
- /** @test */
- public function cache_data_is_separated()
- {
- config([
- 'tenancy.bootstrappers' => [
- CacheTenancyBootstrapper::class,
- ],
- 'cache.default' => 'redis',
- ]);
-
- $tenant1 = Tenant::create();
- $tenant2 = Tenant::create();
-
- cache()->set('foo', 'central');
- $this->assertSame('central', Cache::get('foo'));
-
- tenancy()->initialize($tenant1);
-
- // Assert central cache doesn't leak to tenant context
- $this->assertFalse(Cache::has('foo'));
-
- cache()->set('foo', 'bar');
- $this->assertSame('bar', Cache::get('foo'));
-
- tenancy()->initialize($tenant2);
-
- // Assert one tenant's data doesn't leak to another tenant
- $this->assertFalse(Cache::has('foo'));
-
- cache()->set('foo', 'xyz');
- $this->assertSame('xyz', Cache::get('foo'));
-
- tenancy()->initialize($tenant1);
-
- // Asset data didn't leak to original tenant
- $this->assertSame('bar', Cache::get('foo'));
-
- tenancy()->end();
-
- // Asset central is still the same
- $this->assertSame('central', Cache::get('foo'));
- }
-
- /** @test */
- public function redis_data_is_separated()
- {
- config(['tenancy.bootstrappers' => [
- RedisTenancyBootstrapper::class,
- ]]);
-
- $tenant1 = Tenant::create();
- $tenant2 = Tenant::create();
-
- tenancy()->initialize($tenant1);
- Redis::set('foo', 'bar');
- $this->assertSame('bar', Redis::get('foo'));
-
- tenancy()->initialize($tenant2);
- $this->assertSame(null, Redis::get('foo'));
- Redis::set('foo', 'xyz');
- Redis::set('abc', 'def');
- $this->assertSame('xyz', Redis::get('foo'));
- $this->assertSame('def', Redis::get('abc'));
-
- tenancy()->initialize($tenant1);
- $this->assertSame('bar', Redis::get('foo'));
- $this->assertSame(null, Redis::get('abc'));
-
- $tenant3 = Tenant::create();
- tenancy()->initialize($tenant3);
- $this->assertSame(null, Redis::get('foo'));
- $this->assertSame(null, Redis::get('abc'));
- }
-
- /** @test */
- public function filesystem_data_is_separated()
- {
- config(['tenancy.bootstrappers' => [
- FilesystemTenancyBootstrapper::class,
- ]]);
-
- $old_storage_path = storage_path();
- $old_storage_facade_roots = [];
- foreach (config('tenancy.filesystem.disks') as $disk) {
- $old_storage_facade_roots[$disk] = config("filesystems.disks.{$disk}.root");
- }
-
- $tenant1 = Tenant::create();
- $tenant2 = Tenant::create();
-
- tenancy()->initialize($tenant1);
-
- Storage::disk('public')->put('foo', 'bar');
- $this->assertSame('bar', Storage::disk('public')->get('foo'));
-
- tenancy()->initialize($tenant2);
- $this->assertFalse(Storage::disk('public')->exists('foo'));
- Storage::disk('public')->put('foo', 'xyz');
- Storage::disk('public')->put('abc', 'def');
- $this->assertSame('xyz', Storage::disk('public')->get('foo'));
- $this->assertSame('def', Storage::disk('public')->get('abc'));
-
- tenancy()->initialize($tenant1);
- $this->assertSame('bar', Storage::disk('public')->get('foo'));
- $this->assertFalse(Storage::disk('public')->exists('abc'));
-
- $tenant3 = Tenant::create();
- tenancy()->initialize($tenant3);
- $this->assertFalse(Storage::disk('public')->exists('foo'));
- $this->assertFalse(Storage::disk('public')->exists('abc'));
-
- $expected_storage_path = $old_storage_path . '/tenant' . tenant('id'); // /tenant = suffix base
-
- // Check that disk prefixes respect the root_override logic
- $this->assertSame($expected_storage_path . '/app/', $this->getDiskPrefix('local'));
- $this->assertSame($expected_storage_path . '/app/public/', $this->getDiskPrefix('public'));
- $this->assertSame('tenant' . tenant('id') . '/', $this->getDiskPrefix('s3'), '/');
-
- // Check suffixing logic
- $new_storage_path = storage_path();
- $this->assertEquals($expected_storage_path, $new_storage_path);
- }
-
- protected function getDiskPrefix(string $disk): string
- {
- /** @var FilesystemAdapter $disk */
- $disk = Storage::disk($disk);
- $adapter = $disk->getAdapter();
-
- if (! Str::startsWith(app()->version(), '9.')) {
- return $adapter->getPathPrefix();
- }
-
- $prefixer = (new ReflectionObject($adapter))->getProperty('prefixer');
- $prefixer->setAccessible(true);
-
- // reflection -> instance
- $prefixer = $prefixer->getValue($adapter);
-
- $prefix = (new ReflectionProperty($prefixer, 'prefix'));
- $prefix->setAccessible(true);
-
- return $prefix->getValue($prefixer);
- }
-
- // for queues see QueueTest
+ return $prefix->getValue($prefixer);
}
diff --git a/tests/CacheManagerTest.php b/tests/CacheManagerTest.php
index a54aaa67..7b34a7df 100644
--- a/tests/CacheManagerTest.php
+++ b/tests/CacheManagerTest.php
@@ -2,136 +2,110 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Tests\Etc\Tenant;
-class CacheManagerTest extends TestCase
-{
- public function setUp(): void
- {
- parent::setUp();
+beforeEach(function () {
+ config(['tenancy.bootstrappers' => [
+ CacheTenancyBootstrapper::class,
+ ]]);
- config(['tenancy.bootstrappers' => [
- CacheTenancyBootstrapper::class,
- ]]);
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+});
- Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
- }
+test('default tag is automatically applied', function () {
+ tenancy()->initialize(Tenant::create());
- /** @test */
- public function default_tag_is_automatically_applied()
- {
- tenancy()->initialize(Tenant::create());
+ $this->assertArrayIsSubset([config('tenancy.cache.tag_base') . tenant('id')], cache()->tags('foo')->getTags()->getNames());
+});
- $this->assertArrayIsSubset([config('tenancy.cache.tag_base') . tenant('id')], cache()->tags('foo')->getTags()->getNames());
- }
+test('tags are merged when array is passed', function () {
+ tenancy()->initialize(Tenant::create());
- /** @test */
- public function tags_are_merged_when_array_is_passed()
- {
- tenancy()->initialize(Tenant::create());
+ $expected = [config('tenancy.cache.tag_base') . tenant('id'), 'foo', 'bar'];
+ expect(cache()->tags(['foo', 'bar'])->getTags()->getNames())->toEqual($expected);
+});
- $expected = [config('tenancy.cache.tag_base') . tenant('id'), 'foo', 'bar'];
- $this->assertEquals($expected, cache()->tags(['foo', 'bar'])->getTags()->getNames());
- }
+test('tags are merged when string is passed', function () {
+ tenancy()->initialize(Tenant::create());
- /** @test */
- public function tags_are_merged_when_string_is_passed()
- {
- tenancy()->initialize(Tenant::create());
+ $expected = [config('tenancy.cache.tag_base') . tenant('id'), 'foo'];
+ expect(cache()->tags('foo')->getTags()->getNames())->toEqual($expected);
+});
- $expected = [config('tenancy.cache.tag_base') . tenant('id'), 'foo'];
- $this->assertEquals($expected, cache()->tags('foo')->getTags()->getNames());
- }
+test('exception is thrown when zero arguments are passed to tags method', function () {
+ tenancy()->initialize(Tenant::create());
- /** @test */
- public function exception_is_thrown_when_zero_arguments_are_passed_to_tags_method()
- {
- tenancy()->initialize(Tenant::create());
+ $this->expectException(\Exception::class);
+ cache()->tags();
+});
- $this->expectException(\Exception::class);
- cache()->tags();
- }
+test('exception is thrown when more than one argument is passed to tags method', function () {
+ tenancy()->initialize(Tenant::create());
- /** @test */
- public function exception_is_thrown_when_more_than_one_argument_is_passed_to_tags_method()
- {
- tenancy()->initialize(Tenant::create());
+ $this->expectException(\Exception::class);
+ cache()->tags(1, 2);
+});
- $this->expectException(\Exception::class);
- cache()->tags(1, 2);
- }
+test('tags separate cache well enough', function () {
+ $tenant1 = Tenant::create();
+ tenancy()->initialize($tenant1);
- /** @test */
- public function tags_separate_cache_well_enough()
- {
- $tenant1 = Tenant::create();
- tenancy()->initialize($tenant1);
+ cache()->put('foo', 'bar', 1);
+ expect(cache()->get('foo'))->toBe('bar');
- cache()->put('foo', 'bar', 1);
- $this->assertSame('bar', cache()->get('foo'));
+ $tenant2 = Tenant::create();
+ tenancy()->initialize($tenant2);
- $tenant2 = Tenant::create();
- tenancy()->initialize($tenant2);
+ $this->assertNotSame('bar', cache()->get('foo'));
- $this->assertNotSame('bar', cache()->get('foo'));
+ cache()->put('foo', 'xyz', 1);
+ expect(cache()->get('foo'))->toBe('xyz');
+});
- cache()->put('foo', 'xyz', 1);
- $this->assertSame('xyz', cache()->get('foo'));
- }
+test('invoking the cache helper works', function () {
+ $tenant1 = Tenant::create();
+ tenancy()->initialize($tenant1);
- /** @test */
- public function invoking_the_cache_helper_works()
- {
- $tenant1 = Tenant::create();
- tenancy()->initialize($tenant1);
+ cache(['foo' => 'bar'], 1);
+ expect(cache('foo'))->toBe('bar');
- cache(['foo' => 'bar'], 1);
- $this->assertSame('bar', cache('foo'));
+ $tenant2 = Tenant::create();
+ tenancy()->initialize($tenant2);
- $tenant2 = Tenant::create();
- tenancy()->initialize($tenant2);
+ $this->assertNotSame('bar', cache('foo'));
- $this->assertNotSame('bar', cache('foo'));
+ cache(['foo' => 'xyz'], 1);
+ expect(cache('foo'))->toBe('xyz');
+});
- cache(['foo' => 'xyz'], 1);
- $this->assertSame('xyz', cache('foo'));
- }
+test('cache is persisted', function () {
+ $tenant1 = Tenant::create();
+ tenancy()->initialize($tenant1);
- /** @test */
- public function cache_is_persisted()
- {
- $tenant1 = Tenant::create();
- tenancy()->initialize($tenant1);
+ cache(['foo' => 'bar'], 10);
+ expect(cache('foo'))->toBe('bar');
- cache(['foo' => 'bar'], 10);
- $this->assertSame('bar', cache('foo'));
+ tenancy()->end();
- tenancy()->end();
+ tenancy()->initialize($tenant1);
+ expect(cache('foo'))->toBe('bar');
+});
- tenancy()->initialize($tenant1);
- $this->assertSame('bar', cache('foo'));
- }
+test('cache is persisted when reidentification is used', function () {
+ $tenant1 = Tenant::create();
+ $tenant2 = Tenant::create();
+ tenancy()->initialize($tenant1);
- /** @test */
- public function cache_is_persisted_when_reidentification_is_used()
- {
- $tenant1 = Tenant::create();
- $tenant2 = Tenant::create();
- tenancy()->initialize($tenant1);
+ cache(['foo' => 'bar'], 10);
+ expect(cache('foo'))->toBe('bar');
- cache(['foo' => 'bar'], 10);
- $this->assertSame('bar', cache('foo'));
+ tenancy()->initialize($tenant2);
+ tenancy()->end();
- tenancy()->initialize($tenant2);
- tenancy()->end();
-
- tenancy()->initialize($tenant1);
- $this->assertSame('bar', cache('foo'));
- }
-}
+ tenancy()->initialize($tenant1);
+ expect(cache('foo'))->toBe('bar');
+});
diff --git a/tests/CachedTenantResolverTest.php b/tests/CachedTenantResolverTest.php
index e7eb52d3..dad0c010 100644
--- a/tests/CachedTenantResolverTest.php
+++ b/tests/CachedTenantResolverTest.php
@@ -2,111 +2,94 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Support\Facades\DB;
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
use Stancl\Tenancy\Tests\Etc\Tenant;
-class CachedTenantResolverTest extends TestCase
-{
- public function tearDown(): void
- {
- DomainTenantResolver::$shouldCache = false;
+afterEach(function () {
+ DomainTenantResolver::$shouldCache = false;
+});
- parent::tearDown();
- }
+test('tenants can be resolved using the cached resolver', function () {
+ $tenant = Tenant::create();
+ $tenant->domains()->create([
+ 'domain' => 'acme',
+ ]);
- /** @test */
- public function tenants_can_be_resolved_using_the_cached_resolver()
- {
- $tenant = Tenant::create();
- $tenant->domains()->create([
- 'domain' => 'acme',
- ]);
+ expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue()->toBeTrue();
+});
- $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme')));
- $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme')));
- }
+test('the underlying resolver is not touched when using the cached resolver', function () {
+ $tenant = Tenant::create();
+ $tenant->domains()->create([
+ 'domain' => 'acme',
+ ]);
- /** @test */
- public function the_underlying_resolver_is_not_touched_when_using_the_cached_resolver()
- {
- $tenant = Tenant::create();
- $tenant->domains()->create([
- 'domain' => 'acme',
- ]);
+ DB::enableQueryLog();
- DB::enableQueryLog();
+ DomainTenantResolver::$shouldCache = false;
- DomainTenantResolver::$shouldCache = false;
+ expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
+ DB::flushQueryLog();
+ expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
+ $this->assertNotEmpty(DB::getQueryLog()); // not empty
- $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme')));
- DB::flushQueryLog();
- $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme')));
- $this->assertNotEmpty(DB::getQueryLog()); // not empty
+ DomainTenantResolver::$shouldCache = true;
- DomainTenantResolver::$shouldCache = true;
+ expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
+ DB::flushQueryLog();
+ expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
+ expect(DB::getQueryLog())->toBeEmpty(); // empty
+});
- $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme')));
- DB::flushQueryLog();
- $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme')));
- $this->assertEmpty(DB::getQueryLog()); // empty
- }
+test('cache is invalidated when the tenant is updated', function () {
+ $tenant = Tenant::create();
+ $tenant->createDomain([
+ 'domain' => 'acme',
+ ]);
- /** @test */
- public function cache_is_invalidated_when_the_tenant_is_updated()
- {
- $tenant = Tenant::create();
- $tenant->createDomain([
- 'domain' => 'acme',
- ]);
+ DB::enableQueryLog();
- DB::enableQueryLog();
+ DomainTenantResolver::$shouldCache = true;
- DomainTenantResolver::$shouldCache = true;
+ expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
+ DB::flushQueryLog();
+ expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
+ expect(DB::getQueryLog())->toBeEmpty(); // empty
- $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme')));
- DB::flushQueryLog();
- $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme')));
- $this->assertEmpty(DB::getQueryLog()); // empty
+ $tenant->update([
+ 'foo' => 'bar',
+ ]);
- $tenant->update([
- 'foo' => 'bar',
- ]);
+ DB::flushQueryLog();
+ expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
+ $this->assertNotEmpty(DB::getQueryLog()); // not empty
+});
- DB::flushQueryLog();
- $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme')));
- $this->assertNotEmpty(DB::getQueryLog()); // not empty
- }
+test('cache is invalidated when a tenants domain is changed', function () {
+ $tenant = Tenant::create();
+ $tenant->createDomain([
+ 'domain' => 'acme',
+ ]);
- /** @test */
- public function cache_is_invalidated_when_a_tenants_domain_is_changed()
- {
- $tenant = Tenant::create();
- $tenant->createDomain([
- 'domain' => 'acme',
- ]);
+ DB::enableQueryLog();
- DB::enableQueryLog();
+ DomainTenantResolver::$shouldCache = true;
- DomainTenantResolver::$shouldCache = true;
+ expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
+ DB::flushQueryLog();
+ expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
+ expect(DB::getQueryLog())->toBeEmpty(); // empty
- $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme')));
- DB::flushQueryLog();
- $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme')));
- $this->assertEmpty(DB::getQueryLog()); // empty
+ $tenant->createDomain([
+ 'domain' => 'bar',
+ ]);
- $tenant->createDomain([
- 'domain' => 'bar',
- ]);
+ DB::flushQueryLog();
+ expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
+ $this->assertNotEmpty(DB::getQueryLog()); // not empty
- DB::flushQueryLog();
- $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme')));
- $this->assertNotEmpty(DB::getQueryLog()); // not empty
-
- DB::flushQueryLog();
- $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('bar')));
- $this->assertNotEmpty(DB::getQueryLog()); // not empty
- }
-}
+ DB::flushQueryLog();
+ expect($tenant->is(app(DomainTenantResolver::class)->resolve('bar')))->toBeTrue();
+ $this->assertNotEmpty(DB::getQueryLog()); // not empty
+});
diff --git a/tests/CombinedDomainAndSubdomainIdentificationTest.php b/tests/CombinedDomainAndSubdomainIdentificationTest.php
index 6712458c..db01ef99 100644
--- a/tests/CombinedDomainAndSubdomainIdentificationTest.php
+++ b/tests/CombinedDomainAndSubdomainIdentificationTest.php
@@ -2,76 +2,64 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Database\Concerns\HasDomains;
-use Stancl\Tenancy\Database\Models;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain;
+use Stancl\Tenancy\Database\Models;
-class CombinedDomainAndSubdomainIdentificationTest extends TestCase
-{
- public function setUp(): void
- {
- parent::setUp();
-
- Route::group([
- 'middleware' => InitializeTenancyByDomainOrSubdomain::class,
- ], function () {
- Route::get('/foo/{a}/{b}', function ($a, $b) {
- return "$a + $b";
- });
+beforeEach(function () {
+ Route::group([
+ 'middleware' => InitializeTenancyByDomainOrSubdomain::class,
+ ], function () {
+ Route::get('/foo/{a}/{b}', function ($a, $b) {
+ return "$a + $b";
});
+ });
- config(['tenancy.tenant_model' => CombinedTenant::class]);
- }
+ config(['tenancy.tenant_model' => CombinedTenant::class]);
+});
- /** @test */
- public function tenant_can_be_identified_by_subdomain()
- {
- config(['tenancy.central_domains' => ['localhost']]);
+test('tenant can be identified by subdomain', function () {
+ config(['tenancy.central_domains' => ['localhost']]);
- $tenant = CombinedTenant::create([
- 'id' => 'acme',
- ]);
+ $tenant = CombinedTenant::create([
+ 'id' => 'acme',
+ ]);
- $tenant->domains()->create([
- 'domain' => 'foo',
- ]);
+ $tenant->domains()->create([
+ 'domain' => 'foo',
+ ]);
- $this->assertFalse(tenancy()->initialized);
+ expect(tenancy()->initialized)->toBeFalse();
- $this
- ->get('http://foo.localhost/foo/abc/xyz')
- ->assertSee('abc + xyz');
+ $this
+ ->get('http://foo.localhost/foo/abc/xyz')
+ ->assertSee('abc + xyz');
- $this->assertTrue(tenancy()->initialized);
- $this->assertSame('acme', tenant('id'));
- }
+ expect(tenancy()->initialized)->toBeTrue();
+ expect(tenant('id'))->toBe('acme');
+});
- /** @test */
- public function tenant_can_be_identified_by_domain()
- {
- config(['tenancy.central_domains' => []]);
+test('tenant can be identified by domain', function () {
+ config(['tenancy.central_domains' => []]);
- $tenant = CombinedTenant::create([
- 'id' => 'acme',
- ]);
+ $tenant = CombinedTenant::create([
+ 'id' => 'acme',
+ ]);
- $tenant->domains()->create([
- 'domain' => 'foobar.localhost',
- ]);
+ $tenant->domains()->create([
+ 'domain' => 'foobar.localhost',
+ ]);
- $this->assertFalse(tenancy()->initialized);
+ expect(tenancy()->initialized)->toBeFalse();
- $this
- ->get('http://foobar.localhost/foo/abc/xyz')
- ->assertSee('abc + xyz');
+ $this
+ ->get('http://foobar.localhost/foo/abc/xyz')
+ ->assertSee('abc + xyz');
- $this->assertTrue(tenancy()->initialized);
- $this->assertSame('acme', tenant('id'));
- }
-}
+ expect(tenancy()->initialized)->toBeTrue();
+ expect(tenant('id'))->toBe('acme');
+});
class CombinedTenant extends Models\Tenant
{
diff --git a/tests/CommandsTest.php b/tests/CommandsTest.php
index 145a93c5..8ad67538 100644
--- a/tests/CommandsTest.php
+++ b/tests/CommandsTest.php
@@ -2,8 +2,6 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
@@ -19,219 +17,196 @@ use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Tests\Etc\ExampleSeeder;
use Stancl\Tenancy\Tests\Etc\Tenant;
-class CommandsTest extends TestCase
+beforeEach(function () {
+ Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener());
+
+ config(['tenancy.bootstrappers' => [
+ DatabaseTenancyBootstrapper::class,
+ ]]);
+
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+ Event::listen(TenancyEnded::class, RevertToCentralContext::class);
+});
+
+afterEach(function () {
+ // Cleanup tenancy config cache
+ if (file_exists(base_path('config/tenancy.php'))) {
+ unlink(base_path('config/tenancy.php'));
+ }
+});
+
+test('migrate command doesnt change the db connection', function () {
+ expect(Schema::hasTable('users'))->toBeFalse();
+
+ $old_connection_name = app(\Illuminate\Database\DatabaseManager::class)->connection()->getName();
+ Artisan::call('tenants:migrate');
+ $new_connection_name = app(\Illuminate\Database\DatabaseManager::class)->connection()->getName();
+
+ expect(Schema::hasTable('users'))->toBeFalse();
+ expect($new_connection_name)->toEqual($old_connection_name);
+ $this->assertNotEquals('tenant', $new_connection_name);
+});
+
+test('migrate command works without options', function () {
+ $tenant = Tenant::create();
+
+ expect(Schema::hasTable('users'))->toBeFalse();
+ Artisan::call('tenants:migrate');
+ expect(Schema::hasTable('users'))->toBeFalse();
+
+ tenancy()->initialize($tenant);
+
+ expect(Schema::hasTable('users'))->toBeTrue();
+});
+
+test('migrate command works with tenants option', function () {
+ $tenant = Tenant::create();
+ Artisan::call('tenants:migrate', [
+ '--tenants' => [$tenant['id']],
+ ]);
+
+ expect(Schema::hasTable('users'))->toBeFalse();
+ tenancy()->initialize(Tenant::create());
+ expect(Schema::hasTable('users'))->toBeFalse();
+
+ tenancy()->initialize($tenant);
+ expect(Schema::hasTable('users'))->toBeTrue();
+});
+
+test('migrate command loads schema state', function () {
+ $tenant = Tenant::create();
+
+ expect(Schema::hasTable('schema_users'))->toBeFalse();
+ expect(Schema::hasTable('users'))->toBeFalse();
+
+ Artisan::call('tenants:migrate --schema-path="tests/Etc/tenant-schema.dump"');
+
+ expect(Schema::hasTable('schema_users'))->toBeFalse();
+ expect(Schema::hasTable('users'))->toBeFalse();
+
+ tenancy()->initialize($tenant);
+
+ // Check for both tables to see if missing migrations also get executed
+ expect(Schema::hasTable('schema_users'))->toBeTrue();
+ expect(Schema::hasTable('users'))->toBeTrue();
+});
+
+test('dump command works', function () {
+ $tenant = Tenant::create();
+ Artisan::call('tenants:migrate');
+
+ tenancy()->initialize($tenant);
+
+ Artisan::call('tenants:dump --path="tests/Etc/tenant-schema-test.dump"');
+ expect('tests/Etc/tenant-schema-test.dump')->toBeFile();
+});
+
+test('rollback command works', function () {
+ $tenant = Tenant::create();
+ Artisan::call('tenants:migrate');
+ expect(Schema::hasTable('users'))->toBeFalse();
+
+ tenancy()->initialize($tenant);
+
+ expect(Schema::hasTable('users'))->toBeTrue();
+ Artisan::call('tenants:rollback');
+ expect(Schema::hasTable('users'))->toBeFalse();
+});
+
+// Incomplete test
+test('seed command works');
+
+test('database connection is switched to default', function () {
+ databaseConnectionSwitchedToDefault();
+});
+
+test('database connection is switched to default when tenancy has been initialized', function () {
+ tenancy()->initialize(Tenant::create());
+
+ databaseConnectionSwitchedToDefault();
+});
+
+test('run command works', function () {
+ runCommandWorks();
+});
+
+test('install command works', function () {
+ if (! is_dir($dir = app_path('Http'))) {
+ mkdir($dir, 0777, true);
+ }
+ if (! is_dir($dir = base_path('routes'))) {
+ mkdir($dir, 0777, true);
+ }
+
+ $this->artisan('tenancy:install');
+ expect(base_path('routes/tenant.php'))->toBeFile();
+ expect(base_path('config/tenancy.php'))->toBeFile();
+ expect(app_path('Providers/TenancyServiceProvider.php'))->toBeFile();
+ expect(database_path('migrations/2019_09_15_000010_create_tenants_table.php'))->toBeFile();
+ expect(database_path('migrations/2019_09_15_000020_create_domains_table.php'))->toBeFile();
+ expect(database_path('migrations/tenant'))->toBeDirectory();
+});
+
+test('migrate fresh command works', function () {
+ $tenant = Tenant::create();
+
+ expect(Schema::hasTable('users'))->toBeFalse();
+ Artisan::call('tenants:migrate-fresh');
+ expect(Schema::hasTable('users'))->toBeFalse();
+
+ tenancy()->initialize($tenant);
+
+ expect(Schema::hasTable('users'))->toBeTrue();
+
+ expect(DB::table('users')->exists())->toBeFalse();
+ DB::table('users')->insert(['name' => 'xxx', 'password' => bcrypt('password'), 'email' => 'foo@bar.xxx']);
+ expect(DB::table('users')->exists())->toBeTrue();
+
+ // test that db is wiped
+ Artisan::call('tenants:migrate-fresh');
+ expect(DB::table('users')->exists())->toBeFalse();
+});
+
+test('run command with array of tenants works', function () {
+ $tenantId1 = Tenant::create()->getTenantKey();
+ $tenantId2 = Tenant::create()->getTenantKey();
+ Artisan::call('tenants:migrate-fresh');
+
+ $this->artisan("tenants:run foo --tenants=$tenantId1 --tenants=$tenantId2 --argument='a=foo' --option='b=bar' --option='c=xyz'")
+ ->expectsOutput('Tenant: ' . $tenantId1)
+ ->expectsOutput('Tenant: ' . $tenantId2);
+});
+
+// todo@tests
+function runCommandWorks(): void
{
- public function setUp(): void
- {
- parent::setUp();
+ $id = Tenant::create()->getTenantKey();
- Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener());
+ Artisan::call('tenants:migrate', ['--tenants' => [$id]]);
- config(['tenancy.bootstrappers' => [
- DatabaseTenancyBootstrapper::class,
- ]]);
-
- Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
- Event::listen(TenancyEnded::class, RevertToCentralContext::class);
- }
-
- public function tearDown(): void
- {
- parent::tearDown();
-
- // Cleanup tenancy config cache
- if (file_exists(base_path('config/tenancy.php'))) {
- unlink(base_path('config/tenancy.php'));
- }
- }
-
- /** @test */
- public function migrate_command_doesnt_change_the_db_connection()
- {
- $this->assertFalse(Schema::hasTable('users'));
-
- $old_connection_name = app(\Illuminate\Database\DatabaseManager::class)->connection()->getName();
- Artisan::call('tenants:migrate');
- $new_connection_name = app(\Illuminate\Database\DatabaseManager::class)->connection()->getName();
-
- $this->assertFalse(Schema::hasTable('users'));
- $this->assertEquals($old_connection_name, $new_connection_name);
- $this->assertNotEquals('tenant', $new_connection_name);
- }
-
- /** @test */
- public function migrate_command_works_without_options()
- {
- $tenant = Tenant::create();
-
- $this->assertFalse(Schema::hasTable('users'));
- Artisan::call('tenants:migrate');
- $this->assertFalse(Schema::hasTable('users'));
-
- tenancy()->initialize($tenant);
-
- $this->assertTrue(Schema::hasTable('users'));
- }
-
- /** @test */
- public function migrate_command_works_with_tenants_option()
- {
- $tenant = Tenant::create();
- Artisan::call('tenants:migrate', [
- '--tenants' => [$tenant['id']],
- ]);
-
- $this->assertFalse(Schema::hasTable('users'));
- tenancy()->initialize(Tenant::create());
- $this->assertFalse(Schema::hasTable('users'));
-
- tenancy()->initialize($tenant);
- $this->assertTrue(Schema::hasTable('users'));
- }
-
- /** @test */
- public function migrate_command_loads_schema_state()
- {
- $tenant = Tenant::create();
-
- $this->assertFalse(Schema::hasTable('schema_users'));
- $this->assertFalse(Schema::hasTable('users'));
-
- Artisan::call('tenants:migrate --schema-path="tests/Etc/tenant-schema.dump"');
-
- $this->assertFalse(Schema::hasTable('schema_users'));
- $this->assertFalse(Schema::hasTable('users'));
-
- tenancy()->initialize($tenant);
-
- // Check for both tables to see if missing migrations also get executed
- $this->assertTrue(Schema::hasTable('schema_users'));
- $this->assertTrue(Schema::hasTable('users'));
- }
-
- /** @test */
- public function dump_command_works()
- {
- $tenant = Tenant::create();
- Artisan::call('tenants:migrate');
-
- tenancy()->initialize($tenant);
-
- Artisan::call('tenants:dump --path="tests/Etc/tenant-schema-test.dump"');
- $this->assertFileExists('tests/Etc/tenant-schema-test.dump');
- }
-
- /** @test */
- public function rollback_command_works()
- {
- $tenant = Tenant::create();
- Artisan::call('tenants:migrate');
- $this->assertFalse(Schema::hasTable('users'));
-
- tenancy()->initialize($tenant);
-
- $this->assertTrue(Schema::hasTable('users'));
- Artisan::call('tenants:rollback');
- $this->assertFalse(Schema::hasTable('users'));
- }
-
- /** @test */
- public function seed_command_works()
- {
- $this->markTestIncomplete();
- }
-
- /** @test */
- public function database_connection_is_switched_to_default()
- {
- $originalDBName = DB::connection()->getDatabaseName();
-
- Artisan::call('tenants:migrate');
- $this->assertSame($originalDBName, DB::connection()->getDatabaseName());
-
- Artisan::call('tenants:seed', ['--class' => ExampleSeeder::class]);
- $this->assertSame($originalDBName, DB::connection()->getDatabaseName());
-
- Artisan::call('tenants:rollback');
- $this->assertSame($originalDBName, DB::connection()->getDatabaseName());
-
- $this->run_commands_works();
- $this->assertSame($originalDBName, DB::connection()->getDatabaseName());
- }
-
- /** @test */
- public function database_connection_is_switched_to_default_when_tenancy_has_been_initialized()
- {
- tenancy()->initialize(Tenant::create());
-
- $this->database_connection_is_switched_to_default();
- }
-
- /** @test */
- public function run_commands_works()
- {
- $id = Tenant::create()->getTenantKey();
-
- Artisan::call('tenants:migrate', ['--tenants' => [$id]]);
-
- $this->artisan("tenants:run foo --tenants=$id --argument='a=foo' --option='b=bar' --option='c=xyz'")
- ->expectsOutput("User's name is Test command")
- ->expectsOutput('foo')
- ->expectsOutput('xyz');
- }
-
- /** @test */
- public function install_command_works()
- {
- if (! is_dir($dir = app_path('Http'))) {
- mkdir($dir, 0777, true);
- }
- if (! is_dir($dir = base_path('routes'))) {
- mkdir($dir, 0777, true);
- }
-
- $this->artisan('tenancy:install');
- $this->assertFileExists(base_path('routes/tenant.php'));
- $this->assertFileExists(base_path('config/tenancy.php'));
- $this->assertFileExists(app_path('Providers/TenancyServiceProvider.php'));
- $this->assertFileExists(database_path('migrations/2019_09_15_000010_create_tenants_table.php'));
- $this->assertFileExists(database_path('migrations/2019_09_15_000020_create_domains_table.php'));
- $this->assertDirectoryExists(database_path('migrations/tenant'));
- }
-
- /** @test */
- public function migrate_fresh_command_works()
- {
- $tenant = Tenant::create();
-
- $this->assertFalse(Schema::hasTable('users'));
- Artisan::call('tenants:migrate-fresh');
- $this->assertFalse(Schema::hasTable('users'));
-
- tenancy()->initialize($tenant);
-
- $this->assertTrue(Schema::hasTable('users'));
-
- $this->assertFalse(DB::table('users')->exists());
- DB::table('users')->insert(['name' => 'xxx', 'password' => bcrypt('password'), 'email' => 'foo@bar.xxx']);
- $this->assertTrue(DB::table('users')->exists());
-
- // test that db is wiped
- Artisan::call('tenants:migrate-fresh');
- $this->assertFalse(DB::table('users')->exists());
- }
-
- /** @test */
- public function run_command_with_array_of_tenants_works()
- {
- $tenantId1 = Tenant::create()->getTenantKey();
- $tenantId2 = Tenant::create()->getTenantKey();
- Artisan::call('tenants:migrate-fresh');
-
- $this->artisan("tenants:run foo --tenants=$tenantId1 --tenants=$tenantId2 --argument='a=foo' --option='b=bar' --option='c=xyz'")
- ->expectsOutput('Tenant: ' . $tenantId1)
- ->expectsOutput('Tenant: ' . $tenantId2);
- }
+ test()->artisan("tenants:run foo --tenants=$id --argument='a=foo' --option='b=bar' --option='c=xyz'")
+ ->expectsOutput("User's name is Test command")
+ ->expectsOutput('foo')
+ ->expectsOutput('xyz');
+}
+
+// todo@tests
+function databaseConnectionSwitchedToDefault()
+{
+ $originalDBName = DB::connection()->getDatabaseName();
+
+ Artisan::call('tenants:migrate');
+ expect(DB::connection()->getDatabaseName())->toBe($originalDBName);
+
+ Artisan::call('tenants:seed', ['--class' => ExampleSeeder::class]);
+ expect(DB::connection()->getDatabaseName())->toBe($originalDBName);
+
+ Artisan::call('tenants:rollback');
+ expect(DB::connection()->getDatabaseName())->toBe($originalDBName);
+
+ runCommandWorks();
+
+ expect(DB::connection()->getDatabaseName())->toBe($originalDBName);
}
diff --git a/tests/DatabasePreparationTest.php b/tests/DatabasePreparationTest.php
index 12d30059..64fa1e0f 100644
--- a/tests/DatabasePreparationTest.php
+++ b/tests/DatabasePreparationTest.php
@@ -2,11 +2,6 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
-use Illuminate\Database\Seeder;
-use Illuminate\Foundation\Auth\User as Authenticable;
-use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Schema;
use Stancl\JobPipeline\JobPipeline;
@@ -16,111 +11,85 @@ use Stancl\Tenancy\Jobs\MigrateDatabase;
use Stancl\Tenancy\Jobs\SeedDatabase;
use Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager;
use Stancl\Tenancy\Tests\Etc\Tenant;
+use Illuminate\Foundation\Auth\User as Authenticable;
+use Stancl\Tenancy\Tests\Etc\TestSeeder;
-class DatabasePreparationTest extends TestCase
-{
- /** @test */
- public function database_can_be_created_after_tenant_creation()
- {
- config(['tenancy.database.template_tenant_connection' => 'mysql']);
+test('database can be created after tenant creation', function () {
+ config(['tenancy.database.template_tenant_connection' => 'mysql']);
- Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener());
+ Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener());
- $tenant = Tenant::create();
+ $tenant = Tenant::create();
- $manager = app(MySQLDatabaseManager::class);
- $manager->setConnection('mysql');
+ $manager = app(MySQLDatabaseManager::class);
+ $manager->setConnection('mysql');
- $this->assertTrue($manager->databaseExists($tenant->database()->getName()));
- }
+ expect($manager->databaseExists($tenant->database()->getName()))->toBeTrue();
+});
- /** @test */
- public function database_can_be_migrated_after_tenant_creation()
- {
- Event::listen(TenantCreated::class, JobPipeline::make([
- CreateDatabase::class,
- MigrateDatabase::class,
- ])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener());
+test('database can be migrated after tenant creation', function () {
+ Event::listen(TenantCreated::class, JobPipeline::make([
+ CreateDatabase::class,
+ MigrateDatabase::class,
+ ])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener());
- $tenant = Tenant::create();
+ $tenant = Tenant::create();
- $tenant->run(function () {
- $this->assertTrue(Schema::hasTable('users'));
- });
- }
+ $tenant->run(function () {
+ expect(Schema::hasTable('users'))->toBeTrue();
+ });
+});
- /** @test */
- public function database_can_be_seeded_after_tenant_creation()
- {
- config(['tenancy.seeder_parameters' => [
- '--class' => TestSeeder::class,
- ]]);
+test('database can be seeded after tenant creation', function () {
+ config(['tenancy.seeder_parameters' => [
+ '--class' => TestSeeder::class,
+ ]]);
- Event::listen(TenantCreated::class, JobPipeline::make([
- CreateDatabase::class,
- MigrateDatabase::class,
- SeedDatabase::class,
- ])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener());
+ Event::listen(TenantCreated::class, JobPipeline::make([
+ CreateDatabase::class,
+ MigrateDatabase::class,
+ SeedDatabase::class,
+ ])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener());
- $tenant = Tenant::create();
+ $tenant = Tenant::create();
- $tenant->run(function () {
- $this->assertSame('Seeded User', User::first()->name);
- });
- }
+ $tenant->run(function () {
+ expect(User::first()->name)->toBe('Seeded User');
+ });
+});
- /** @test */
- public function custom_job_can_be_added_to_the_pipeline()
- {
- config(['tenancy.seeder_parameters' => [
- '--class' => TestSeeder::class,
- ]]);
+test('custom job can be added to the pipeline', function () {
+ config(['tenancy.seeder_parameters' => [
+ '--class' => TestSeeder::class,
+ ]]);
- Event::listen(TenantCreated::class, JobPipeline::make([
- CreateDatabase::class,
- MigrateDatabase::class,
- SeedDatabase::class,
- CreateSuperuser::class,
- ])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener());
+ Event::listen(TenantCreated::class, JobPipeline::make([
+ CreateDatabase::class,
+ MigrateDatabase::class,
+ SeedDatabase::class,
+ CreateSuperuser::class,
+ ])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener());
- $tenant = Tenant::create();
+ $tenant = Tenant::create();
- $tenant->run(function () {
- $this->assertSame('Foo', User::all()[1]->name);
- });
- }
-}
+ $tenant->run(function () {
+ expect(User::all()[1]->name)->toBe('Foo');
+ });
+});
class User extends Authenticable
{
protected $guarded = [];
}
-class TestSeeder extends Seeder
-{
- /**
- * Run the database seeds.
- *
- * @return void
- */
- public function run()
- {
- DB::table('users')->insert([
- 'name' => 'Seeded User',
- 'email' => 'seeded@user',
- 'password' => bcrypt('password'),
- ]);
- }
-}
-
class CreateSuperuser
{
protected $tenant;
diff --git a/tests/DatabaseUsersTest.php b/tests/DatabaseUsersTest.php
index 344239d1..93ac7ec3 100644
--- a/tests/DatabaseUsersTest.php
+++ b/tests/DatabaseUsersTest.php
@@ -2,8 +2,6 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Str;
@@ -20,111 +18,97 @@ use Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager;
use Stancl\Tenancy\TenantDatabaseManagers\PermissionControlledMySQLDatabaseManager;
use Stancl\Tenancy\Tests\Etc\Tenant;
-class DatabaseUsersTest extends TestCase
-{
- public function setUp(): void
- {
- parent::setUp();
+beforeEach(function () {
+ config([
+ 'tenancy.database.managers.mysql' => PermissionControlledMySQLDatabaseManager::class,
+ 'tenancy.database.suffix' => '',
+ 'tenancy.database.template_tenant_connection' => 'mysql',
+ ]);
- config([
- 'tenancy.database.managers.mysql' => PermissionControlledMySQLDatabaseManager::class,
- 'tenancy.database.suffix' => '',
- 'tenancy.database.template_tenant_connection' => 'mysql',
- ]);
+ Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener());
+});
- Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener());
- }
+test('users are created when permission controlled mysql manager is used', function () {
+ $tenant = new Tenant([
+ 'id' => 'foo' . Str::random(10),
+ ]);
+ $tenant->database()->makeCredentials();
- /** @test */
- public function users_are_created_when_permission_controlled_mysql_manager_is_used()
- {
- $tenant = new Tenant([
- 'id' => 'foo' . Str::random(10),
- ]);
- $tenant->database()->makeCredentials();
+ /** @var ManagesDatabaseUsers $manager */
+ $manager = $tenant->database()->manager();
+ expect($manager->userExists($tenant->database()->getUsername()))->toBeFalse();
- /** @var ManagesDatabaseUsers $manager */
- $manager = $tenant->database()->manager();
- $this->assertFalse($manager->userExists($tenant->database()->getUsername()));
+ $tenant->save();
- $tenant->save();
+ expect($manager->userExists($tenant->database()->getUsername()))->toBeTrue();
+});
- $this->assertTrue($manager->userExists($tenant->database()->getUsername()));
- }
+test('a tenants database cannot be created when the user already exists', function () {
+ $username = 'foo' . Str::random(8);
+ $tenant = Tenant::create([
+ 'tenancy_db_username' => $username,
+ ]);
- /** @test */
- public function a_tenants_database_cannot_be_created_when_the_user_already_exists()
- {
- $username = 'foo' . Str::random(8);
- $tenant = Tenant::create([
- 'tenancy_db_username' => $username,
- ]);
+ /** @var ManagesDatabaseUsers $manager */
+ $manager = $tenant->database()->manager();
+ expect($manager->userExists($tenant->database()->getUsername()))->toBeTrue();
+ expect($manager->databaseExists($tenant->database()->getName()))->toBeTrue();
- /** @var ManagesDatabaseUsers $manager */
- $manager = $tenant->database()->manager();
- $this->assertTrue($manager->userExists($tenant->database()->getUsername()));
- $this->assertTrue($manager->databaseExists($tenant->database()->getName()));
+ $this->expectException(TenantDatabaseUserAlreadyExistsException::class);
+ Event::fake([DatabaseCreated::class]);
- $this->expectException(TenantDatabaseUserAlreadyExistsException::class);
- Event::fake([DatabaseCreated::class]);
+ $tenant2 = Tenant::create([
+ 'tenancy_db_username' => $username,
+ ]);
- $tenant2 = Tenant::create([
- 'tenancy_db_username' => $username,
- ]);
+ /** @var ManagesDatabaseUsers $manager */
+ $manager2 = $tenant2->database()->manager();
- /** @var ManagesDatabaseUsers $manager */
- $manager2 = $tenant2->database()->manager();
+ // database was not created because of DB transaction
+ expect($manager2->databaseExists($tenant2->database()->getName()))->toBeFalse();
+ Event::assertNotDispatched(DatabaseCreated::class);
+});
- // database was not created because of DB transaction
- $this->assertFalse($manager2->databaseExists($tenant2->database()->getName()));
- Event::assertNotDispatched(DatabaseCreated::class);
- }
+test('correct grants are given to users', function () {
+ PermissionControlledMySQLDatabaseManager::$grants = [
+ 'ALTER', 'ALTER ROUTINE', 'CREATE',
+ ];
- /** @test */
- public function correct_grants_are_given_to_users()
- {
- PermissionControlledMySQLDatabaseManager::$grants = [
- 'ALTER', 'ALTER ROUTINE', 'CREATE',
- ];
+ $tenant = Tenant::create([
+ 'tenancy_db_username' => $user = 'user' . Str::random(8),
+ ]);
- $tenant = Tenant::create([
- 'tenancy_db_username' => $user = 'user' . Str::random(8),
- ]);
+ $query = DB::connection('mysql')->select("SHOW GRANTS FOR `{$tenant->database()->getUsername()}`@`%`")[1];
+ expect($query->{"Grants for {$user}@%"})->toStartWith('GRANT CREATE, ALTER, ALTER ROUTINE ON'); // @mysql because that's the hostname within the docker network
+});
- $query = DB::connection('mysql')->select("SHOW GRANTS FOR `{$tenant->database()->getUsername()}`@`%`")[1];
- $this->assertStringStartsWith('GRANT CREATE, ALTER, ALTER ROUTINE ON', $query->{"Grants for {$user}@%"}); // @mysql because that's the hostname within the docker network
- }
+test('having existing databases without users and switching to permission controlled mysql manager doesnt break existing dbs', function () {
+ config([
+ 'tenancy.database.managers.mysql' => MySQLDatabaseManager::class,
+ 'tenancy.database.suffix' => '',
+ 'tenancy.database.template_tenant_connection' => 'mysql',
+ 'tenancy.bootstrappers' => [
+ DatabaseTenancyBootstrapper::class,
+ ],
+ ]);
- /** @test */
- public function having_existing_databases_without_users_and_switching_to_permission_controlled_mysql_manager_doesnt_break_existing_dbs()
- {
- config([
- 'tenancy.database.managers.mysql' => MySQLDatabaseManager::class,
- 'tenancy.database.suffix' => '',
- 'tenancy.database.template_tenant_connection' => 'mysql',
- 'tenancy.bootstrappers' => [
- DatabaseTenancyBootstrapper::class,
- ],
- ]);
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
- Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+ $tenant = Tenant::create([
+ 'id' => 'foo' . Str::random(10),
+ ]);
- $tenant = Tenant::create([
- 'id' => 'foo' . Str::random(10),
- ]);
+ expect($tenant->database()->manager() instanceof MySQLDatabaseManager)->toBeTrue();
- $this->assertTrue($tenant->database()->manager() instanceof MySQLDatabaseManager);
+ tenancy()->initialize($tenant); // check if everything works
+ tenancy()->end();
- tenancy()->initialize($tenant); // check if everything works
- tenancy()->end();
+ config(['tenancy.database.managers.mysql' => PermissionControlledMySQLDatabaseManager::class]);
- config(['tenancy.database.managers.mysql' => PermissionControlledMySQLDatabaseManager::class]);
+ tenancy()->initialize($tenant); // check if everything works
- tenancy()->initialize($tenant); // check if everything works
-
- $this->assertTrue($tenant->database()->manager() instanceof PermissionControlledMySQLDatabaseManager);
- $this->assertSame('root', config('database.connections.tenant.username'));
- }
-}
+ expect($tenant->database()->manager() instanceof PermissionControlledMySQLDatabaseManager)->toBeTrue();
+ expect(config('database.connections.tenant.username'))->toBe('root');
+});
diff --git a/tests/DeleteDomainsJobTest.php b/tests/DeleteDomainsJobTest.php
index 7fce9cf3..bdee14dd 100644
--- a/tests/DeleteDomainsJobTest.php
+++ b/tests/DeleteDomainsJobTest.php
@@ -2,41 +2,31 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Stancl\Tenancy\Database\Concerns\HasDomains;
use Stancl\Tenancy\Jobs\DeleteDomains;
-class DeleteDomainsJobTest extends TestCase
-{
- public function setUp(): void
- {
- parent::setUp();
+beforeEach(function () {
+ config(['tenancy.tenant_model' => DatabaseAndDomainTenant::class]);
+});
- config(['tenancy.tenant_model' => DatabaseAndDomainTenant::class]);
- }
+test('job delete domains successfully', function (){
+ $tenant = DatabaseAndDomainTenant::create();
- /** @test */
- public function job_delete_domains_successfully()
- {
- $tenant = DatabaseAndDomainTenant::create();
+ $tenant->domains()->create([
+ 'domain' => 'foo.localhost',
+ ]);
+ $tenant->domains()->create([
+ 'domain' => 'bar.localhost',
+ ]);
- $tenant->domains()->create([
- 'domain' => 'foo.localhost',
- ]);
- $tenant->domains()->create([
- 'domain' => 'bar.localhost',
- ]);
+ expect($tenant->domains()->count())->toBe(2);
- $this->assertSame($tenant->domains()->count(), 2);
+ (new DeleteDomains($tenant))->handle();
- (new DeleteDomains($tenant))->handle();
+ expect($tenant->refresh()->domains()->count())->toBe(0);
+});
- $this->assertSame($tenant->refresh()->domains()->count(), 0);
- }
-}
-
-class DatabaseAndDomainTenant extends Etc\Tenant
+class DatabaseAndDomainTenant extends \Stancl\Tenancy\Tests\Etc\Tenant
{
use HasDomains;
-}
+}
\ No newline at end of file
diff --git a/tests/DomainTest.php b/tests/DomainTest.php
index 9c1bac28..907681ff 100644
--- a/tests/DomainTest.php
+++ b/tests/DomainTest.php
@@ -2,8 +2,6 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Database\Concerns\HasDomains;
use Stancl\Tenancy\Database\Models;
@@ -13,110 +11,92 @@ use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
-class DomainTest extends TestCase
-{
- public function setUp(): void
- {
- parent::setUp();
-
- Route::group([
- 'middleware' => InitializeTenancyByDomain::class,
- ], function () {
- Route::get('/foo/{a}/{b}', function ($a, $b) {
- return "$a + $b";
- });
+beforeEach(function () {
+ Route::group([
+ 'middleware' => InitializeTenancyByDomain::class,
+ ], function () {
+ Route::get('/foo/{a}/{b}', function ($a, $b) {
+ return "$a + $b";
});
+ });
- config(['tenancy.tenant_model' => DomainTenant::class]);
- }
+ config(['tenancy.tenant_model' => DomainTenant::class]);
+});
- /** @test */
- public function tenant_can_be_identified_using_hostname()
- {
- $tenant = DomainTenant::create();
+test('tenant can be identified using hostname', function () {
+ $tenant = DomainTenant::create();
- $id = $tenant->id;
+ $id = $tenant->id;
- $tenant->domains()->create([
- 'domain' => 'foo.localhost',
- ]);
+ $tenant->domains()->create([
+ 'domain' => 'foo.localhost',
+ ]);
- $resolvedTenant = app(DomainTenantResolver::class)->resolve('foo.localhost');
+ $resolvedTenant = app(DomainTenantResolver::class)->resolve('foo.localhost');
- $this->assertSame($id, $resolvedTenant->id);
- $this->assertSame(['foo.localhost'], $resolvedTenant->domains->pluck('domain')->toArray());
- }
+ expect($resolvedTenant->id)->toBe($id);
+ expect($resolvedTenant->domains->pluck('domain')->toArray())->toBe(['foo.localhost']);
+});
- /** @test */
- public function a_domain_can_belong_to_only_one_tenant()
- {
- $tenant = DomainTenant::create();
+test('a domain can belong to only one tenant', function () {
+ $tenant = DomainTenant::create();
- $tenant->domains()->create([
- 'domain' => 'foo.localhost',
- ]);
+ $tenant->domains()->create([
+ 'domain' => 'foo.localhost',
+ ]);
- $tenant2 = DomainTenant::create();
+ $tenant2 = DomainTenant::create();
- $this->expectException(DomainOccupiedByOtherTenantException::class);
- $tenant2->domains()->create([
- 'domain' => 'foo.localhost',
- ]);
- }
+ $this->expectException(DomainOccupiedByOtherTenantException::class);
+ $tenant2->domains()->create([
+ 'domain' => 'foo.localhost',
+ ]);
+});
- /** @test */
- public function an_exception_is_thrown_if_tenant_cannot_be_identified()
- {
- $this->expectException(TenantCouldNotBeIdentifiedOnDomainException::class);
+test('an exception is thrown if tenant cannot be identified', function () {
+ $this->expectException(TenantCouldNotBeIdentifiedOnDomainException::class);
- app(DomainTenantResolver::class)->resolve('foo.localhost');
- }
+ app(DomainTenantResolver::class)->resolve('foo.localhost');
+});
- /** @test */
- public function tenant_can_be_identified_by_domain()
- {
- $tenant = DomainTenant::create([
- 'id' => 'acme',
- ]);
+test('tenant can be identified by domain', function () {
+ $tenant = DomainTenant::create([
+ 'id' => 'acme',
+ ]);
- $tenant->domains()->create([
- 'domain' => 'foo.localhost',
- ]);
+ $tenant->domains()->create([
+ 'domain' => 'foo.localhost',
+ ]);
- $this->assertFalse(tenancy()->initialized);
+ expect(tenancy()->initialized)->toBeFalse();
- $this
- ->get('http://foo.localhost/foo/abc/xyz')
- ->assertSee('abc + xyz');
+ $this
+ ->get('http://foo.localhost/foo/abc/xyz')
+ ->assertSee('abc + xyz');
- $this->assertTrue(tenancy()->initialized);
- $this->assertSame('acme', tenant('id'));
- }
+ expect(tenancy()->initialized)->toBeTrue();
+ expect(tenant('id'))->toBe('acme');
+});
- /** @test */
- public function onfail_logic_can_be_customized()
- {
- InitializeTenancyByDomain::$onFail = function () {
- return 'foo';
- };
+test('onfail logic can be customized', function () {
+ InitializeTenancyByDomain::$onFail = function () {
+ return 'foo';
+ };
- $this
- ->get('http://foo.localhost/foo/abc/xyz')
- ->assertSee('foo');
- }
+ $this
+ ->get('http://foo.localhost/foo/abc/xyz')
+ ->assertSee('foo');
+});
- /** @test */
- public function domains_are_always_lowercase()
- {
- $tenant = DomainTenant::create();
+test('domains are always lowercase', function () {
+ $tenant = DomainTenant::create();
- $tenant->domains()->create([
- 'domain' => 'CAPITALS',
- ]);
+ $tenant->domains()->create([
+ 'domain' => 'CAPITALS',
+ ]);
- $this->assertSame('capitals', Domain::first()->domain);
- }
-}
+ expect(Domain::first()->domain)->toBe('capitals');
+});
class DomainTenant extends Models\Tenant
{
diff --git a/tests/Etc/TestSeeder.php b/tests/Etc/TestSeeder.php
new file mode 100644
index 00000000..3412948e
--- /dev/null
+++ b/tests/Etc/TestSeeder.php
@@ -0,0 +1,23 @@
+insert([
+ 'name' => 'Seeded User',
+ 'email' => 'seeded@user',
+ 'password' => bcrypt('password'),
+ ]);
+ }
+}
diff --git a/tests/EventListenerTest.php b/tests/EventListenerTest.php
index 02ed8b3b..66dcedc8 100644
--- a/tests/EventListenerTest.php
+++ b/tests/EventListenerTest.php
@@ -2,8 +2,6 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Events\CallQueuedListener;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue;
@@ -22,183 +20,164 @@ use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\QueueableListener;
use Stancl\Tenancy\Tests\Etc\Tenant;
-class EventListenerTest extends TestCase
-{
- /** @test */
- public function listeners_can_be_synchronous()
- {
- Queue::fake();
- Event::listen(TenantCreated::class, FooListener::class);
+test('listeners can be synchronous', function () {
+ Queue::fake();
+ Event::listen(TenantCreated::class, FooListener::class);
- Tenant::create();
+ Tenant::create();
- Queue::assertNothingPushed();
+ Queue::assertNothingPushed();
- $this->assertSame('bar', app('foo'));
- }
+ expect(app('foo'))->toBe('bar');
+});
- /** @test */
- public function listeners_can_be_queued_by_setting_a_static_property()
- {
- Queue::fake();
+test('listeners can be queued by setting a static property', function () {
+ Queue::fake();
- Event::listen(TenantCreated::class, FooListener::class);
- FooListener::$shouldQueue = true;
+ Event::listen(TenantCreated::class, FooListener::class);
+ FooListener::$shouldQueue = true;
- Tenant::create();
+ Tenant::create();
- Queue::assertPushed(CallQueuedListener::class, function (CallQueuedListener $job) {
- return $job->class === FooListener::class;
- });
+ Queue::assertPushed(CallQueuedListener::class, function (CallQueuedListener $job) {
+ return $job->class === FooListener::class;
+ });
- $this->assertFalse(app()->bound('foo'));
- }
+ expect(app()->bound('foo'))->toBeFalse();
+});
- /** @test */
- public function ing_events_can_be_used_to_cancel_tenant_model_actions()
- {
- Event::listen(CreatingTenant::class, function () {
- return false;
- });
+test('ing events can be used to cancel tenant model actions', function () {
+ Event::listen(CreatingTenant::class, function () {
+ return false;
+ });
- $this->assertSame(false, Tenant::create()->exists);
- $this->assertSame(0, Tenant::count());
- }
+ expect(Tenant::create()->exists)->toBe(false);
+ expect(Tenant::count())->toBe(0);
+});
- /** @test */
- public function ing_events_can_be_used_to_cancel_domain_model_actions()
- {
- $tenant = Tenant::create();
+test('ing events can be used to cancel domain model actions', function () {
+ $tenant = Tenant::create();
- Event::listen(UpdatingDomain::class, function () {
- return false;
- });
+ Event::listen(UpdatingDomain::class, function () {
+ return false;
+ });
- $domain = $tenant->domains()->create([
- 'domain' => 'acme',
- ]);
+ $domain = $tenant->domains()->create([
+ 'domain' => 'acme',
+ ]);
- $domain->update([
- 'domain' => 'foo',
- ]);
+ $domain->update([
+ 'domain' => 'foo',
+ ]);
- $this->assertSame('acme', $domain->refresh()->domain);
- }
+ expect($domain->refresh()->domain)->toBe('acme');
+});
- /** @test */
- public function ing_events_can_be_used_to_cancel_db_creation()
- {
- Event::listen(CreatingDatabase::class, function (CreatingDatabase $event) {
- $event->tenant->setInternal('create_database', false);
- });
+test('ing events can be used to cancel db creation', function () {
+ Event::listen(CreatingDatabase::class, function (CreatingDatabase $event) {
+ $event->tenant->setInternal('create_database', false);
+ });
- $tenant = Tenant::create();
- dispatch_now(new CreateDatabase($tenant));
+ $tenant = Tenant::create();
+ dispatch_now(new CreateDatabase($tenant));
- $this->assertFalse($tenant->database()->manager()->databaseExists(
- $tenant->database()->getName()
- ));
- }
+ $this->assertFalse($tenant->database()->manager()->databaseExists(
+ $tenant->database()->getName()
+ ));
+});
- /** @test */
- public function ing_events_can_be_used_to_cancel_tenancy_bootstrapping()
- {
- config(['tenancy.bootstrappers' => [
- DatabaseTenancyBootstrapper::class,
- RedisTenancyBootstrapper::class,
- ]]);
+test('ing events can be used to cancel tenancy bootstrapping', function () {
+ config(['tenancy.bootstrappers' => [
+ DatabaseTenancyBootstrapper::class,
+ RedisTenancyBootstrapper::class,
+ ]]);
- Event::listen(
- TenantCreated::class,
- JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener()
- );
+ Event::listen(
+ TenantCreated::class,
+ JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener()
+ );
- Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
- Event::listen(BootstrappingTenancy::class, function (BootstrappingTenancy $event) {
- $event->tenancy->getBootstrappersUsing = function () {
- return [DatabaseTenancyBootstrapper::class];
- };
- });
+ Event::listen(BootstrappingTenancy::class, function (BootstrappingTenancy $event) {
+ $event->tenancy->getBootstrappersUsing = function () {
+ return [DatabaseTenancyBootstrapper::class];
+ };
+ });
- tenancy()->initialize(Tenant::create());
+ tenancy()->initialize(Tenant::create());
- $this->assertSame([DatabaseTenancyBootstrapper::class], array_map('get_class', tenancy()->getBootstrappers()));
- }
+ expect(array_map('get_class', tenancy()->getBootstrappers()))->toBe([DatabaseTenancyBootstrapper::class]);
+});
- /** @test */
- public function individual_job_pipelines_can_terminate_while_leaving_others_running()
- {
- $executed = [];
+test('individual job pipelines can terminate while leaving others running', function () {
+ $executed = [];
- Event::listen(
- TenantCreated::class,
- JobPipeline::make([
- function () use (&$executed) {
- $executed[] = 'P1J1';
- },
+ Event::listen(
+ TenantCreated::class,
+ JobPipeline::make([
+ function () use (&$executed) {
+ $executed[] = 'P1J1';
+ },
- function () use (&$executed) {
- $executed[] = 'P1J2';
- },
- ])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener()
- );
+ function () use (&$executed) {
+ $executed[] = 'P1J2';
+ },
+ ])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener()
+ );
- Event::listen(
- TenantCreated::class,
- JobPipeline::make([
- function () use (&$executed) {
- $executed[] = 'P2J1';
+ Event::listen(
+ TenantCreated::class,
+ JobPipeline::make([
+ function () use (&$executed) {
+ $executed[] = 'P2J1';
- return false;
- },
+ return false;
+ },
- function () use (&$executed) {
- $executed[] = 'P2J2';
- },
- ])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener()
- );
+ function () use (&$executed) {
+ $executed[] = 'P2J2';
+ },
+ ])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener()
+ );
- Tenant::create();
+ Tenant::create();
- $this->assertSame([
- 'P1J1',
- 'P1J2',
- 'P2J1', // termminated after this
- // P2J2 was not reached
- ], $executed);
- }
+ $this->assertSame([
+ 'P1J1',
+ 'P1J2',
+ 'P2J1', // termminated after this
+ // P2J2 was not reached
+ ], $executed);
+});
- /** @test */
- public function database_is_not_migrated_if_creation_is_disabled()
- {
- Event::listen(
- TenantCreated::class,
- JobPipeline::make([
- CreateDatabase::class,
- function () {
- $this->fail("The job pipeline didn't exit.");
- },
- MigrateDatabase::class,
- ])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener()
- );
+test('database is not migrated if creation is disabled', function () {
+ Event::listen(
+ TenantCreated::class,
+ JobPipeline::make([
+ CreateDatabase::class,
+ function () {
+ $this->fail("The job pipeline didn't exit.");
+ },
+ MigrateDatabase::class,
+ ])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener()
+ );
- Tenant::create([
- 'tenancy_create_database' => false,
- 'tenancy_db_name' => 'already_created',
- ]);
+ Tenant::create([
+ 'tenancy_create_database' => false,
+ 'tenancy_db_name' => 'already_created',
+ ]);
- $this->assertFalse($this->hasFailed());
- }
-}
+ expect($this->hasFailed())->toBeFalse();
+});
class FooListener extends QueueableListener
{
diff --git a/tests/Features/RedirectTest.php b/tests/Features/RedirectTest.php
index 4f7f77a1..7686867e 100644
--- a/tests/Features/RedirectTest.php
+++ b/tests/Features/RedirectTest.php
@@ -2,45 +2,35 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests\Features;
-
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Features\CrossDomainRedirect;
use Stancl\Tenancy\Tests\Etc\Tenant;
-use Stancl\Tenancy\Tests\TestCase;
-class RedirectTest extends TestCase
-{
- /** @test */
- public function tenant_redirect_macro_replaces_only_the_hostname()
- {
- config([
- 'tenancy.features' => [CrossDomainRedirect::class],
- ]);
+test('tenant redirect macro replaces only the hostname', function () {
+ config([
+ 'tenancy.features' => [CrossDomainRedirect::class],
+ ]);
- Route::get('/foobar', function () {
- return 'Foo';
- })->name('home');
+ Route::get('/foobar', function () {
+ return 'Foo';
+ })->name('home');
- Route::get('/redirect', function () {
- return redirect()->route('home')->domain('abcd');
- });
+ Route::get('/redirect', function () {
+ return redirect()->route('home')->domain('abcd');
+ });
- $tenant = Tenant::create();
- tenancy()->initialize($tenant);
+ $tenant = Tenant::create();
+ tenancy()->initialize($tenant);
- $this->get('/redirect')
- ->assertRedirect('http://abcd/foobar');
- }
+ $this->get('/redirect')
+ ->assertRedirect('http://abcd/foobar');
+});
- /** @test */
- public function tenant_route_helper_generates_correct_url()
- {
- Route::get('/abcdef/{a?}/{b?}', function () {
- return 'Foo';
- })->name('foo');
+test('tenant route helper generates correct url', function () {
+ Route::get('/abcdef/{a?}/{b?}', function () {
+ return 'Foo';
+ })->name('foo');
- $this->assertSame('http://foo.localhost/abcdef/as/df', tenant_route('foo.localhost', 'foo', ['a' => 'as', 'b' => 'df']));
- $this->assertSame('http://foo.localhost/abcdef', tenant_route('foo.localhost', 'foo', []));
- }
-}
+ expect(tenant_route('foo.localhost', 'foo', ['a' => 'as', 'b' => 'df']))->toBe('http://foo.localhost/abcdef/as/df');
+ expect(tenant_route('foo.localhost', 'foo', []))->toBe('http://foo.localhost/abcdef');
+});
diff --git a/tests/Features/TenantConfigTest.php b/tests/Features/TenantConfigTest.php
index 37e26198..21c92592 100644
--- a/tests/Features/TenantConfigTest.php
+++ b/tests/Features/TenantConfigTest.php
@@ -2,8 +2,6 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests\Features;
-
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Events\TenancyInitialized;
@@ -11,84 +9,73 @@ use Stancl\Tenancy\Features\TenantConfig;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Tests\Etc\Tenant;
-use Stancl\Tenancy\Tests\TestCase;
-class TenantConfigTest extends TestCase
-{
- public function tearDown(): void
- {
- TenantConfig::$storageToConfigMap = [];
+afterEach(function () {
+ TenantConfig::$storageToConfigMap = [];
+});
- parent::tearDown();
- }
+test('config is merged and removed', function () {
+ expect(config('services.paypal'))->toBe(null);
+ config([
+ 'tenancy.features' => [TenantConfig::class],
+ 'tenancy.bootstrappers' => [],
+ ]);
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+ Event::listen(TenancyEnded::class, RevertToCentralContext::class);
- /** @test */
- public function config_is_merged_and_removed()
- {
- $this->assertSame(null, config('services.paypal'));
- config([
- 'tenancy.features' => [TenantConfig::class],
- 'tenancy.bootstrappers' => [],
- ]);
- Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
- Event::listen(TenancyEnded::class, RevertToCentralContext::class);
+ TenantConfig::$storageToConfigMap = [
+ 'paypal_api_public' => 'services.paypal.public',
+ 'paypal_api_private' => 'services.paypal.private',
+ ];
- TenantConfig::$storageToConfigMap = [
- 'paypal_api_public' => 'services.paypal.public',
- 'paypal_api_private' => 'services.paypal.private',
- ];
+ $tenant = Tenant::create([
+ 'paypal_api_public' => 'foo',
+ 'paypal_api_private' => 'bar',
+ ]);
- $tenant = Tenant::create([
- 'paypal_api_public' => 'foo',
- 'paypal_api_private' => 'bar',
- ]);
+ tenancy()->initialize($tenant);
+ expect(config('services.paypal'))->toBe(['public' => 'foo', 'private' => 'bar']);
- tenancy()->initialize($tenant);
- $this->assertSame(['public' => 'foo', 'private' => 'bar'], config('services.paypal'));
+ tenancy()->end();
+ $this->assertSame([
+ 'public' => null,
+ 'private' => null,
+ ], config('services.paypal'));
+});
- tenancy()->end();
- $this->assertSame([
- 'public' => null,
- 'private' => null,
- ], config('services.paypal'));
- }
+test('the value can be set to multiple config keys', function () {
+ expect(config('services.paypal'))->toBe(null);
+ config([
+ 'tenancy.features' => [TenantConfig::class],
+ 'tenancy.bootstrappers' => [],
+ ]);
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+ Event::listen(TenancyEnded::class, RevertToCentralContext::class);
- /** @test */
- public function the_value_can_be_set_to_multiple_config_keys()
- {
- $this->assertSame(null, config('services.paypal'));
- config([
- 'tenancy.features' => [TenantConfig::class],
- 'tenancy.bootstrappers' => [],
- ]);
- Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
- Event::listen(TenancyEnded::class, RevertToCentralContext::class);
+ TenantConfig::$storageToConfigMap = [
+ 'paypal_api_public' => [
+ 'services.paypal.public1',
+ 'services.paypal.public2',
+ ],
+ 'paypal_api_private' => 'services.paypal.private',
+ ];
- TenantConfig::$storageToConfigMap = [
- 'paypal_api_public' => [
- 'services.paypal.public1',
- 'services.paypal.public2',
- ],
- 'paypal_api_private' => 'services.paypal.private',
- ];
+ $tenant = Tenant::create([
+ 'paypal_api_public' => 'foo',
+ 'paypal_api_private' => 'bar',
+ ]);
- $tenant = Tenant::create([
- 'paypal_api_public' => 'foo',
- 'paypal_api_private' => 'bar',
- ]);
+ tenancy()->initialize($tenant);
+ $this->assertSame([
+ 'public1' => 'foo',
+ 'public2' => 'foo',
+ 'private' => 'bar',
+ ], config('services.paypal'));
- tenancy()->initialize($tenant);
- $this->assertSame([
- 'public1' => 'foo',
- 'public2' => 'foo',
- 'private' => 'bar',
- ], config('services.paypal'));
-
- tenancy()->end();
- $this->assertSame([
- 'public1' => null,
- 'public2' => null,
- 'private' => null,
- ], config('services.paypal'));
- }
-}
+ tenancy()->end();
+ $this->assertSame([
+ 'public1' => null,
+ 'public2' => null,
+ 'private' => null,
+ ], config('services.paypal'));
+});
diff --git a/tests/GlobalCacheTest.php b/tests/GlobalCacheTest.php
index a39a1f55..8a13395c 100644
--- a/tests/GlobalCacheTest.php
+++ b/tests/GlobalCacheTest.php
@@ -2,8 +2,6 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper;
use Stancl\Tenancy\Events\TenancyEnded;
@@ -13,49 +11,42 @@ use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Tests\Etc\Tenant;
-class GlobalCacheTest extends TestCase
-{
- public function setUp(): void
- {
- parent::setUp();
+beforeEach(function () {
+ config(['tenancy.bootstrappers' => [
+ CacheTenancyBootstrapper::class,
+ ]]);
- config(['tenancy.bootstrappers' => [
- CacheTenancyBootstrapper::class,
- ]]);
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+ Event::listen(TenancyEnded::class, RevertToCentralContext::class);
+});
- Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
- Event::listen(TenancyEnded::class, RevertToCentralContext::class);
- }
+test('global cache manager stores data in global cache', function () {
+ expect(cache('foo'))->toBe(null);
+ GlobalCache::put(['foo' => 'bar'], 1);
+ expect(GlobalCache::get('foo'))->toBe('bar');
- /** @test */
- public function global_cache_manager_stores_data_in_global_cache()
- {
- $this->assertSame(null, cache('foo'));
- GlobalCache::put(['foo' => 'bar'], 1);
- $this->assertSame('bar', GlobalCache::get('foo'));
+ $tenant1 = Tenant::create();
+ tenancy()->initialize($tenant1);
+ expect(GlobalCache::get('foo'))->toBe('bar');
- $tenant1 = Tenant::create();
- tenancy()->initialize($tenant1);
- $this->assertSame('bar', GlobalCache::get('foo'));
+ GlobalCache::put(['abc' => 'xyz'], 1);
+ cache(['def' => 'ghi'], 10);
+ expect(cache('def'))->toBe('ghi');
- GlobalCache::put(['abc' => 'xyz'], 1);
- cache(['def' => 'ghi'], 10);
- $this->assertSame('ghi', cache('def'));
+ tenancy()->end();
+ expect(GlobalCache::get('abc'))->toBe('xyz');
+ expect(GlobalCache::get('foo'))->toBe('bar');
+ expect(cache('def'))->toBe(null);
- tenancy()->end();
- $this->assertSame('xyz', GlobalCache::get('abc'));
- $this->assertSame('bar', GlobalCache::get('foo'));
- $this->assertSame(null, cache('def'));
+ $tenant2 = Tenant::create();
+ tenancy()->initialize($tenant2);
+ expect(GlobalCache::get('abc'))->toBe('xyz');
+ expect(GlobalCache::get('foo'))->toBe('bar');
+ expect(cache('def'))->toBe(null);
+ cache(['def' => 'xxx'], 1);
+ expect(cache('def'))->toBe('xxx');
- $tenant2 = Tenant::create();
- tenancy()->initialize($tenant2);
- $this->assertSame('xyz', GlobalCache::get('abc'));
- $this->assertSame('bar', GlobalCache::get('foo'));
- $this->assertSame(null, cache('def'));
- cache(['def' => 'xxx'], 1);
- $this->assertSame('xxx', cache('def'));
+ tenancy()->initialize($tenant1);
+ expect(cache('def'))->toBe('ghi');
+});
- tenancy()->initialize($tenant1);
- $this->assertSame('ghi', cache('def'));
- }
-}
diff --git a/tests/MaintenanceModeTest.php b/tests/MaintenanceModeTest.php
index 90232932..dace6c51 100644
--- a/tests/MaintenanceModeTest.php
+++ b/tests/MaintenanceModeTest.php
@@ -2,41 +2,34 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
-use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Database\Concerns\MaintenanceMode;
+use Symfony\Component\HttpKernel\Exception\HttpException;
+use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Middleware\CheckTenantForMaintenanceMode;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Tests\Etc\Tenant;
-use Symfony\Component\HttpKernel\Exception\HttpException;
-class MaintenanceModeTest extends TestCase
-{
- /** @test */
- public function tenant_can_be_in_maintenance_mode()
- {
- Route::get('/foo', function () {
- return 'bar';
- })->middleware([InitializeTenancyByDomain::class, CheckTenantForMaintenanceMode::class]);
+test('tenant can be in maintenance mode', function () {
+ Route::get('/foo', function () {
+ return 'bar';
+ })->middleware([InitializeTenancyByDomain::class, CheckTenantForMaintenanceMode::class]);
- $tenant = MaintenanceTenant::create();
- $tenant->domains()->create([
- 'domain' => 'acme.localhost',
- ]);
+ $tenant = MaintenanceTenant::create();
+ $tenant->domains()->create([
+ 'domain' => 'acme.localhost',
+ ]);
- $this->get('http://acme.localhost/foo')
- ->assertSuccessful();
+ $this->get('http://acme.localhost/foo')
+ ->assertSuccessful();
- tenancy()->end(); // flush stored tenant instance
+ tenancy()->end(); // flush stored tenant instance
- $tenant->putDownForMaintenance();
+ $tenant->putDownForMaintenance();
- $this->expectException(HttpException::class);
- $this->withoutExceptionHandling()
- ->get('http://acme.localhost/foo');
- }
-}
+ $this->expectException(HttpException::class);
+ $this->withoutExceptionHandling()
+ ->get('http://acme.localhost/foo');
+});
class MaintenanceTenant extends Tenant
{
diff --git a/tests/PathIdentificationTest.php b/tests/PathIdentificationTest.php
index 7a408ed0..4cd793d7 100644
--- a/tests/PathIdentificationTest.php
+++ b/tests/PathIdentificationTest.php
@@ -2,8 +2,6 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByPathException;
@@ -11,138 +9,117 @@ use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
use Stancl\Tenancy\Resolvers\PathTenantResolver;
use Stancl\Tenancy\Tests\Etc\Tenant;
-class PathIdentificationTest extends TestCase
-{
- public function setUp(): void
- {
- parent::setUp();
+beforeEach(function () {
+ PathTenantResolver::$tenantParameterName = 'tenant';
- PathTenantResolver::$tenantParameterName = 'tenant';
-
- Route::group([
- 'prefix' => '/{tenant}',
- 'middleware' => InitializeTenancyByPath::class,
- ], function () {
- Route::get('/foo/{a}/{b}', function ($a, $b) {
- return "$a + $b";
- });
+ Route::group([
+ 'prefix' => '/{tenant}',
+ 'middleware' => InitializeTenancyByPath::class,
+ ], function () {
+ Route::get('/foo/{a}/{b}', function ($a, $b) {
+ return "$a + $b";
});
- }
+ });
+});
- public function tearDown(): void
- {
- parent::tearDown();
+afterEach(function () {
+ // Global state cleanup
+ PathTenantResolver::$tenantParameterName = 'tenant';
+});
- // Global state cleanup
- PathTenantResolver::$tenantParameterName = 'tenant';
- }
+test('tenant can be identified by path', function () {
+ Tenant::create([
+ 'id' => 'acme',
+ ]);
- /** @test */
- public function tenant_can_be_identified_by_path()
- {
- Tenant::create([
- 'id' => 'acme',
- ]);
+ expect(tenancy()->initialized)->toBeFalse();
- $this->assertFalse(tenancy()->initialized);
+ $this->get('/acme/foo/abc/xyz');
- $this->get('/acme/foo/abc/xyz');
+ expect(tenancy()->initialized)->toBeTrue();
+ expect(tenant('id'))->toBe('acme');
+});
- $this->assertTrue(tenancy()->initialized);
- $this->assertSame('acme', tenant('id'));
- }
+test('route actions dont get the tenant id', function () {
+ Tenant::create([
+ 'id' => 'acme',
+ ]);
- /** @test */
- public function route_actions_dont_get_the_tenant_id()
- {
- Tenant::create([
- 'id' => 'acme',
- ]);
+ expect(tenancy()->initialized)->toBeFalse();
- $this->assertFalse(tenancy()->initialized);
+ $this
+ ->get('/acme/foo/abc/xyz')
+ ->assertContent('abc + xyz');
- $this
- ->get('/acme/foo/abc/xyz')
- ->assertContent('abc + xyz');
+ expect(tenancy()->initialized)->toBeTrue();
+ expect(tenant('id'))->toBe('acme');
+});
- $this->assertTrue(tenancy()->initialized);
- $this->assertSame('acme', tenant('id'));
- }
+test('exception is thrown when tenant cannot be identified by path', function () {
+ $this->expectException(TenantCouldNotBeIdentifiedByPathException::class);
- /** @test */
- public function exception_is_thrown_when_tenant_cannot_be_identified_by_path()
- {
- $this->expectException(TenantCouldNotBeIdentifiedByPathException::class);
+ $this
+ ->withoutExceptionHandling()
+ ->get('/acme/foo/abc/xyz');
- $this
- ->withoutExceptionHandling()
- ->get('/acme/foo/abc/xyz');
+ expect(tenancy()->initialized)->toBeFalse();
+});
- $this->assertFalse(tenancy()->initialized);
- }
+test('onfail logic can be customized', function () {
+ InitializeTenancyByPath::$onFail = function () {
+ return 'foo';
+ };
- /** @test */
- public function onfail_logic_can_be_customized()
- {
- InitializeTenancyByPath::$onFail = function () {
- return 'foo';
- };
+ $this
+ ->get('/acme/foo/abc/xyz')
+ ->assertContent('foo');
+});
- $this
- ->get('/acme/foo/abc/xyz')
- ->assertContent('foo');
- }
-
- /** @test */
- public function an_exception_is_thrown_when_the_routes_first_parameter_is_not_tenant()
- {
- Route::group([
- // 'prefix' => '/{tenant}', -- intentionally commented
- 'middleware' => InitializeTenancyByPath::class,
- ], function () {
- Route::get('/bar/{a}/{b}', function ($a, $b) {
- return "$a + $b";
- });
+test('an exception is thrown when the routes first parameter is not tenant', function () {
+ Route::group([
+ // 'prefix' => '/{tenant}', -- intentionally commented
+ 'middleware' => InitializeTenancyByPath::class,
+ ], function () {
+ Route::get('/bar/{a}/{b}', function ($a, $b) {
+ return "$a + $b";
});
+ });
- Tenant::create([
- 'id' => 'acme',
- ]);
+ Tenant::create([
+ 'id' => 'acme',
+ ]);
- $this->expectException(RouteIsMissingTenantParameterException::class);
+ $this->expectException(RouteIsMissingTenantParameterException::class);
- $this
- ->withoutExceptionHandling()
- ->get('/bar/foo/bar');
- }
+ $this
+ ->withoutExceptionHandling()
+ ->get('/bar/foo/bar');
+});
- /** @test */
- public function tenant_parameter_name_can_be_customized()
- {
- PathTenantResolver::$tenantParameterName = 'team';
+test('tenant parameter name can be customized', function () {
+ PathTenantResolver::$tenantParameterName = 'team';
- Route::group([
- 'prefix' => '/{team}',
- 'middleware' => InitializeTenancyByPath::class,
- ], function () {
- Route::get('/bar/{a}/{b}', function ($a, $b) {
- return "$a + $b";
- });
+ Route::group([
+ 'prefix' => '/{team}',
+ 'middleware' => InitializeTenancyByPath::class,
+ ], function () {
+ Route::get('/bar/{a}/{b}', function ($a, $b) {
+ return "$a + $b";
});
+ });
- Tenant::create([
- 'id' => 'acme',
- ]);
+ Tenant::create([
+ 'id' => 'acme',
+ ]);
- $this
- ->get('/acme/bar/abc/xyz')
- ->assertContent('abc + xyz');
+ $this
+ ->get('/acme/bar/abc/xyz')
+ ->assertContent('abc + xyz');
- // Parameter for resolver is changed, so the /{tenant}/foo route will no longer work.
- $this->expectException(RouteIsMissingTenantParameterException::class);
+ // Parameter for resolver is changed, so the /{tenant}/foo route will no longer work.
+ $this->expectException(RouteIsMissingTenantParameterException::class);
- $this
- ->withoutExceptionHandling()
- ->get('/acme/foo/abc/xyz');
- }
-}
+ $this
+ ->withoutExceptionHandling()
+ ->get('/acme/foo/abc/xyz');
+});
diff --git a/tests/Pest.php b/tests/Pest.php
new file mode 100644
index 00000000..9325cf53
--- /dev/null
+++ b/tests/Pest.php
@@ -0,0 +1,3 @@
+in(__DIR__);
diff --git a/tests/QueueTest.php b/tests/QueueTest.php
index fe34ba92..1e662645 100644
--- a/tests/QueueTest.php
+++ b/tests/QueueTest.php
@@ -2,264 +2,229 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
-use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
-use Illuminate\Database\Schema\Blueprint;
use Illuminate\Foundation\Bus\Dispatchable;
-use Illuminate\Queue\Events\JobProcessed;
-use Illuminate\Queue\Events\JobProcessing;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
-use Illuminate\Support\Facades\DB;
-use Illuminate\Support\Facades\Event;
-use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Spatie\Valuestore\Valuestore;
+use Illuminate\Support\Facades\DB;
+use Stancl\Tenancy\Tests\Etc\User;
use Stancl\JobPipeline\JobPipeline;
-use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
-use Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper;
+use Stancl\Tenancy\Tests\Etc\Tenant;
+use Illuminate\Support\Facades\Event;
+use Illuminate\Support\Facades\Schema;
use Stancl\Tenancy\Events\TenancyEnded;
-use Stancl\Tenancy\Events\TenancyInitialized;
-use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Jobs\CreateDatabase;
+use Stancl\Tenancy\Events\TenantCreated;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Queue\Events\JobProcessed;
+use Illuminate\Queue\Events\JobProcessing;
+use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
-use Stancl\Tenancy\Tests\Etc\Tenant;
-use Stancl\Tenancy\Tests\Etc\User;
+use Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper;
+use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
-class QueueTest extends TestCase
+beforeEach(function () {
+ config([
+ 'tenancy.bootstrappers' => [
+ QueueTenancyBootstrapper::class,
+ DatabaseTenancyBootstrapper::class,
+ ],
+ 'queue.default' => 'redis',
+ ]);
+
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+ Event::listen(TenancyEnded::class, RevertToCentralContext::class);
+
+ createValueStore();
+});
+
+afterEach(function () {
+ $this->valuestore->flush();
+});
+
+test('tenant id is passed to tenant queues', function () {
+ config(['queue.default' => 'sync']);
+
+ $tenant = Tenant::create();
+
+ tenancy()->initialize($tenant);
+
+ Event::fake([JobProcessing::class, JobProcessed::class]);
+
+ dispatch(new TestJob($this->valuestore));
+
+ Event::assertDispatched(JobProcessing::class, function ($event) {
+ return $event->job->payload()['tenant_id'] === tenant('id');
+ });
+});
+
+test('tenant id is not passed to central queues', function () {
+ $tenant = Tenant::create();
+
+ tenancy()->initialize($tenant);
+
+ Event::fake();
+
+ config(['queue.connections.central' => [
+ 'driver' => 'sync',
+ 'central' => true,
+ ]]);
+
+ dispatch(new TestJob($this->valuestore))->onConnection('central');
+
+ Event::assertDispatched(JobProcessing::class, function ($event) {
+ return ! isset($event->job->payload()['tenant_id']);
+ });
+});
+
+test('tenancy is initialized inside queues', function (bool $shouldEndTenancy) {
+ withTenantDatabases();
+ withFailedJobs();
+
+ $tenant = Tenant::create();
+
+ tenancy()->initialize($tenant);
+
+ withUsers();
+
+ $user = User::create(['name' => 'Foo', 'email' => 'foo@bar.com', 'password' => 'secret']);
+
+ $this->valuestore->put('userName', 'Bar');
+
+ dispatch(new TestJob($this->valuestore, $user));
+
+ expect($this->valuestore->has('tenant_id'))->toBeFalse();
+
+ if ($shouldEndTenancy) {
+ tenancy()->end();
+ }
+
+ $this->artisan('queue:work --once');
+
+ expect(DB::connection('central')->table('failed_jobs')->count())->toBe(0);
+
+ expect($this->valuestore->get('tenant_id'))->toBe('The current tenant id is: ' . $tenant->id);
+
+ $tenant->run(function () use ($user) {
+ expect($user->fresh()->name)->toBe('Bar');
+ });
+})->with([true, false]);;
+
+test('tenancy is initialized when retrying jobs', function (bool $shouldEndTenancy) {
+ if (! Str::startsWith(app()->version(), '8')) {
+ $this->markTestSkipped('queue:retry tenancy is only supported in Laravel 8');
+ }
+
+ withFailedJobs();
+ withTenantDatabases();
+
+ $tenant = Tenant::create();
+
+ tenancy()->initialize($tenant);
+
+ withUsers();
+
+ $user = User::create(['name' => 'Foo', 'email' => 'foo@bar.com', 'password' => 'secret']);
+
+ $this->valuestore->put('userName', 'Bar');
+ $this->valuestore->put('shouldFail', true);
+
+ dispatch(new TestJob($this->valuestore, $user));
+
+ expect($this->valuestore->has('tenant_id'))->toBeFalse();
+
+ if ($shouldEndTenancy) {
+ tenancy()->end();
+ }
+
+ $this->artisan('queue:work --once');
+
+ expect(DB::connection('central')->table('failed_jobs')->count())->toBe(1);
+ expect($this->valuestore->get('tenant_id'))->toBeNull(); // job failed
+
+ $this->artisan('queue:retry all');
+ $this->artisan('queue:work --once');
+
+ expect(DB::connection('central')->table('failed_jobs')->count())->toBe(0);
+
+ expect($this->valuestore->get('tenant_id'))->toBe('The current tenant id is: ' . $tenant->id); // job succeeded
+
+ $tenant->run(function () use ($user) {
+ expect($user->fresh()->name)->toBe('Bar');
+ });
+})->with([true, false]);
+
+test('the tenant used by the job doesnt change when the current tenant changes', function () {
+ $tenant1 = Tenant::create([
+ 'id' => 'acme',
+ ]);
+
+ tenancy()->initialize($tenant1);
+
+ dispatch(new TestJob($this->valuestore));
+
+ $tenant2 = Tenant::create([
+ 'id' => 'foobar',
+ ]);
+
+ tenancy()->initialize($tenant2);
+
+ expect($this->valuestore->has('tenant_id'))->toBeFalse();
+ $this->artisan('queue:work --once');
+
+ expect($this->valuestore->get('tenant_id'))->toBe('The current tenant id is: acme');
+});
+
+function createValueStore(): void
{
- public $mockConsoleOutput = false;
+ $valueStorePath = __DIR__ . '/Etc/tmp/queuetest.json';
- /** @var Valuestore */
- protected $valuestore;
-
- public function setUp(): void
- {
- parent::setUp();
-
- config([
- 'tenancy.bootstrappers' => [
- QueueTenancyBootstrapper::class,
- DatabaseTenancyBootstrapper::class,
- ],
- 'queue.default' => 'redis',
- ]);
-
- Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
- Event::listen(TenancyEnded::class, RevertToCentralContext::class);
-
- $this->createValueStore();
- }
-
- public function tearDown(): void
- {
- $this->valuestore->flush();
- }
-
- protected function createValueStore(): void
- {
- $valueStorePath = __DIR__ . '/Etc/tmp/queuetest.json';
-
- if (! file_exists($valueStorePath)) {
- // The directory sometimes goes missing as well when the file is deleted in git
- if (! is_dir(__DIR__ . '/Etc/tmp')) {
- mkdir(__DIR__ . '/Etc/tmp');
- }
-
- file_put_contents($valueStorePath, '');
+ if (! file_exists($valueStorePath)) {
+ // The directory sometimes goes missing as well when the file is deleted in git
+ if (! is_dir(__DIR__ . '/Etc/tmp')) {
+ mkdir(__DIR__ . '/Etc/tmp');
}
- $this->valuestore = Valuestore::make($valueStorePath)->flush();
+ file_put_contents($valueStorePath, '');
}
- protected function withFailedJobs()
- {
- Schema::connection('central')->create('failed_jobs', function (Blueprint $table) {
- $table->increments('id');
- $table->string('uuid')->unique();
- $table->text('connection');
- $table->text('queue');
- $table->longText('payload');
- $table->longText('exception');
- $table->timestamp('failed_at')->useCurrent();
- });
- }
+ test()->valuestore = Valuestore::make($valueStorePath)->flush();
+}
- protected function withUsers()
- {
- Schema::create('users', function (Blueprint $table) {
- $table->increments('id');
- $table->string('name');
- $table->string('email')->unique();
- $table->string('password');
- $table->rememberToken();
- $table->timestamps();
- });
- }
+function withFailedJobs()
+{
+ Schema::connection('central')->create('failed_jobs', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('uuid')->unique();
+ $table->text('connection');
+ $table->text('queue');
+ $table->longText('payload');
+ $table->longText('exception');
+ $table->timestamp('failed_at')->useCurrent();
+ });
+}
- protected function withTenantDatabases()
- {
- Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener());
- }
+function withUsers()
+{
+ Schema::create('users', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('name');
+ $table->string('email')->unique();
+ $table->string('password');
+ $table->rememberToken();
+ $table->timestamps();
+ });
+}
- /** @test */
- public function tenant_id_is_passed_to_tenant_queues()
- {
- config(['queue.default' => 'sync']);
-
- $tenant = Tenant::create();
-
- tenancy()->initialize($tenant);
-
- Event::fake([JobProcessing::class, JobProcessed::class]);
-
- dispatch(new TestJob($this->valuestore));
-
- Event::assertDispatched(JobProcessing::class, function ($event) {
- return $event->job->payload()['tenant_id'] === tenant('id');
- });
- }
-
- /** @test */
- public function tenant_id_is_not_passed_to_central_queues()
- {
- $tenant = Tenant::create();
-
- tenancy()->initialize($tenant);
-
- Event::fake();
-
- config(['queue.connections.central' => [
- 'driver' => 'sync',
- 'central' => true,
- ]]);
-
- dispatch(new TestJob($this->valuestore))->onConnection('central');
-
- Event::assertDispatched(JobProcessing::class, function ($event) {
- return ! isset($event->job->payload()['tenant_id']);
- });
- }
-
- /**
- * @test
- *
- * @testWith [true]
- * [false]
- */
- public function tenancy_is_initialized_inside_queues(bool $shouldEndTenancy)
- {
- $this->withTenantDatabases();
- $this->withFailedJobs();
-
- $tenant = Tenant::create();
-
- tenancy()->initialize($tenant);
-
- $this->withUsers();
-
- $user = User::create(['name' => 'Foo', 'email' => 'foo@bar.com', 'password' => 'secret']);
-
- $this->valuestore->put('userName', 'Bar');
-
- dispatch(new TestJob($this->valuestore, $user));
-
- $this->assertFalse($this->valuestore->has('tenant_id'));
-
- if ($shouldEndTenancy) {
- tenancy()->end();
- }
-
- $this->artisan('queue:work --once');
-
- $this->assertSame(0, DB::connection('central')->table('failed_jobs')->count());
-
- $this->assertSame('The current tenant id is: ' . $tenant->id, $this->valuestore->get('tenant_id'));
-
- $tenant->run(function () use ($user) {
- $this->assertSame('Bar', $user->fresh()->name);
- });
- }
-
- /**
- * @test
- *
- * @testWith [true]
- * [false]
- */
- public function tenancy_is_initialized_when_retrying_jobs(bool $shouldEndTenancy)
- {
- if (! Str::startsWith(app()->version(), '8')) {
- $this->markTestSkipped('queue:retry tenancy is only supported in Laravel 8');
- }
-
- $this->withFailedJobs();
- $this->withTenantDatabases();
-
- $tenant = Tenant::create();
-
- tenancy()->initialize($tenant);
-
- $this->withUsers();
-
- $user = User::create(['name' => 'Foo', 'email' => 'foo@bar.com', 'password' => 'secret']);
-
- $this->valuestore->put('userName', 'Bar');
- $this->valuestore->put('shouldFail', true);
-
- dispatch(new TestJob($this->valuestore, $user));
-
- $this->assertFalse($this->valuestore->has('tenant_id'));
-
- if ($shouldEndTenancy) {
- tenancy()->end();
- }
-
- $this->artisan('queue:work --once');
-
- $this->assertSame(1, DB::connection('central')->table('failed_jobs')->count());
- $this->assertNull($this->valuestore->get('tenant_id')); // job failed
-
- $this->artisan('queue:retry all');
- $this->artisan('queue:work --once');
-
- $this->assertSame(0, DB::connection('central')->table('failed_jobs')->count());
-
- $this->assertSame('The current tenant id is: ' . $tenant->id, $this->valuestore->get('tenant_id')); // job succeeded
-
- $tenant->run(function () use ($user) {
- $this->assertSame('Bar', $user->fresh()->name);
- });
- }
-
- /** @test */
- public function the_tenant_used_by_the_job_doesnt_change_when_the_current_tenant_changes()
- {
- $tenant1 = Tenant::create([
- 'id' => 'acme',
- ]);
-
- tenancy()->initialize($tenant1);
-
- dispatch(new TestJob($this->valuestore));
-
- $tenant2 = Tenant::create([
- 'id' => 'foobar',
- ]);
-
- tenancy()->initialize($tenant2);
-
- $this->assertFalse($this->valuestore->has('tenant_id'));
- $this->artisan('queue:work --once');
-
- $this->assertSame('The current tenant id is: acme', $this->valuestore->get('tenant_id'));
- }
+function withTenantDatabases()
+{
+ Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener());
}
class TestJob implements ShouldQueue
@@ -297,3 +262,4 @@ class TestJob implements ShouldQueue
}
}
}
+
diff --git a/tests/RequestDataIdentificationTest.php b/tests/RequestDataIdentificationTest.php
index 52a502f9..81bdda53 100644
--- a/tests/RequestDataIdentificationTest.php
+++ b/tests/RequestDataIdentificationTest.php
@@ -2,64 +2,49 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
use Stancl\Tenancy\Tests\Etc\Tenant;
-class RequestDataIdentificationTest extends TestCase
-{
- public function setUp(): void
- {
- parent::setUp();
+beforeEach(function () {
+ config([
+ 'tenancy.central_domains' => [
+ 'localhost',
+ ],
+ ]);
- config([
- 'tenancy.central_domains' => [
- 'localhost',
- ],
- ]);
+ Route::middleware(InitializeTenancyByRequestData::class)->get('/test', function () {
+ return 'Tenant id: ' . tenant('id');
+ });
+});
- Route::middleware(InitializeTenancyByRequestData::class)->get('/test', function () {
- return 'Tenant id: ' . tenant('id');
- });
- }
+afterEach(function () {
+ InitializeTenancyByRequestData::$header = 'X-Tenant';
+ InitializeTenancyByRequestData::$queryParameter = 'tenant';
+});
- public function tearDown(): void
- {
- InitializeTenancyByRequestData::$header = 'X-Tenant';
- InitializeTenancyByRequestData::$queryParameter = 'tenant';
+test('header identification works', function () {
+ InitializeTenancyByRequestData::$header = 'X-Tenant';
+ $tenant = Tenant::create();
+ $tenant2 = Tenant::create();
- parent::tearDown();
- }
+ $this
+ ->withoutExceptionHandling()
+ ->get('test', [
+ 'X-Tenant' => $tenant->id,
+ ])
+ ->assertSee($tenant->id);
+});
- /** @test */
- public function header_identification_works()
- {
- InitializeTenancyByRequestData::$header = 'X-Tenant';
- $tenant = Tenant::create();
- $tenant2 = Tenant::create();
+test('query parameter identification works', function () {
+ InitializeTenancyByRequestData::$header = null;
+ InitializeTenancyByRequestData::$queryParameter = 'tenant';
- $this
- ->withoutExceptionHandling()
- ->get('test', [
- 'X-Tenant' => $tenant->id,
- ])
- ->assertSee($tenant->id);
- }
+ $tenant = Tenant::create();
+ $tenant2 = Tenant::create();
- /** @test */
- public function query_parameter_identification_works()
- {
- InitializeTenancyByRequestData::$header = null;
- InitializeTenancyByRequestData::$queryParameter = 'tenant';
-
- $tenant = Tenant::create();
- $tenant2 = Tenant::create();
-
- $this
- ->withoutExceptionHandling()
- ->get('test?tenant=' . $tenant->id)
- ->assertSee($tenant->id);
- }
-}
+ $this
+ ->withoutExceptionHandling()
+ ->get('test?tenant=' . $tenant->id)
+ ->assertSee($tenant->id);
+});
diff --git a/tests/ResourceSyncingTest.php b/tests/ResourceSyncingTest.php
index 0ff95a52..99b41f23 100644
--- a/tests/ResourceSyncingTest.php
+++ b/tests/ResourceSyncingTest.php
@@ -2,8 +2,6 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Events\CallQueuedListener;
@@ -30,549 +28,521 @@ use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Listeners\UpdateSyncedResource;
use Stancl\Tenancy\Tests\Etc\Tenant;
-class ResourceSyncingTest extends TestCase
-{
- public function setUp(): void
- {
- parent::setUp();
+beforeEach(function () {
+ config(['tenancy.bootstrappers' => [
+ DatabaseTenancyBootstrapper::class,
+ ]]);
- config(['tenancy.bootstrappers' => [
- DatabaseTenancyBootstrapper::class,
- ]]);
+ Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener());
- Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener());
+ DatabaseConfig::generateDatabaseNamesUsing(function () {
+ return 'db' . Str::random(16);
+ });
- DatabaseConfig::generateDatabaseNamesUsing(function () {
- return 'db' . Str::random(16);
- });
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+ Event::listen(TenancyEnded::class, RevertToCentralContext::class);
- Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
- Event::listen(TenancyEnded::class, RevertToCentralContext::class);
+ UpdateSyncedResource::$shouldQueue = false; // global state cleanup
+ Event::listen(SyncedResourceSaved::class, UpdateSyncedResource::class);
- UpdateSyncedResource::$shouldQueue = false; // global state cleanup
- Event::listen(SyncedResourceSaved::class, UpdateSyncedResource::class);
+ test()->artisan('migrate', [
+ '--path' => [
+ __DIR__ . '/Etc/synced_resource_migrations',
+ __DIR__ . '/Etc/synced_resource_migrations/users',
+ ],
+ '--realpath' => true,
+ ])->assertExitCode(0);
+});
- $this->artisan('migrate', [
- '--path' => [
- __DIR__ . '/Etc/synced_resource_migrations',
- __DIR__ . '/Etc/synced_resource_migrations/users',
- ],
- '--realpath' => true,
- ])->assertExitCode(0);
- }
+test('an event is triggered when a synced resource is changed', function () {
+ Event::fake([SyncedResourceSaved::class]);
- protected function migrateTenants()
- {
- $this->artisan('tenants:migrate', [
- '--path' => __DIR__ . '/Etc/synced_resource_migrations/users',
- '--realpath' => true,
- ])->assertExitCode(0);
- }
+ $user = ResourceUser::create([
+ 'name' => 'Foo',
+ 'email' => 'foo@email.com',
+ 'password' => 'secret',
+ 'global_id' => 'foo',
+ 'role' => 'foo',
+ ]);
- /** @test */
- public function an_event_is_triggered_when_a_synced_resource_is_changed()
- {
- Event::fake([SyncedResourceSaved::class]);
+ Event::assertDispatched(SyncedResourceSaved::class, function (SyncedResourceSaved $event) use ($user) {
+ return $event->model === $user;
+ });
+});
- $user = ResourceUser::create([
- 'name' => 'Foo',
- 'email' => 'foo@email.com',
- 'password' => 'secret',
- 'global_id' => 'foo',
- 'role' => 'foo',
- ]);
+test('only the synced columns are updated in the central db', function () {
+ // Create user in central DB
+ $user = CentralUser::create([
+ 'global_id' => 'acme',
+ 'name' => 'John Doe',
+ 'email' => 'john@localhost',
+ 'password' => 'secret',
+ 'role' => 'superadmin', // unsynced
+ ]);
- Event::assertDispatched(SyncedResourceSaved::class, function (SyncedResourceSaved $event) use ($user) {
- return $event->model === $user;
- });
- }
+ $tenant = ResourceTenant::create();
+ migrateTenantsResource();
- /** @test */
- public function only_the_synced_columns_are_updated_in_the_central_db()
- {
- // Create user in central DB
- $user = CentralUser::create([
- 'global_id' => 'acme',
- 'name' => 'John Doe',
- 'email' => 'john@localhost',
- 'password' => 'secret',
- 'role' => 'superadmin', // unsynced
- ]);
+ tenancy()->initialize($tenant);
- $tenant = ResourceTenant::create();
- $this->migrateTenants();
+ // Create the same user in tenant DB
+ $user = ResourceUser::create([
+ 'global_id' => 'acme',
+ 'name' => 'John Doe',
+ 'email' => 'john@localhost',
+ 'password' => 'secret',
+ 'role' => 'commenter', // unsynced
+ ]);
- tenancy()->initialize($tenant);
+ // Update user in tenant DB
+ $user->update([
+ 'name' => 'John Foo', // synced
+ 'email' => 'john@foreignhost', // synced
+ 'role' => 'admin', // unsynced
+ ]);
- // Create the same user in tenant DB
- $user = ResourceUser::create([
- 'global_id' => 'acme',
- 'name' => 'John Doe',
- 'email' => 'john@localhost',
- 'password' => 'secret',
- 'role' => 'commenter', // unsynced
- ]);
+ // Assert new values
+ $this->assertEquals([
+ 'id' => 1,
+ 'global_id' => 'acme',
+ 'name' => 'John Foo',
+ 'email' => 'john@foreignhost',
+ 'password' => 'secret',
+ 'role' => 'admin',
+ ], $user->getAttributes());
- // Update user in tenant DB
- $user->update([
- 'name' => 'John Foo', // synced
- 'email' => 'john@foreignhost', // synced
- 'role' => 'admin', // unsynced
- ]);
+ tenancy()->end();
- // Assert new values
- $this->assertEquals([
- 'id' => 1,
- 'global_id' => 'acme',
- 'name' => 'John Foo',
- 'email' => 'john@foreignhost',
- 'password' => 'secret',
- 'role' => 'admin',
- ], $user->getAttributes());
+ // Assert changes bubbled up
+ $this->assertEquals([
+ 'id' => 1,
+ 'global_id' => 'acme',
+ 'name' => 'John Foo', // synced
+ 'email' => 'john@foreignhost', // synced
+ 'password' => 'secret', // no changes
+ 'role' => 'superadmin', // unsynced
+ ], ResourceUser::first()->getAttributes());
+});
- tenancy()->end();
+test('creating the resource in tenant database creates it in central database and creates the mapping', function () {
+ creatingResourceInTenantDatabaseCreatesAndMapInCentralDatabase();
+});
- // Assert changes bubbled up
- $this->assertEquals([
- 'id' => 1,
- 'global_id' => 'acme',
- 'name' => 'John Foo', // synced
- 'email' => 'john@foreignhost', // synced
- 'password' => 'secret', // no changes
- 'role' => 'superadmin', // unsynced
- ], ResourceUser::first()->getAttributes());
- }
+test('trying to update synced resources from central context using tenant models results in an exception', function () {
+ creatingResourceInTenantDatabaseCreatesAndMapInCentralDatabase();
- /** @test */
- public function creating_the_resource_in_tenant_database_creates_it_in_central_database_and_creates_the_mapping()
- {
- // Assert no user in central DB
- $this->assertCount(0, ResourceUser::all());
+ tenancy()->end();
+ expect(tenancy()->initialized)->toBeFalse();
- $tenant = ResourceTenant::create();
- $this->migrateTenants();
+ $this->expectException(ModelNotSyncMasterException::class);
+ ResourceUser::first()->update(['role' => 'foobar']);
+});
- tenancy()->initialize($tenant);
+test('attaching a tenant to the central resource triggers a pull from the tenant db', function () {
+ $centralUser = CentralUser::create([
+ 'global_id' => 'acme',
+ 'name' => 'John Doe',
+ 'email' => 'john@localhost',
+ 'password' => 'secret',
+ 'role' => 'commenter', // unsynced
+ ]);
- // Create the same user in tenant DB
+ $tenant = ResourceTenant::create([
+ 'id' => 't1',
+ ]);
+ migrateTenantsResource();
+
+ $tenant->run(function () {
+ expect(ResourceUser::all())->toHaveCount(0);
+ });
+
+ $centralUser->tenants()->attach('t1');
+
+ $tenant->run(function () {
+ expect(ResourceUser::all())->toHaveCount(1);
+ });
+});
+
+test('attaching users to tenants does not do anything', function () {
+ $centralUser = CentralUser::create([
+ 'global_id' => 'acme',
+ 'name' => 'John Doe',
+ 'email' => 'john@localhost',
+ 'password' => 'secret',
+ 'role' => 'commenter', // unsynced
+ ]);
+
+ $tenant = ResourceTenant::create([
+ 'id' => 't1',
+ ]);
+ migrateTenantsResource();
+
+ $tenant->run(function () {
+ expect(ResourceUser::all())->toHaveCount(0);
+ });
+
+ // The child model is inaccessible in the Pivot Model, so we can't fire any events.
+ $tenant->users()->attach($centralUser);
+
+ $tenant->run(function () {
+ // Still zero
+ expect(ResourceUser::all())->toHaveCount(0);
+ });
+});
+
+test('resources are synced only to workspaces that have the resource', function () {
+ $centralUser = CentralUser::create([
+ 'global_id' => 'acme',
+ 'name' => 'John Doe',
+ 'email' => 'john@localhost',
+ 'password' => 'secret',
+ 'role' => 'commenter', // unsynced
+ ]);
+
+ $t1 = ResourceTenant::create([
+ 'id' => 't1',
+ ]);
+
+ $t2 = ResourceTenant::create([
+ 'id' => 't2',
+ ]);
+
+ $t3 = ResourceTenant::create([
+ 'id' => 't3',
+ ]);
+ migrateTenantsResource();
+
+ $centralUser->tenants()->attach('t1');
+ $centralUser->tenants()->attach('t2');
+ // t3 is not attached
+
+ $t1->run(function () {
+ // assert user exists
+ expect(ResourceUser::all())->toHaveCount(1);
+ });
+
+ $t2->run(function () {
+ // assert user exists
+ expect(ResourceUser::all())->toHaveCount(1);
+ });
+
+ $t3->run(function () {
+ // assert user does NOT exist
+ expect(ResourceUser::all())->toHaveCount(0);
+ });
+});
+
+test('when a resource exists in other tenant dbs but is created in a tenant db the synced columns are updated in the other dbs', function () {
+ // create shared resource
+ $centralUser = CentralUser::create([
+ 'global_id' => 'acme',
+ 'name' => 'John Doe',
+ 'email' => 'john@localhost',
+ 'password' => 'secret',
+ 'role' => 'commenter', // unsynced
+ ]);
+
+ $t1 = ResourceTenant::create([
+ 'id' => 't1',
+ ]);
+ $t2 = ResourceTenant::create([
+ 'id' => 't2',
+ ]);
+ migrateTenantsResource();
+
+ // Copy (cascade) user to t1 DB
+ $centralUser->tenants()->attach('t1');
+
+ $t2->run(function () {
+ // Create user with the same global ID in t2 database
ResourceUser::create([
'global_id' => 'acme',
- 'name' => 'John Doe',
- 'email' => 'john@localhost',
+ 'name' => 'John Foo', // changed
+ 'email' => 'john@foo', // changed
'password' => 'secret',
- 'role' => 'commenter', // unsynced
+ 'role' => 'superadmin', // unsynced
+ ]);
+ });
+
+ $centralUser = CentralUser::first();
+ expect($centralUser->name)->toBe('John Foo'); // name changed
+ expect($centralUser->email)->toBe('john@foo'); // email changed
+ expect($centralUser->role)->toBe('commenter'); // role didn't change
+
+ $t1->run(function () {
+ $user = ResourceUser::first();
+ expect($user->name)->toBe('John Foo'); // name changed
+ expect($user->email)->toBe('john@foo'); // email changed
+ expect($user->role)->toBe('commenter'); // role didn't change, i.e. is the same as from the original copy from central
+ });
+});
+
+test('the synced columns are updated in other tenant dbs where the resource exists', function () {
+ // create shared resource
+ $centralUser = CentralUser::create([
+ 'global_id' => 'acme',
+ 'name' => 'John Doe',
+ 'email' => 'john@localhost',
+ 'password' => 'secret',
+ 'role' => 'commenter', // unsynced
+ ]);
+
+ $t1 = ResourceTenant::create([
+ 'id' => 't1',
+ ]);
+ $t2 = ResourceTenant::create([
+ 'id' => 't2',
+ ]);
+ $t3 = ResourceTenant::create([
+ 'id' => 't3',
+ ]);
+ migrateTenantsResource();
+
+ // Copy (cascade) user to t1 DB
+ $centralUser->tenants()->attach('t1');
+ $centralUser->tenants()->attach('t2');
+ $centralUser->tenants()->attach('t3');
+
+ $t3->run(function () {
+ ResourceUser::first()->update([
+ 'name' => 'John 3',
+ 'role' => 'employee', // unsynced
]);
- tenancy()->end();
+ expect(ResourceUser::first()->role)->toBe('employee');
+ });
- // Asset user was created
- $this->assertSame('acme', CentralUser::first()->global_id);
- $this->assertSame('commenter', CentralUser::first()->role);
+ // Check that change was cascaded to other tenants
+ $t1->run($check = function () {
+ $user = ResourceUser::first();
- // Assert mapping was created
- $this->assertCount(1, CentralUser::first()->tenants);
+ expect($user->name)->toBe('John 3'); // synced
+ expect($user->role)->toBe('commenter'); // unsynced
+ });
+ $t2->run($check);
- // Assert role change doesn't cascade
- CentralUser::first()->update(['role' => 'central superadmin']);
- tenancy()->initialize($tenant);
- $this->assertSame('commenter', ResourceUser::first()->role);
- }
+ // Check that change bubbled up to central DB
+ expect(CentralUser::count())->toBe(1);
+ $centralUser = CentralUser::first();
+ expect($centralUser->name)->toBe('John 3'); // synced
+ expect($centralUser->role)->toBe('commenter'); // unsynced
+});
- /** @test */
- public function trying_to_update_synced_resources_from_central_context_using_tenant_models_results_in_an_exception()
- {
- $this->creating_the_resource_in_tenant_database_creates_it_in_central_database_and_creates_the_mapping();
+test('global id is generated using id generator when its not supplied', function () {
+ $user = CentralUser::create([
+ 'name' => 'John Doe',
+ 'email' => 'john@doe',
+ 'password' => 'secret',
+ 'role' => 'employee',
+ ]);
- tenancy()->end();
- $this->assertFalse(tenancy()->initialized);
+ $this->assertNotNull($user->global_id);
+});
- $this->expectException(ModelNotSyncMasterException::class);
- ResourceUser::first()->update(['role' => 'foobar']);
- }
+test('when the resource doesnt exist in the tenant db non synced columns will cascade too', function () {
+ $centralUser = CentralUser::create([
+ 'name' => 'John Doe',
+ 'email' => 'john@doe',
+ 'password' => 'secret',
+ 'role' => 'employee',
+ ]);
- /** @test */
- public function attaching_a_tenant_to_the_central_resource_triggers_a_pull_from_the_tenant_db()
- {
- $centralUser = CentralUser::create([
- 'global_id' => 'acme',
- 'name' => 'John Doe',
- 'email' => 'john@localhost',
- 'password' => 'secret',
- 'role' => 'commenter', // unsynced
- ]);
+ $t1 = ResourceTenant::create([
+ 'id' => 't1',
+ ]);
- $tenant = ResourceTenant::create([
- 'id' => 't1',
- ]);
- $this->migrateTenants();
+ migrateTenantsResource();
- $tenant->run(function () {
- $this->assertCount(0, ResourceUser::all());
- });
+ $centralUser->tenants()->attach('t1');
- $centralUser->tenants()->attach('t1');
+ $t1->run(function () {
+ expect(ResourceUser::first()->role)->toBe('employee');
+ });
+});
- $tenant->run(function () {
- $this->assertCount(1, ResourceUser::all());
- });
- }
+test('when the resource doesnt exist in the central db non synced columns will bubble up too', function () {
+ $t1 = ResourceTenant::create([
+ 'id' => 't1',
+ ]);
- /** @test */
- public function attaching_users_to_tenants_DOES_NOT_DO_ANYTHING()
- {
- $centralUser = CentralUser::create([
- 'global_id' => 'acme',
- 'name' => 'John Doe',
- 'email' => 'john@localhost',
- 'password' => 'secret',
- 'role' => 'commenter', // unsynced
- ]);
+ migrateTenantsResource();
- $tenant = ResourceTenant::create([
- 'id' => 't1',
- ]);
- $this->migrateTenants();
-
- $tenant->run(function () {
- $this->assertCount(0, ResourceUser::all());
- });
-
- // The child model is inaccessible in the Pivot Model, so we can't fire any events.
- $tenant->users()->attach($centralUser);
-
- $tenant->run(function () {
- // Still zero
- $this->assertCount(0, ResourceUser::all());
- });
- }
-
- /** @test */
- public function resources_are_synced_only_to_workspaces_that_have_the_resource()
- {
- $centralUser = CentralUser::create([
- 'global_id' => 'acme',
- 'name' => 'John Doe',
- 'email' => 'john@localhost',
- 'password' => 'secret',
- 'role' => 'commenter', // unsynced
- ]);
-
- $t1 = ResourceTenant::create([
- 'id' => 't1',
- ]);
-
- $t2 = ResourceTenant::create([
- 'id' => 't2',
- ]);
-
- $t3 = ResourceTenant::create([
- 'id' => 't3',
- ]);
- $this->migrateTenants();
-
- $centralUser->tenants()->attach('t1');
- $centralUser->tenants()->attach('t2');
- // t3 is not attached
-
- $t1->run(function () {
- // assert user exists
- $this->assertCount(1, ResourceUser::all());
- });
-
- $t2->run(function () {
- // assert user exists
- $this->assertCount(1, ResourceUser::all());
- });
-
- $t3->run(function () {
- // assert user does NOT exist
- $this->assertCount(0, ResourceUser::all());
- });
- }
-
- /** @test */
- public function when_a_resource_exists_in_other_tenant_dbs_but_is_CREATED_in_a_tenant_db_the_synced_columns_are_updated_in_the_other_dbs()
- {
- // create shared resource
- $centralUser = CentralUser::create([
- 'global_id' => 'acme',
- 'name' => 'John Doe',
- 'email' => 'john@localhost',
- 'password' => 'secret',
- 'role' => 'commenter', // unsynced
- ]);
-
- $t1 = ResourceTenant::create([
- 'id' => 't1',
- ]);
- $t2 = ResourceTenant::create([
- 'id' => 't2',
- ]);
- $this->migrateTenants();
-
- // Copy (cascade) user to t1 DB
- $centralUser->tenants()->attach('t1');
-
- $t2->run(function () {
- // Create user with the same global ID in t2 database
- ResourceUser::create([
- 'global_id' => 'acme',
- 'name' => 'John Foo', // changed
- 'email' => 'john@foo', // changed
- 'password' => 'secret',
- 'role' => 'superadmin', // unsynced
- ]);
- });
-
- $centralUser = CentralUser::first();
- $this->assertSame('John Foo', $centralUser->name); // name changed
- $this->assertSame('john@foo', $centralUser->email); // email changed
- $this->assertSame('commenter', $centralUser->role); // role didn't change
-
- $t1->run(function () {
- $user = ResourceUser::first();
- $this->assertSame('John Foo', $user->name); // name changed
- $this->assertSame('john@foo', $user->email); // email changed
- $this->assertSame('commenter', $user->role); // role didn't change, i.e. is the same as from the original copy from central
- });
- }
-
- /** @test */
- public function the_synced_columns_are_updated_in_other_tenant_dbs_where_the_resource_exists()
- {
- // create shared resource
- $centralUser = CentralUser::create([
- 'global_id' => 'acme',
- 'name' => 'John Doe',
- 'email' => 'john@localhost',
- 'password' => 'secret',
- 'role' => 'commenter', // unsynced
- ]);
-
- $t1 = ResourceTenant::create([
- 'id' => 't1',
- ]);
- $t2 = ResourceTenant::create([
- 'id' => 't2',
- ]);
- $t3 = ResourceTenant::create([
- 'id' => 't3',
- ]);
- $this->migrateTenants();
-
- // Copy (cascade) user to t1 DB
- $centralUser->tenants()->attach('t1');
- $centralUser->tenants()->attach('t2');
- $centralUser->tenants()->attach('t3');
-
- $t3->run(function () {
- ResourceUser::first()->update([
- 'name' => 'John 3',
- 'role' => 'employee', // unsynced
- ]);
-
- $this->assertSame('employee', ResourceUser::first()->role);
- });
-
- // Check that change was cascaded to other tenants
- $t1->run($check = function () {
- $user = ResourceUser::first();
-
- $this->assertSame('John 3', $user->name); // synced
- $this->assertSame('commenter', $user->role); // unsynced
- });
- $t2->run($check);
-
- // Check that change bubbled up to central DB
- $this->assertSame(1, CentralUser::count());
- $centralUser = CentralUser::first();
- $this->assertSame('John 3', $centralUser->name); // synced
- $this->assertSame('commenter', $centralUser->role); // unsynced
- }
-
- /** @test */
- public function global_id_is_generated_using_id_generator_when_its_not_supplied()
- {
- $user = CentralUser::create([
+ $t1->run(function () {
+ ResourceUser::create([
'name' => 'John Doe',
'email' => 'john@doe',
'password' => 'secret',
'role' => 'employee',
]);
+ });
- $this->assertNotNull($user->global_id);
- }
+ expect(CentralUser::first()->role)->toBe('employee');
+});
- /** @test */
- public function when_the_resource_doesnt_exist_in_the_tenant_db_non_synced_columns_will_cascade_too()
- {
- $centralUser = CentralUser::create([
+test('the listener can be queued', function () {
+ Queue::fake();
+ UpdateSyncedResource::$shouldQueue = true;
+
+ $t1 = ResourceTenant::create([
+ 'id' => 't1',
+ ]);
+
+ migrateTenantsResource();
+
+ Queue::assertNothingPushed();
+
+ $t1->run(function () {
+ ResourceUser::create([
'name' => 'John Doe',
'email' => 'john@doe',
'password' => 'secret',
'role' => 'employee',
]);
+ });
- $t1 = ResourceTenant::create([
- 'id' => 't1',
+ Queue::assertPushed(CallQueuedListener::class, function (CallQueuedListener $job) {
+ return $job->class === UpdateSyncedResource::class;
+ });
+});
+
+test('an event is fired for all touched resources', function () {
+ Event::fake([SyncedResourceChangedInForeignDatabase::class]);
+
+ // create shared resource
+ $centralUser = CentralUser::create([
+ 'global_id' => 'acme',
+ 'name' => 'John Doe',
+ 'email' => 'john@localhost',
+ 'password' => 'secret',
+ 'role' => 'commenter', // unsynced
+ ]);
+
+ $t1 = ResourceTenant::create([
+ 'id' => 't1',
+ ]);
+ $t2 = ResourceTenant::create([
+ 'id' => 't2',
+ ]);
+ $t3 = ResourceTenant::create([
+ 'id' => 't3',
+ ]);
+ migrateTenantsResource();
+
+ // Copy (cascade) user to t1 DB
+ $centralUser->tenants()->attach('t1');
+ Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
+ return $event->tenant->getTenantKey() === 't1';
+ });
+
+ $centralUser->tenants()->attach('t2');
+ Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
+ return $event->tenant->getTenantKey() === 't2';
+ });
+
+ $centralUser->tenants()->attach('t3');
+ Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
+ return $event->tenant->getTenantKey() === 't3';
+ });
+
+ // Assert no event for central
+ Event::assertNotDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
+ return $event->tenant === null;
+ });
+
+ // Flush
+ Event::fake([SyncedResourceChangedInForeignDatabase::class]);
+
+ $t3->run(function () {
+ ResourceUser::first()->update([
+ 'name' => 'John 3',
+ 'role' => 'employee', // unsynced
]);
- $this->migrateTenants();
+ expect(ResourceUser::first()->role)->toBe('employee');
+ });
- $centralUser->tenants()->attach('t1');
+ Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
+ return optional($event->tenant)->getTenantKey() === 't1';
+ });
+ Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
+ return optional($event->tenant)->getTenantKey() === 't2';
+ });
- $t1->run(function () {
- $this->assertSame('employee', ResourceUser::first()->role);
- });
- }
+ // Assert NOT dispatched in t3
+ Event::assertNotDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
+ return optional($event->tenant)->getTenantKey() === 't3';
+ });
- /** @test */
- public function when_the_resource_doesnt_exist_in_the_central_db_non_synced_columns_will_bubble_up_too()
- {
- $t1 = ResourceTenant::create([
- 'id' => 't1',
- ]);
+ // Assert dispatched in central
+ Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
+ return $event->tenant === null;
+ });
- $this->migrateTenants();
+ // Flush
+ Event::fake([SyncedResourceChangedInForeignDatabase::class]);
- $t1->run(function () {
- ResourceUser::create([
- 'name' => 'John Doe',
- 'email' => 'john@doe',
- 'password' => 'secret',
- 'role' => 'employee',
- ]);
- });
+ $centralUser->update([
+ 'name' => 'John Central',
+ ]);
- $this->assertSame('employee', CentralUser::first()->role);
- }
+ Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
+ return optional($event->tenant)->getTenantKey() === 't1';
+ });
+ Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
+ return optional($event->tenant)->getTenantKey() === 't2';
+ });
+ Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
+ return optional($event->tenant)->getTenantKey() === 't3';
+ });
+ // Assert NOT dispatched in central
+ Event::assertNotDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
+ return $event->tenant === null;
+ });
+});
- /** @test */
- public function the_listener_can_be_queued()
- {
- Queue::fake();
- UpdateSyncedResource::$shouldQueue = true;
+// todo@tests
+function creatingResourceInTenantDatabaseCreatesAndMapInCentralDatabase()
+{
+ // Assert no user in central DB
+ expect(ResourceUser::all())->toHaveCount(0);
- $t1 = ResourceTenant::create([
- 'id' => 't1',
- ]);
+ $tenant = ResourceTenant::create();
+ migrateTenantsResource();
- $this->migrateTenants();
+ tenancy()->initialize($tenant);
- Queue::assertNothingPushed();
+ // Create the same user in tenant DB
+ ResourceUser::create([
+ 'global_id' => 'acme',
+ 'name' => 'John Doe',
+ 'email' => 'john@localhost',
+ 'password' => 'secret',
+ 'role' => 'commenter', // unsynced
+ ]);
- $t1->run(function () {
- ResourceUser::create([
- 'name' => 'John Doe',
- 'email' => 'john@doe',
- 'password' => 'secret',
- 'role' => 'employee',
- ]);
- });
+ tenancy()->end();
- Queue::assertPushed(CallQueuedListener::class, function (CallQueuedListener $job) {
- return $job->class === UpdateSyncedResource::class;
- });
- }
+ // Asset user was created
+ expect(CentralUser::first()->global_id)->toBe('acme');
+ expect(CentralUser::first()->role)->toBe('commenter');
- /** @test */
- public function an_event_is_fired_for_all_touched_resources()
- {
- Event::fake([SyncedResourceChangedInForeignDatabase::class]);
+ // Assert mapping was created
+ expect(CentralUser::first()->tenants)->toHaveCount(1);
- // create shared resource
- $centralUser = CentralUser::create([
- 'global_id' => 'acme',
- 'name' => 'John Doe',
- 'email' => 'john@localhost',
- 'password' => 'secret',
- 'role' => 'commenter', // unsynced
- ]);
+ // Assert role change doesn't cascade
+ CentralUser::first()->update(['role' => 'central superadmin']);
+ tenancy()->initialize($tenant);
+ expect(ResourceUser::first()->role)->toBe('commenter');
+}
- $t1 = ResourceTenant::create([
- 'id' => 't1',
- ]);
- $t2 = ResourceTenant::create([
- 'id' => 't2',
- ]);
- $t3 = ResourceTenant::create([
- 'id' => 't3',
- ]);
- $this->migrateTenants();
-
- // Copy (cascade) user to t1 DB
- $centralUser->tenants()->attach('t1');
- Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
- return $event->tenant->getTenantKey() === 't1';
- });
-
- $centralUser->tenants()->attach('t2');
- Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
- return $event->tenant->getTenantKey() === 't2';
- });
-
- $centralUser->tenants()->attach('t3');
- Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
- return $event->tenant->getTenantKey() === 't3';
- });
-
- // Assert no event for central
- Event::assertNotDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
- return $event->tenant === null;
- });
-
- // Flush
- Event::fake([SyncedResourceChangedInForeignDatabase::class]);
-
- $t3->run(function () {
- ResourceUser::first()->update([
- 'name' => 'John 3',
- 'role' => 'employee', // unsynced
- ]);
-
- $this->assertSame('employee', ResourceUser::first()->role);
- });
-
- Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
- return optional($event->tenant)->getTenantKey() === 't1';
- });
- Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
- return optional($event->tenant)->getTenantKey() === 't2';
- });
-
- // Assert NOT dispatched in t3
- Event::assertNotDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
- return optional($event->tenant)->getTenantKey() === 't3';
- });
-
- // Assert dispatched in central
- Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
- return $event->tenant === null;
- });
-
- // Flush
- Event::fake([SyncedResourceChangedInForeignDatabase::class]);
-
- $centralUser->update([
- 'name' => 'John Central',
- ]);
-
- Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
- return optional($event->tenant)->getTenantKey() === 't1';
- });
- Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
- return optional($event->tenant)->getTenantKey() === 't2';
- });
- Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
- return optional($event->tenant)->getTenantKey() === 't3';
- });
- // Assert NOT dispatched in central
- Event::assertNotDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
- return $event->tenant === null;
- });
- }
+function migrateTenantsResource()
+{
+ test()->artisan('tenants:migrate', [
+ '--path' => __DIR__ . '/Etc/synced_resource_migrations/users',
+ '--realpath' => true,
+ ])->assertExitCode(0);
}
class ResourceTenant extends Tenant
diff --git a/tests/ScopeSessionsTest.php b/tests/ScopeSessionsTest.php
index b5fb962a..b1b6a05e 100644
--- a/tests/ScopeSessionsTest.php
+++ b/tests/ScopeSessionsTest.php
@@ -2,8 +2,6 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Session\Middleware\StartSession;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Route;
@@ -13,69 +11,57 @@ use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
use Stancl\Tenancy\Middleware\ScopeSessions;
use Stancl\Tenancy\Tests\Etc\Tenant;
-class ScopeSessionsTest extends TestCase
-{
- public function setUp(): void
- {
- parent::setUp();
-
- Route::group([
- 'middleware' => [StartSession::class, InitializeTenancyBySubdomain::class, ScopeSessions::class],
- ], function () {
- Route::get('/foo', function () {
- return 'true';
- });
+beforeEach(function () {
+ Route::group([
+ 'middleware' => [StartSession::class, InitializeTenancyBySubdomain::class, ScopeSessions::class],
+ ], function () {
+ Route::get('/foo', function () {
+ return 'true';
});
+ });
- Event::listen(TenantCreated::class, function (TenantCreated $event) {
- $tenant = $event->tenant;
+ Event::listen(TenantCreated::class, function (TenantCreated $event) {
+ $tenant = $event->tenant;
- /** @var Tenant $tenant */
- $tenant->domains()->create([
- 'domain' => $tenant->id,
- ]);
- });
- }
-
- /** @test */
- public function tenant_id_is_auto_added_to_session_if_its_missing()
- {
- $tenant = Tenant::create([
- 'id' => 'acme',
+ /** @var Tenant $tenant */
+ $tenant->domains()->create([
+ 'domain' => $tenant->id,
]);
+ });
+});
- $this->get('http://acme.localhost/foo')
- ->assertSessionHas(ScopeSessions::$tenantIdKey, 'acme');
- }
+test('tenant id is auto added to session if its missing', function () {
+ $tenant = Tenant::create([
+ 'id' => 'acme',
+ ]);
- /** @test */
- public function changing_tenant_id_in_session_will_abort_the_request()
- {
- $tenant = Tenant::create([
- 'id' => 'acme',
- ]);
+ $this->get('http://acme.localhost/foo')
+ ->assertSessionHas(ScopeSessions::$tenantIdKey, 'acme');
+});
- $this->get('http://acme.localhost/foo')
- ->assertSuccessful();
+test('changing tenant id in session will abort the request', function () {
+ $tenant = Tenant::create([
+ 'id' => 'acme',
+ ]);
- session()->put(ScopeSessions::$tenantIdKey, 'foobar');
+ $this->get('http://acme.localhost/foo')
+ ->assertSuccessful();
- $this->get('http://acme.localhost/foo')
- ->assertStatus(403);
- }
+ session()->put(ScopeSessions::$tenantIdKey, 'foobar');
- /** @test */
- public function an_exception_is_thrown_when_the_middleware_is_executed_before_tenancy_is_initialized()
- {
- Route::get('/bar', function () {
- return true;
- })->middleware([StartSession::class, ScopeSessions::class]);
+ $this->get('http://acme.localhost/foo')
+ ->assertStatus(403);
+});
- $tenant = Tenant::create([
- 'id' => 'acme',
- ]);
+test('an exception is thrown when the middleware is executed before tenancy is initialized', function () {
+ Route::get('/bar', function () {
+ return true;
+ })->middleware([StartSession::class, ScopeSessions::class]);
- $this->expectException(TenancyNotInitializedException::class);
- $this->withoutExceptionHandling()->get('http://acme.localhost/bar');
- }
-}
+ $tenant = Tenant::create([
+ 'id' => 'acme',
+ ]);
+
+ $this->expectException(TenancyNotInitializedException::class);
+ $this->withoutExceptionHandling()->get('http://acme.localhost/bar');
+});
diff --git a/tests/SingleDatabaseTenancyTest.php b/tests/SingleDatabaseTenancyTest.php
index d0425dd9..83807d14 100644
--- a/tests/SingleDatabaseTenancyTest.php
+++ b/tests/SingleDatabaseTenancyTest.php
@@ -2,8 +2,6 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\QueryException;
use Illuminate\Database\Schema\Blueprint;
@@ -14,309 +12,293 @@ use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
use Stancl\Tenancy\Database\Concerns\HasScopedValidationRules;
use Stancl\Tenancy\Tests\Etc\Tenant as TestTenant;
-class SingleDatabaseTenancyTest extends TestCase
+beforeEach(function () {
+ BelongsToTenant::$tenantIdColumn = 'tenant_id';
+
+ Schema::create('posts', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('text');
+
+ $table->string('tenant_id');
+
+ $table->foreign('tenant_id')->references('id')->on('tenants')->onUpdate('cascade')->onDelete('cascade');
+ });
+
+ Schema::create('comments', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('text');
+
+ $table->unsignedInteger('post_id');
+
+ $table->foreign('post_id')->references('id')->on('posts')->onUpdate('cascade')->onDelete('cascade');
+ });
+
+ config(['tenancy.tenant_model' => Tenant::class]);
+});
+
+test('primary models are scoped to the current tenant', function () {
+ primaryModelsScopedToCurrentTenant();
+});
+
+test('primary models are not scoped in the central context', function () {
+ primaryModelsScopedToCurrentTenant();
+
+ tenancy()->end();
+
+ expect(Post::count())->toBe(2);
+});
+
+test('secondary models are scoped to the current tenant when accessed via primary model', function () {
+ secondaryModelsAreScopedToCurrentTenant();
+});
+
+test('secondary models are not scoped to the current tenant when accessed directly', function () {
+ secondaryModelsAreScopedToCurrentTenant();
+
+ // We're in acme context
+ expect(tenant('id'))->toBe('acme');
+
+ expect(Comment::count())->toBe(2);
+});
+
+test('secondary models a r e scoped to the current tenant when accessed directly and parent relationship traitis used', function () {
+ $acme = Tenant::create([
+ 'id' => 'acme',
+ ]);
+
+ $acme->run(function () {
+ $post = Post::create(['text' => 'Foo']);
+ $post->scoped_comments()->create(['text' => 'Comment Text']);
+
+ expect(Post::count())->toBe(1);
+ expect(ScopedComment::count())->toBe(1);
+ });
+
+ $foobar = Tenant::create([
+ 'id' => 'foobar',
+ ]);
+
+ $foobar->run(function () {
+ expect(Post::count())->toBe(0);
+ expect(ScopedComment::count())->toBe(0);
+
+ $post = Post::create(['text' => 'Bar']);
+ $post->scoped_comments()->create(['text' => 'Comment Text 2']);
+
+ expect(Post::count())->toBe(1);
+ expect(ScopedComment::count())->toBe(1);
+ });
+
+ // Global context
+ expect(ScopedComment::count())->toBe(2);
+});
+
+test('secondary models are not scoped in the central context', function () {
+ secondaryModelsAreScopedToCurrentTenant();
+
+ tenancy()->end();
+
+ expect(Comment::count())->toBe(2);
+});
+
+test('global models are not scoped at all', function () {
+ Schema::create('global_resources', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('text');
+ });
+
+ GlobalResource::create(['text' => 'First']);
+ GlobalResource::create(['text' => 'Second']);
+
+ $acme = Tenant::create([
+ 'id' => 'acme',
+ ]);
+
+ $acme->run(function () {
+ expect(GlobalResource::count())->toBe(2);
+
+ GlobalResource::create(['text' => 'Third']);
+ GlobalResource::create(['text' => 'Fourth']);
+ });
+
+ expect(GlobalResource::count())->toBe(4);
+});
+
+test('tenant id and relationship is auto added when creating primary resources in tenant context', function () {
+ tenancy()->initialize($acme = Tenant::create([
+ 'id' => 'acme',
+ ]));
+
+ $post = Post::create(['text' => 'Foo']);
+
+ expect($post->tenant_id)->toBe('acme');
+ expect($post->relationLoaded('tenant'))->toBeTrue();
+ expect($post->tenant)->toBe($acme);
+ expect($post->tenant)->toBe(tenant());
+});
+
+test('tenant id is not auto added when creating primary resources in central context', function () {
+ $this->expectException(QueryException::class);
+
+ Post::create(['text' => 'Foo']);
+});
+
+test('tenant id column name can be customized', function () {
+ BelongsToTenant::$tenantIdColumn = 'team_id';
+
+ Schema::drop('comments');
+ Schema::drop('posts');
+ Schema::create('posts', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('text');
+
+ $table->string('team_id');
+
+ $table->foreign('team_id')->references('id')->on('tenants')->onUpdate('cascade')->onDelete('cascade');
+ });
+
+ tenancy()->initialize($acme = Tenant::create([
+ 'id' => 'acme',
+ ]));
+
+ $post = Post::create(['text' => 'Foo']);
+
+ expect($post->team_id)->toBe('acme');
+
+ // ======================================
+ // foobar context
+ tenancy()->initialize($foobar = Tenant::create([
+ 'id' => 'foobar',
+ ]));
+
+ $post = Post::create(['text' => 'Bar']);
+
+ expect($post->team_id)->toBe('foobar');
+
+ $post = Post::first();
+
+ expect($post->team_id)->toBe('foobar');
+
+ // ======================================
+ // acme context again
+
+ tenancy()->initialize($acme);
+
+ $post = Post::first();
+ expect($post->team_id)->toBe('acme');
+
+ // Assert foobar models are inaccessible in acme context
+ expect(Post::count())->toBe(1);
+});
+
+test('the model returned by the tenant helper has unique and exists validation rules', function () {
+ Schema::table('posts', function (Blueprint $table) {
+ $table->string('slug')->nullable();
+ $table->unique(['tenant_id', 'slug']);
+ });
+
+ tenancy()->initialize($acme = Tenant::create([
+ 'id' => 'acme',
+ ]));
+
+ Post::create(['text' => 'Foo', 'slug' => 'foo']);
+ $data = ['text' => 'Foo 2', 'slug' => 'foo'];
+
+ $uniqueFails = Validator::make($data, [
+ 'slug' => 'unique:posts',
+ ])->fails();
+ $existsFails = Validator::make($data, [
+ 'slug' => 'exists:posts',
+ ])->fails();
+
+ // Assert that 'unique' and 'exists' aren't scoped by default
+ // $this->assertFalse($uniqueFails); // todo get these two assertions to pass. for some reason, the validator is passing for both 'unique' and 'exists'
+ // $this->assertTrue($existsFails); // todo get these two assertions to pass. for some reason, the validator is passing for both 'unique' and 'exists'
+
+ $uniqueFails = Validator::make($data, [
+ 'slug' => tenant()->unique('posts'),
+ ])->fails();
+ $existsFails = Validator::make($data, [
+ 'slug' => tenant()->exists('posts'),
+ ])->fails();
+
+ // Assert that tenant()->unique() and tenant()->exists() are scoped
+ expect($uniqueFails)->toBeTrue();
+ expect($existsFails)->toBeFalse();
+});
+
+// todo@tests
+function primaryModelsScopedToCurrentTenant()
{
- public function setUp(): void
- {
- parent::setUp();
+ // acme context
+ tenancy()->initialize($acme = Tenant::create([
+ 'id' => 'acme',
+ ]));
- BelongsToTenant::$tenantIdColumn = 'tenant_id';
+ $post = Post::create(['text' => 'Foo']);
- Schema::create('posts', function (Blueprint $table) {
- $table->increments('id');
- $table->string('text');
+ expect($post->tenant_id)->toBe('acme');
+ expect($post->tenant->id)->toBe('acme');
- $table->string('tenant_id');
+ $post = Post::first();
- $table->foreign('tenant_id')->references('id')->on('tenants')->onUpdate('cascade')->onDelete('cascade');
- });
+ expect($post->tenant_id)->toBe('acme');
+ expect($post->tenant->id)->toBe('acme');
- Schema::create('comments', function (Blueprint $table) {
- $table->increments('id');
- $table->string('text');
+ // ======================================
+ // foobar context
+ tenancy()->initialize($foobar = Tenant::create([
+ 'id' => 'foobar',
+ ]));
- $table->unsignedInteger('post_id');
+ $post = Post::create(['text' => 'Bar']);
- $table->foreign('post_id')->references('id')->on('posts')->onUpdate('cascade')->onDelete('cascade');
- });
+ expect($post->tenant_id)->toBe('foobar');
+ expect($post->tenant->id)->toBe('foobar');
- config(['tenancy.tenant_model' => Tenant::class]);
- }
+ $post = Post::first();
- /** @test */
- public function primary_models_are_scoped_to_the_current_tenant()
- {
- // acme context
- tenancy()->initialize($acme = Tenant::create([
- 'id' => 'acme',
- ]));
+ expect($post->tenant_id)->toBe('foobar');
+ expect($post->tenant->id)->toBe('foobar');
- $post = Post::create(['text' => 'Foo']);
+ // ======================================
+ // acme context again
- $this->assertSame('acme', $post->tenant_id);
- $this->assertSame('acme', $post->tenant->id);
+ tenancy()->initialize($acme);
- $post = Post::first();
+ $post = Post::first();
+ expect($post->tenant_id)->toBe('acme');
+ expect($post->tenant->id)->toBe('acme');
- $this->assertSame('acme', $post->tenant_id);
- $this->assertSame('acme', $post->tenant->id);
+ // Assert foobar models are inaccessible in acme context
+ expect(Post::count())->toBe(1);
+}
- // ======================================
- // foobar context
- tenancy()->initialize($foobar = Tenant::create([
- 'id' => 'foobar',
- ]));
+// todo@tests
+function secondaryModelsAreScopedToCurrentTenant()
+{
+ // acme context
+ tenancy()->initialize($acme = Tenant::create([
+ 'id' => 'acme',
+ ]));
- $post = Post::create(['text' => 'Bar']);
+ $post = Post::create(['text' => 'Foo']);
+ $post->comments()->create(['text' => 'Comment text']);
- $this->assertSame('foobar', $post->tenant_id);
- $this->assertSame('foobar', $post->tenant->id);
+ // ================
+ // foobar context
+ tenancy()->initialize($foobar = Tenant::create([
+ 'id' => 'foobar',
+ ]));
- $post = Post::first();
+ $post = Post::create(['text' => 'Bar']);
+ $post->comments()->create(['text' => 'Comment text 2']);
- $this->assertSame('foobar', $post->tenant_id);
- $this->assertSame('foobar', $post->tenant->id);
-
- // ======================================
- // acme context again
-
- tenancy()->initialize($acme);
-
- $post = Post::first();
- $this->assertSame('acme', $post->tenant_id);
- $this->assertSame('acme', $post->tenant->id);
-
- // Assert foobar models are inaccessible in acme context
- $this->assertSame(1, Post::count());
- }
-
- /** @test */
- public function primary_models_are_not_scoped_in_the_central_context()
- {
- $this->primary_models_are_scoped_to_the_current_tenant();
-
- tenancy()->end();
-
- $this->assertSame(2, Post::count());
- }
-
- /** @test */
- public function secondary_models_are_scoped_to_the_current_tenant_when_accessed_via_primary_model()
- {
- // acme context
- tenancy()->initialize($acme = Tenant::create([
- 'id' => 'acme',
- ]));
-
- $post = Post::create(['text' => 'Foo']);
- $post->comments()->create(['text' => 'Comment text']);
-
- // ================
- // foobar context
- tenancy()->initialize($foobar = Tenant::create([
- 'id' => 'foobar',
- ]));
-
- $post = Post::create(['text' => 'Bar']);
- $post->comments()->create(['text' => 'Comment text 2']);
-
- // ================
- // acme context again
- tenancy()->initialize($acme);
- $this->assertSame(1, Post::count());
- $this->assertSame(1, Post::first()->comments->count());
- }
-
- /** @test */
- public function secondary_models_are_NOT_scoped_to_the_current_tenant_when_accessed_directly()
- {
- $this->secondary_models_are_scoped_to_the_current_tenant_when_accessed_via_primary_model();
-
- // We're in acme context
- $this->assertSame('acme', tenant('id'));
-
- $this->assertSame(2, Comment::count());
- }
-
- /** @test */
- public function secondary_models_ARE_scoped_to_the_current_tenant_when_accessed_directly_AND_PARENT_RELATIONSHIP_TRAIT_IS_USED()
- {
- $acme = Tenant::create([
- 'id' => 'acme',
- ]);
-
- $acme->run(function () {
- $post = Post::create(['text' => 'Foo']);
- $post->scoped_comments()->create(['text' => 'Comment Text']);
-
- $this->assertSame(1, Post::count());
- $this->assertSame(1, ScopedComment::count());
- });
-
- $foobar = Tenant::create([
- 'id' => 'foobar',
- ]);
-
- $foobar->run(function () {
- $this->assertSame(0, Post::count());
- $this->assertSame(0, ScopedComment::count());
-
- $post = Post::create(['text' => 'Bar']);
- $post->scoped_comments()->create(['text' => 'Comment Text 2']);
-
- $this->assertSame(1, Post::count());
- $this->assertSame(1, ScopedComment::count());
- });
-
- // Global context
- $this->assertSame(2, ScopedComment::count());
- }
-
- /** @test */
- public function secondary_models_are_NOT_scoped_in_the_central_context()
- {
- $this->secondary_models_are_scoped_to_the_current_tenant_when_accessed_via_primary_model();
-
- tenancy()->end();
-
- $this->assertSame(2, Comment::count());
- }
-
- /** @test */
- public function global_models_are_not_scoped_at_all()
- {
- Schema::create('global_resources', function (Blueprint $table) {
- $table->increments('id');
- $table->string('text');
- });
-
- GlobalResource::create(['text' => 'First']);
- GlobalResource::create(['text' => 'Second']);
-
- $acme = Tenant::create([
- 'id' => 'acme',
- ]);
-
- $acme->run(function () {
- $this->assertSame(2, GlobalResource::count());
-
- GlobalResource::create(['text' => 'Third']);
- GlobalResource::create(['text' => 'Fourth']);
- });
-
- $this->assertSame(4, GlobalResource::count());
- }
-
- /** @test */
- public function tenant_id_and_relationship_is_auto_added_when_creating_primary_resources_in_tenant_context()
- {
- tenancy()->initialize($acme = Tenant::create([
- 'id' => 'acme',
- ]));
-
- $post = Post::create(['text' => 'Foo']);
-
- $this->assertSame('acme', $post->tenant_id);
- $this->assertTrue($post->relationLoaded('tenant'));
- $this->assertSame($acme, $post->tenant);
- $this->assertSame(tenant(), $post->tenant);
- }
-
- /** @test */
- public function tenant_id_is_not_auto_added_when_creating_primary_resources_in_central_context()
- {
- $this->expectException(QueryException::class);
-
- Post::create(['text' => 'Foo']);
- }
-
- /** @test */
- public function tenant_id_column_name_can_be_customized()
- {
- BelongsToTenant::$tenantIdColumn = 'team_id';
-
- Schema::drop('comments');
- Schema::drop('posts');
- Schema::create('posts', function (Blueprint $table) {
- $table->increments('id');
- $table->string('text');
-
- $table->string('team_id');
-
- $table->foreign('team_id')->references('id')->on('tenants')->onUpdate('cascade')->onDelete('cascade');
- });
-
- tenancy()->initialize($acme = Tenant::create([
- 'id' => 'acme',
- ]));
-
- $post = Post::create(['text' => 'Foo']);
-
- $this->assertSame('acme', $post->team_id);
-
- // ======================================
- // foobar context
- tenancy()->initialize($foobar = Tenant::create([
- 'id' => 'foobar',
- ]));
-
- $post = Post::create(['text' => 'Bar']);
-
- $this->assertSame('foobar', $post->team_id);
-
- $post = Post::first();
-
- $this->assertSame('foobar', $post->team_id);
-
- // ======================================
- // acme context again
-
- tenancy()->initialize($acme);
-
- $post = Post::first();
- $this->assertSame('acme', $post->team_id);
-
- // Assert foobar models are inaccessible in acme context
- $this->assertSame(1, Post::count());
- }
-
- /** @test */
- public function the_model_returned_by_the_tenant_helper_has_unique_and_exists_validation_rules()
- {
- Schema::table('posts', function (Blueprint $table) {
- $table->string('slug')->nullable();
- $table->unique(['tenant_id', 'slug']);
- });
-
- tenancy()->initialize($acme = Tenant::create([
- 'id' => 'acme',
- ]));
-
- Post::create(['text' => 'Foo', 'slug' => 'foo']);
- $data = ['text' => 'Foo 2', 'slug' => 'foo'];
-
- $uniqueFails = Validator::make($data, [
- 'slug' => 'unique:posts',
- ])->fails();
- $existsFails = Validator::make($data, [
- 'slug' => 'exists:posts',
- ])->fails();
-
- // Assert that 'unique' and 'exists' aren't scoped by default
- // $this->assertFalse($uniqueFails); // todo get these two assertions to pass. for some reason, the validator is passing for both 'unique' and 'exists'
- // $this->assertTrue($existsFails); // todo get these two assertions to pass. for some reason, the validator is passing for both 'unique' and 'exists'
-
- $uniqueFails = Validator::make($data, [
- 'slug' => tenant()->unique('posts'),
- ])->fails();
- $existsFails = Validator::make($data, [
- 'slug' => tenant()->exists('posts'),
- ])->fails();
-
- // Assert that tenant()->unique() and tenant()->exists() are scoped
- $this->assertTrue($uniqueFails);
- $this->assertFalse($existsFails);
- }
+ // ================
+ // acme context again
+ tenancy()->initialize($acme);
+ expect(Post::count())->toBe(1);
+ expect(Post::first()->comments->count())->toBe(1);
}
class Tenant extends TestTenant
diff --git a/tests/SubdomainTest.php b/tests/SubdomainTest.php
index 17fbc1b3..6cbe1f05 100644
--- a/tests/SubdomainTest.php
+++ b/tests/SubdomainTest.php
@@ -2,151 +2,129 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Database\Concerns\HasDomains;
-use Stancl\Tenancy\Database\Models;
use Stancl\Tenancy\Exceptions\NotASubdomainException;
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
+use Stancl\Tenancy\Database\Models;
-class SubdomainTest extends TestCase
-{
- public function setUp(): void
- {
- parent::setUp();
+beforeEach(function () {
+ // Global state cleanup after some tests
+ InitializeTenancyBySubdomain::$onFail = null;
- // Global state cleanup after some tests
- InitializeTenancyBySubdomain::$onFail = null;
-
- Route::group([
- 'middleware' => InitializeTenancyBySubdomain::class,
- ], function () {
- Route::get('/foo/{a}/{b}', function ($a, $b) {
- return "$a + $b";
- });
+ Route::group([
+ 'middleware' => InitializeTenancyBySubdomain::class,
+ ], function () {
+ Route::get('/foo/{a}/{b}', function ($a, $b) {
+ return "$a + $b";
});
+ });
- config(['tenancy.tenant_model' => SubdomainTenant::class]);
- }
+ config(['tenancy.tenant_model' => SubdomainTenant::class]);
+});
- /** @test */
- public function tenant_can_be_identified_by_subdomain()
- {
- $tenant = SubdomainTenant::create([
- 'id' => 'acme',
- ]);
+test('tenant can be identified by subdomain', function () {
+ $tenant = SubdomainTenant::create([
+ 'id' => 'acme',
+ ]);
- $tenant->domains()->create([
- 'domain' => 'foo',
- ]);
+ $tenant->domains()->create([
+ 'domain' => 'foo',
+ ]);
- $this->assertFalse(tenancy()->initialized);
+ expect(tenancy()->initialized)->toBeFalse();
- $this
- ->get('http://foo.localhost/foo/abc/xyz')
- ->assertSee('abc + xyz');
+ $this
+ ->get('http://foo.localhost/foo/abc/xyz')
+ ->assertSee('abc + xyz');
- $this->assertTrue(tenancy()->initialized);
- $this->assertSame('acme', tenant('id'));
- }
+ expect(tenancy()->initialized)->toBeTrue();
+ expect(tenant('id'))->toBe('acme');
+});
- /** @test */
- public function onfail_logic_can_be_customized()
- {
- InitializeTenancyBySubdomain::$onFail = function () {
- return 'foo';
- };
+test('onfail logic can be customized', function () {
+ InitializeTenancyBySubdomain::$onFail = function () {
+ return 'foo';
+ };
- $this
- ->get('http://foo.localhost/foo/abc/xyz')
- ->assertSee('foo');
- }
+ $this
+ ->get('http://foo.localhost/foo/abc/xyz')
+ ->assertSee('foo');
+});
- /** @test */
- public function localhost_is_not_a_valid_subdomain()
- {
- $this->expectException(NotASubdomainException::class);
+test('localhost is not a valid subdomain', function () {
+ $this->expectException(NotASubdomainException::class);
- $this
- ->withoutExceptionHandling()
- ->get('http://localhost/foo/abc/xyz');
- }
+ $this
+ ->withoutExceptionHandling()
+ ->get('http://localhost/foo/abc/xyz');
+});
- /** @test */
- public function ip_address_is_not_a_valid_subdomain()
- {
- $this->expectException(NotASubdomainException::class);
+test('ip address is not a valid subdomain', function () {
+ $this->expectException(NotASubdomainException::class);
- $this
- ->withoutExceptionHandling()
- ->get('http://127.0.0.1/foo/abc/xyz');
- }
+ $this
+ ->withoutExceptionHandling()
+ ->get('http://127.0.0.1/foo/abc/xyz');
+});
- /** @test */
- public function oninvalidsubdomain_logic_can_be_customized()
- {
- // in this case, we need to return a response instance
- // since a string would be treated as the subdomain
- InitializeTenancyBySubdomain::$onFail = function ($e) {
- if ($e instanceof NotASubdomainException) {
- return response('foo custom invalid subdomain handler');
- }
+test('oninvalidsubdomain logic can be customized', function () {
+ // in this case, we need to return a response instance
+ // since a string would be treated as the subdomain
+ InitializeTenancyBySubdomain::$onFail = function ($e) {
+ if ($e instanceof NotASubdomainException) {
+ return response('foo custom invalid subdomain handler');
+ }
- throw $e;
- };
+ throw $e;
+ };
- $this
- ->withoutExceptionHandling()
- ->get('http://127.0.0.1/foo/abc/xyz')
- ->assertSee('foo custom invalid subdomain handler');
- }
+ $this
+ ->withoutExceptionHandling()
+ ->get('http://127.0.0.1/foo/abc/xyz')
+ ->assertSee('foo custom invalid subdomain handler');
+});
- /** @test */
- public function we_cant_use_a_subdomain_that_doesnt_belong_to_our_central_domains()
- {
- config(['tenancy.central_domains' => [
- '127.0.0.1',
- // not 'localhost'
- ]]);
+test('we cant use a subdomain that doesnt belong to our central domains', function () {
+ config(['tenancy.central_domains' => [
+ '127.0.0.1',
+ // not 'localhost'
+ ]]);
- $tenant = SubdomainTenant::create([
- 'id' => 'acme',
- ]);
+ $tenant = SubdomainTenant::create([
+ 'id' => 'acme',
+ ]);
- $tenant->domains()->create([
- 'domain' => 'foo',
- ]);
+ $tenant->domains()->create([
+ 'domain' => 'foo',
+ ]);
- $this->expectException(NotASubdomainException::class);
+ $this->expectException(NotASubdomainException::class);
- $this
- ->withoutExceptionHandling()
- ->get('http://foo.localhost/foo/abc/xyz');
- }
+ $this
+ ->withoutExceptionHandling()
+ ->get('http://foo.localhost/foo/abc/xyz');
+});
- /** @test */
- public function central_domain_is_not_a_subdomain()
- {
- config(['tenancy.central_domains' => [
- 'localhost',
- ]]);
+test('central domain is not a subdomain', function () {
+ config(['tenancy.central_domains' => [
+ 'localhost',
+ ]]);
- $tenant = SubdomainTenant::create([
- 'id' => 'acme',
- ]);
+ $tenant = SubdomainTenant::create([
+ 'id' => 'acme',
+ ]);
- $tenant->domains()->create([
- 'domain' => 'acme',
- ]);
+ $tenant->domains()->create([
+ 'domain' => 'acme',
+ ]);
- $this->expectException(NotASubdomainException::class);
+ $this->expectException(NotASubdomainException::class);
- $this
- ->withoutExceptionHandling()
- ->get('http://localhost/foo/abc/xyz');
- }
-}
+ $this
+ ->withoutExceptionHandling()
+ ->get('http://localhost/foo/abc/xyz');
+});
class SubdomainTenant extends Models\Tenant
{
diff --git a/tests/TenantAssetTest.php b/tests/TenantAssetTest.php
index 77a130b4..93a0d3b3 100644
--- a/tests/TenantAssetTest.php
+++ b/tests/TenantAssetTest.php
@@ -2,8 +2,6 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
@@ -15,115 +13,94 @@ use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
use Stancl\Tenancy\Tests\Etc\Tenant;
-class TenantAssetTest extends TestCase
+beforeEach(function () {
+ config(['tenancy.bootstrappers' => [
+ FilesystemTenancyBootstrapper::class,
+ ]]);
+
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+});
+
+afterEach(function () {
+ // Cleanup
+ TenantAssetsController::$tenancyMiddleware = InitializeTenancyByDomain::class;
+});
+
+test('asset can be accessed using the url returned by the tenant asset helper', function () {
+ TenantAssetsController::$tenancyMiddleware = InitializeTenancyByRequestData::class;
+
+ $tenant = Tenant::create();
+ tenancy()->initialize($tenant);
+
+ $filename = 'testfile' . $this->randomString(10);
+ Storage::disk('public')->put($filename, 'bar');
+ $path = storage_path("app/public/$filename");
+
+ // response()->file() returns BinaryFileResponse whose content is
+ // inaccessible via getContent, so ->assertSee() can't be used
+ expect($path)->toBeFile();
+ $response = $this->get(tenant_asset($filename), [
+ 'X-Tenant' => $tenant->id,
+ ]);
+
+ $response->assertSuccessful();
+
+ $f = fopen($path, 'r');
+ $content = fread($f, filesize($path));
+ fclose($f);
+
+ expect($content)->toBe('bar');
+});
+
+test('asset helper returns a link to tenant asset controller when asset url is null', function () {
+ config(['app.asset_url' => null]);
+
+ $tenant = Tenant::create();
+ tenancy()->initialize($tenant);
+
+ expect(asset('foo'))->toBe(route('stancl.tenancy.asset', ['path' => 'foo']));
+});
+
+test('asset helper returns a link to an external url when asset url is not null', function () {
+ config(['app.asset_url' => 'https://an-s3-bucket']);
+
+ $tenant = Tenant::create();
+ tenancy()->initialize($tenant);
+
+ expect(asset('foo'))->toBe("https://an-s3-bucket/tenant{$tenant->id}/foo");
+});
+
+test('global asset helper returns the same url regardless of tenancy initialization', function () {
+ $original = global_asset('foobar');
+ expect(global_asset('foobar'))->toBe(asset('foobar'));
+
+ $tenant = Tenant::create();
+ tenancy()->initialize($tenant);
+
+ expect(global_asset('foobar'))->toBe($original);
+});
+
+test('asset helper tenancy can be disabled', function () {
+ $original = asset('foo');
+
+ config([
+ 'app.asset_url' => null,
+ 'tenancy.filesystem.asset_helper_tenancy' => false,
+ ]);
+
+ $tenant = Tenant::create();
+ tenancy()->initialize($tenant);
+
+ expect(asset('foo'))->toBe($original);
+});
+
+function getEnvironmentSetUp($app)
{
- public function getEnvironmentSetUp($app)
- {
- parent::getEnvironmentSetUp($app);
-
- $app->booted(function () {
- if (file_exists(base_path('routes/tenant.php'))) {
- Route::middleware(['web'])
- ->namespace($this->app['config']['tenancy.tenant_route_namespace'] ?? 'App\Http\Controllers')
- ->group(base_path('routes/tenant.php'));
- }
- });
- }
-
- public function setUp(): void
- {
- parent::setUp();
-
- config(['tenancy.bootstrappers' => [
- FilesystemTenancyBootstrapper::class,
- ]]);
-
- Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
- }
-
- public function tearDown(): void
- {
- parent::tearDown();
-
- // Cleanup
- TenantAssetsController::$tenancyMiddleware = InitializeTenancyByDomain::class;
- }
-
- /** @test */
- public function asset_can_be_accessed_using_the_url_returned_by_the_tenant_asset_helper()
- {
- TenantAssetsController::$tenancyMiddleware = InitializeTenancyByRequestData::class;
-
- $tenant = Tenant::create();
- tenancy()->initialize($tenant);
-
- $filename = 'testfile' . $this->randomString(10);
- Storage::disk('public')->put($filename, 'bar');
- $path = storage_path("app/public/$filename");
-
- // response()->file() returns BinaryFileResponse whose content is
- // inaccessible via getContent, so ->assertSee() can't be used
- $this->assertFileExists($path);
- $response = $this->get(tenant_asset($filename), [
- 'X-Tenant' => $tenant->id,
- ]);
-
- $response->assertSuccessful();
-
- $f = fopen($path, 'r');
- $content = fread($f, filesize($path));
- fclose($f);
-
- $this->assertSame('bar', $content);
- }
-
- /** @test */
- public function asset_helper_returns_a_link_to_TenantAssetController_when_asset_url_is_null()
- {
- config(['app.asset_url' => null]);
-
- $tenant = Tenant::create();
- tenancy()->initialize($tenant);
-
- $this->assertSame(route('stancl.tenancy.asset', ['path' => 'foo']), asset('foo'));
- }
-
- /** @test */
- public function asset_helper_returns_a_link_to_an_external_url_when_asset_url_is_not_null()
- {
- config(['app.asset_url' => 'https://an-s3-bucket']);
-
- $tenant = Tenant::create();
- tenancy()->initialize($tenant);
-
- $this->assertSame("https://an-s3-bucket/tenant{$tenant->id}/foo", asset('foo'));
- }
-
- /** @test */
- public function global_asset_helper_returns_the_same_url_regardless_of_tenancy_initialization()
- {
- $original = global_asset('foobar');
- $this->assertSame(asset('foobar'), global_asset('foobar'));
-
- $tenant = Tenant::create();
- tenancy()->initialize($tenant);
-
- $this->assertSame($original, global_asset('foobar'));
- }
-
- /** @test */
- public function asset_helper_tenancy_can_be_disabled()
- {
- $original = asset('foo');
-
- config([
- 'app.asset_url' => null,
- 'tenancy.filesystem.asset_helper_tenancy' => false,
- ]);
-
- $tenant = Tenant::create();
- tenancy()->initialize($tenant);
-
- $this->assertSame($original, asset('foo'));
- }
+ $app->booted(function () {
+ if (file_exists(base_path('routes/tenant.php'))) {
+ Route::middleware(['web'])
+ ->namespace(test()->app['config']['tenancy.tenant_route_namespace'] ?? 'App\Http\Controllers')
+ ->group(base_path('routes/tenant.php'));
+ }
+ });
}
diff --git a/tests/TenantAwareCommandTest.php b/tests/TenantAwareCommandTest.php
index b8d75aed..1332ccfd 100644
--- a/tests/TenantAwareCommandTest.php
+++ b/tests/TenantAwareCommandTest.php
@@ -2,32 +2,25 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Stancl\Tenancy\Tests\Etc\Tenant;
-class TenantAwareCommandTest extends TestCase
-{
- /** @test */
- public function commands_run_globally_are_tenant_aware_and_return_valid_exit_code()
- {
- $tenant1 = Tenant::create();
- $tenant2 = Tenant::create();
- Artisan::call('tenants:migrate', [
- '--tenants' => [$tenant1['id'], $tenant2['id']],
- ]);
+test('commands run globally are tenant aware and return valid exit code', function () {
+ $tenant1 = Tenant::create();
+ $tenant2 = Tenant::create();
+ Artisan::call('tenants:migrate', [
+ '--tenants' => [$tenant1['id'], $tenant2['id']],
+ ]);
- $this->artisan('user:add')
- ->assertExitCode(0);
+ $this->artisan('user:add')
+ ->assertExitCode(0);
- tenancy()->initialize($tenant1);
- $this->assertNotEmpty(DB::table('users')->get());
- tenancy()->end();
+ tenancy()->initialize($tenant1);
+ $this->assertNotEmpty(DB::table('users')->get());
+ tenancy()->end();
- tenancy()->initialize($tenant2);
- $this->assertNotEmpty(DB::table('users')->get());
- tenancy()->end();
- }
-}
+ tenancy()->initialize($tenant2);
+ $this->assertNotEmpty(DB::table('users')->get());
+ tenancy()->end();
+});
diff --git a/tests/TenantDatabaseManagerTest.php b/tests/TenantDatabaseManagerTest.php
index 12273c85..5a470972 100644
--- a/tests/TenantDatabaseManagerTest.php
+++ b/tests/TenantDatabaseManagerTest.php
@@ -2,13 +2,10 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
-use PDO;
use Stancl\JobPipeline\JobPipeline;
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
use Stancl\Tenancy\Database\DatabaseManager;
@@ -27,252 +24,228 @@ use Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLSchemaManager;
use Stancl\Tenancy\TenantDatabaseManagers\SQLiteDatabaseManager;
use Stancl\Tenancy\Tests\Etc\Tenant;
-class TenantDatabaseManagerTest extends TestCase
+test('databases can be created and deleted', function ($driver, $databaseManager) {
+ Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener());
+
+ config()->set([
+ "tenancy.database.managers.$driver" => $databaseManager,
+ ]);
+
+ $name = 'db' . $this->randomString();
+
+ $manager = app($databaseManager);
+ $manager->setConnection($driver);
+
+ expect($manager->databaseExists($name))->toBeFalse();
+
+ $tenant = Tenant::create([
+ 'tenancy_db_name' => $name,
+ 'tenancy_db_connection' => $driver,
+ ]);
+
+ expect($manager->databaseExists($name))->toBeTrue();
+ $manager->deleteDatabase($tenant);
+ expect($manager->databaseExists($name))->toBeFalse();
+})->with('database_manager_provider');
+
+test('dbs can be created when another driver is used for the central db', function () {
+ expect(config('database.default'))->toBe('central');
+
+ Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener());
+
+ $database = 'db' . $this->randomString();
+
+ $mysqlmanager = app(MySQLDatabaseManager::class);
+ $mysqlmanager->setConnection('mysql');
+
+ expect($mysqlmanager->databaseExists($database))->toBeFalse();
+ Tenant::create([
+ 'tenancy_db_name' => $database,
+ 'tenancy_db_connection' => 'mysql',
+ ]);
+
+ expect($mysqlmanager->databaseExists($database))->toBeTrue();
+
+ $postgresManager = app(PostgreSQLDatabaseManager::class);
+ $postgresManager->setConnection('pgsql');
+
+ $database = 'db' . $this->randomString();
+ expect($postgresManager->databaseExists($database))->toBeFalse();
+
+ Tenant::create([
+ 'tenancy_db_name' => $database,
+ 'tenancy_db_connection' => 'pgsql',
+ ]);
+
+ expect($postgresManager->databaseExists($database))->toBeTrue();
+});
+
+test('the tenant connection is fully removed', function () {
+ config([
+ 'tenancy.boostrappers' => [
+ DatabaseTenancyBootstrapper::class,
+ ],
+ ]);
+
+ Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener());
+
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+ Event::listen(TenancyEnded::class, RevertToCentralContext::class);
+
+ $tenant = Tenant::create();
+
+ expect(array_keys(app('db')->getConnections()))->toBe(['central']);
+ $this->assertArrayNotHasKey('tenant', config('database.connections'));
+
+ tenancy()->initialize($tenant);
+
+ createUsersTable();
+
+ expect(array_keys(app('db')->getConnections()))->toBe(['central', 'tenant']);
+ $this->assertArrayHasKey('tenant', config('database.connections'));
+
+ tenancy()->end();
+
+ expect(array_keys(app('db')->getConnections()))->toBe(['central']);
+ expect(config('database.connections.tenant'))->toBeNull();
+});
+
+test('db name is prefixed with db path when sqlite is used', function () {
+ if (file_exists(database_path('foodb'))) {
+ unlink(database_path('foodb')); // cleanup
+ }
+ config([
+ 'database.connections.fooconn.driver' => 'sqlite',
+ ]);
+
+ $tenant = Tenant::create([
+ 'tenancy_db_name' => 'foodb',
+ 'tenancy_db_connection' => 'fooconn',
+ ]);
+ app(DatabaseManager::class)->createTenantConnection($tenant);
+
+ expect(database_path('foodb'))->toBe(config('database.connections.tenant.database'));
+});
+
+test('schema manager uses schema to separate tenant dbs', function () {
+ config([
+ 'tenancy.database.managers.pgsql' => \Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLSchemaManager::class,
+ 'tenancy.boostrappers' => [
+ DatabaseTenancyBootstrapper::class,
+ ],
+ ]);
+
+ Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener());
+
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+
+ $originalDatabaseName = config(['database.connections.pgsql.database']);
+
+ $tenant = Tenant::create([
+ 'tenancy_db_connection' => 'pgsql',
+ ]);
+ tenancy()->initialize($tenant);
+
+ $schemaConfig = version_compare(app()->version(), '9.0', '>=') ?
+ config('database.connections.' . config('database.default') . '.search_path') :
+ config('database.connections.' . config('database.default') . '.schema');
+
+ expect($schemaConfig)->toBe($tenant->database()->getName());
+ expect(config(['database.connections.pgsql.database']))->toBe($originalDatabaseName);
+});
+
+test('a tenants database cannot be created when the database already exists', function () {
+ Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener());
+
+ $name = 'foo' . Str::random(8);
+ $tenant = Tenant::create([
+ 'tenancy_db_name' => $name,
+ ]);
+
+ $manager = $tenant->database()->manager();
+ expect($manager->databaseExists($tenant->database()->getName()))->toBeTrue();
+
+ $this->expectException(TenantDatabaseAlreadyExistsException::class);
+ $tenant2 = Tenant::create([
+ 'tenancy_db_name' => $name,
+ ]);
+});
+
+test('tenant database can be created on a foreign server', function () {
+ config([
+ 'tenancy.database.managers.mysql' => PermissionControlledMySQLDatabaseManager::class,
+ 'database.connections.mysql2' => [
+ 'driver' => 'mysql',
+ 'host' => 'mysql2', // important line
+ 'port' => 3306,
+ 'database' => 'main',
+ 'username' => 'root',
+ 'password' => 'password',
+ 'unix_socket' => env('DB_SOCKET', ''),
+ 'charset' => 'utf8mb4',
+ 'collation' => 'utf8mb4_unicode_ci',
+ 'prefix' => '',
+ 'prefix_indexes' => true,
+ 'strict' => true,
+ 'engine' => null,
+ 'options' => extension_loaded('pdo_mysql') ? array_filter([
+ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
+ ]) : [],
+ ],
+ ]);
+
+ Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener());
+
+ $name = 'foo' . Str::random(8);
+ $tenant = Tenant::create([
+ 'tenancy_db_name' => $name,
+ 'tenancy_db_connection' => 'mysql2',
+ ]);
+
+ /** @var PermissionControlledMySQLDatabaseManager $manager */
+ $manager = $tenant->database()->manager();
+
+ $manager->setConnection('mysql');
+ expect($manager->databaseExists($name))->toBeFalse();
+
+ $manager->setConnection('mysql2');
+ expect($manager->databaseExists($name))->toBeTrue();
+});
+
+test('path used by sqlite manager can be customized', function () {
+ $this->markTestIncomplete();
+});
+
+// Datasets
+dataset('database_manager_provider', [
+ ['mysql', MySQLDatabaseManager::class],
+ ['mysql', PermissionControlledMySQLDatabaseManager::class],
+ ['sqlite', SQLiteDatabaseManager::class],
+ ['pgsql', PostgreSQLDatabaseManager::class],
+ ['pgsql', PostgreSQLSchemaManager::class],
+ ['sqlsrv', MicrosoftSQLDatabaseManager::class]
+]);
+
+function createUsersTable()
{
- /**
- * @test
- * @dataProvider database_manager_provider
- */
- public function databases_can_be_created_and_deleted($driver, $databaseManager)
- {
- Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener());
-
- config()->set([
- "tenancy.database.managers.$driver" => $databaseManager,
- ]);
-
- $name = 'db' . $this->randomString();
-
- $manager = app($databaseManager);
- $manager->setConnection($driver);
-
- $this->assertFalse($manager->databaseExists($name));
-
- $tenant = Tenant::create([
- 'tenancy_db_name' => $name,
- 'tenancy_db_connection' => $driver,
- ]);
-
- $this->assertTrue($manager->databaseExists($name));
- $manager->deleteDatabase($tenant);
- $this->assertFalse($manager->databaseExists($name));
- }
-
- /** @test */
- public function dbs_can_be_created_when_another_driver_is_used_for_the_central_db()
- {
- $this->assertSame('central', config('database.default'));
-
- Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener());
-
- $database = 'db' . $this->randomString();
-
- $mysqlmanager = app(MySQLDatabaseManager::class);
- $mysqlmanager->setConnection('mysql');
-
- $this->assertFalse($mysqlmanager->databaseExists($database));
- Tenant::create([
- 'tenancy_db_name' => $database,
- 'tenancy_db_connection' => 'mysql',
- ]);
-
- $this->assertTrue($mysqlmanager->databaseExists($database));
-
- $postgresManager = app(PostgreSQLDatabaseManager::class);
- $postgresManager->setConnection('pgsql');
-
- $database = 'db' . $this->randomString();
- $this->assertFalse($postgresManager->databaseExists($database));
-
- Tenant::create([
- 'tenancy_db_name' => $database,
- 'tenancy_db_connection' => 'pgsql',
- ]);
-
- $this->assertTrue($postgresManager->databaseExists($database));
- }
-
- public function database_manager_provider()
- {
- return [
- ['mysql', MySQLDatabaseManager::class],
- ['mysql', PermissionControlledMySQLDatabaseManager::class],
- ['sqlite', SQLiteDatabaseManager::class],
- ['pgsql', PostgreSQLDatabaseManager::class],
- ['pgsql', PostgreSQLSchemaManager::class],
- ['sqlsrv', MicrosoftSQLDatabaseManager::class],
- ];
- }
-
- /** @test */
- public function the_tenant_connection_is_fully_removed()
- {
- config([
- 'tenancy.boostrappers' => [
- DatabaseTenancyBootstrapper::class,
- ],
- ]);
-
- Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener());
-
- Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
- Event::listen(TenancyEnded::class, RevertToCentralContext::class);
-
- $tenant = Tenant::create();
-
- $this->assertSame(['central'], array_keys(app('db')->getConnections()));
- $this->assertArrayNotHasKey('tenant', config('database.connections'));
-
- tenancy()->initialize($tenant);
-
- $this->createUsersTable();
-
- $this->assertSame(['central', 'tenant'], array_keys(app('db')->getConnections()));
- $this->assertArrayHasKey('tenant', config('database.connections'));
-
- tenancy()->end();
-
- $this->assertSame(['central'], array_keys(app('db')->getConnections()));
- $this->assertNull(config('database.connections.tenant'));
- }
-
- protected function createUsersTable()
- {
- Schema::create('users', function (Blueprint $table) {
- $table->increments('id');
- $table->string('name');
- $table->string('email')->unique();
- $table->string('password');
- $table->rememberToken();
- $table->timestamps();
- });
- }
-
- /** @test */
- public function db_name_is_prefixed_with_db_path_when_sqlite_is_used()
- {
- if (file_exists(database_path('foodb'))) {
- unlink(database_path('foodb')); // cleanup
- }
- config([
- 'database.connections.fooconn.driver' => 'sqlite',
- ]);
-
- $tenant = Tenant::create([
- 'tenancy_db_name' => 'foodb',
- 'tenancy_db_connection' => 'fooconn',
- ]);
- app(DatabaseManager::class)->createTenantConnection($tenant);
-
- $this->assertSame(config('database.connections.tenant.database'), database_path('foodb'));
- }
-
- /** @test */
- public function schema_manager_uses_schema_to_separate_tenant_dbs()
- {
- config([
- 'tenancy.database.managers.pgsql' => \Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLSchemaManager::class,
- 'tenancy.boostrappers' => [
- DatabaseTenancyBootstrapper::class,
- ],
- ]);
-
- Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener());
-
- Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
-
- $originalDatabaseName = config(['database.connections.pgsql.database']);
-
- $tenant = Tenant::create([
- 'tenancy_db_connection' => 'pgsql',
- ]);
- tenancy()->initialize($tenant);
-
- $schemaConfig = version_compare(app()->version(), '9.0', '>=') ?
- config('database.connections.' . config('database.default') . '.search_path') :
- config('database.connections.' . config('database.default') . '.schema');
-
- $this->assertSame($tenant->database()->getName(), $schemaConfig);
- $this->assertSame($originalDatabaseName, config(['database.connections.pgsql.database']));
- }
-
- /** @test */
- public function a_tenants_database_cannot_be_created_when_the_database_already_exists()
- {
- Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener());
-
- $name = 'foo' . Str::random(8);
- $tenant = Tenant::create([
- 'tenancy_db_name' => $name,
- ]);
-
- $manager = $tenant->database()->manager();
- $this->assertTrue($manager->databaseExists($tenant->database()->getName()));
-
- $this->expectException(TenantDatabaseAlreadyExistsException::class);
- $tenant2 = Tenant::create([
- 'tenancy_db_name' => $name,
- ]);
- }
-
- /** @test */
- public function tenant_database_can_be_created_on_a_foreign_server()
- {
- config([
- 'tenancy.database.managers.mysql' => PermissionControlledMySQLDatabaseManager::class,
- 'database.connections.mysql2' => [
- 'driver' => 'mysql',
- 'host' => 'mysql2', // important line
- 'port' => 3306,
- 'database' => 'main',
- 'username' => 'root',
- 'password' => 'password',
- 'unix_socket' => env('DB_SOCKET', ''),
- 'charset' => 'utf8mb4',
- 'collation' => 'utf8mb4_unicode_ci',
- 'prefix' => '',
- 'prefix_indexes' => true,
- 'strict' => true,
- 'engine' => null,
- 'options' => extension_loaded('pdo_mysql') ? array_filter([
- PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
- ]) : [],
- ],
- ]);
-
- Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener());
-
- $name = 'foo' . Str::random(8);
- $tenant = Tenant::create([
- 'tenancy_db_name' => $name,
- 'tenancy_db_connection' => 'mysql2',
- ]);
-
- /** @var PermissionControlledMySQLDatabaseManager $manager */
- $manager = $tenant->database()->manager();
-
- $manager->setConnection('mysql');
- $this->assertFalse($manager->databaseExists($name));
-
- $manager->setConnection('mysql2');
- $this->assertTrue($manager->databaseExists($name));
- }
-
- /** @test */
- public function path_used_by_sqlite_manager_can_be_customized()
- {
- $this->markTestIncomplete();
- }
+ Schema::create('users', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('name');
+ $table->string('email')->unique();
+ $table->string('password');
+ $table->rememberToken();
+ $table->timestamps();
+ });
}
diff --git a/tests/TenantModelTest.php b/tests/TenantModelTest.php
index 2d46c233..346a208e 100644
--- a/tests/TenantModelTest.php
+++ b/tests/TenantModelTest.php
@@ -2,8 +2,6 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Event;
@@ -21,148 +19,127 @@ use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Tests\Etc\Tenant;
use Stancl\Tenancy\UUIDGenerator;
-class TenantModelTest extends TestCase
-{
- /** @test */
- public function created_event_is_dispatched()
- {
- Event::fake([TenantCreated::class]);
+test('created event is dispatched', function () {
+ Event::fake([TenantCreated::class]);
- Event::assertNotDispatched(TenantCreated::class);
+ Event::assertNotDispatched(TenantCreated::class);
- Tenant::create();
+ Tenant::create();
- Event::assertDispatched(TenantCreated::class);
- }
+ Event::assertDispatched(TenantCreated::class);
+});
- /** @test */
- public function current_tenant_can_be_resolved_from_service_container_using_typehint()
- {
- $tenant = Tenant::create();
+test('current tenant can be resolved from service container using typehint', function () {
+ $tenant = Tenant::create();
- tenancy()->initialize($tenant);
+ tenancy()->initialize($tenant);
- $this->assertSame($tenant->id, app(Contracts\Tenant::class)->id);
+ expect(app(Contracts\Tenant::class)->id)->toBe($tenant->id);
- tenancy()->end();
+ tenancy()->end();
- $this->assertSame(null, app(Contracts\Tenant::class));
- }
+ expect(app(Contracts\Tenant::class))->toBe(null);
+});
- /** @test */
- public function id_is_generated_when_no_id_is_supplied()
- {
- config(['tenancy.id_generator' => UUIDGenerator::class]);
+test('id is generated when no id is supplied', function () {
+ config(['tenancy.id_generator' => UUIDGenerator::class]);
- $this->mock(UUIDGenerator::class, function ($mock) {
- return $mock->shouldReceive('generate')->once();
- });
+ $this->mock(UUIDGenerator::class, function ($mock) {
+ return $mock->shouldReceive('generate')->once();
+ });
- $tenant = Tenant::create();
+ $tenant = Tenant::create();
- $this->assertNotNull($tenant->id);
- }
+ $this->assertNotNull($tenant->id);
+});
- /** @test */
- public function autoincrement_ids_are_supported()
- {
- Schema::drop('domains');
- Schema::table('tenants', function (Blueprint $table) {
- $table->bigIncrements('id')->change();
- });
+test('autoincrement ids are supported', function () {
+ Schema::drop('domains');
+ Schema::table('tenants', function (Blueprint $table) {
+ $table->bigIncrements('id')->change();
+ });
- unset(app()[UniqueIdentifierGenerator::class]);
+ unset(app()[UniqueIdentifierGenerator::class]);
- $tenant1 = Tenant::create();
- $tenant2 = Tenant::create();
+ $tenant1 = Tenant::create();
+ $tenant2 = Tenant::create();
- $this->assertSame(1, $tenant1->id);
- $this->assertSame(2, $tenant2->id);
- }
+ expect($tenant1->id)->toBe(1);
+ expect($tenant2->id)->toBe(2);
+});
- /** @test */
- public function custom_tenant_model_can_be_used()
- {
- $tenant = MyTenant::create();
+test('custom tenant model can be used', function () {
+ $tenant = MyTenant::create();
- tenancy()->initialize($tenant);
+ tenancy()->initialize($tenant);
- $this->assertTrue(tenant() instanceof MyTenant);
- }
+ expect(tenant() instanceof MyTenant)->toBeTrue();
+});
- /** @test */
- public function custom_tenant_model_that_doesnt_extend_vendor_Tenant_model_can_be_used()
- {
- $tenant = AnotherTenant::create([
- 'id' => 'acme',
+test('custom tenant model that doesnt extend vendor tenant model can be used', function () {
+ $tenant = AnotherTenant::create([
+ 'id' => 'acme',
+ ]);
+
+ tenancy()->initialize($tenant);
+
+ expect(tenant() instanceof AnotherTenant)->toBeTrue();
+});
+
+test('tenant can be created even when we are in another tenants context', function () {
+ config(['tenancy.bootstrappers' => [
+ DatabaseTenancyBootstrapper::class,
+ ]]);
+
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+ Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function ($event) {
+ return $event->tenant;
+ })->toListener());
+
+ $tenant1 = Tenant::create([
+ 'id' => 'foo',
+ 'tenancy_db_name' => 'db' . Str::random(16),
+ ]);
+
+ tenancy()->initialize($tenant1);
+
+ $tenant2 = Tenant::create([
+ 'id' => 'bar',
+ 'tenancy_db_name' => 'db' . Str::random(16),
+ ]);
+
+ tenancy()->end();
+
+ expect(Tenant::count())->toBe(2);
+});
+
+test('the model uses tenant collection', function () {
+ Tenant::create();
+ Tenant::create();
+
+ expect(Tenant::count())->toBe(2);
+ expect(Tenant::all() instanceof TenantCollection)->toBeTrue();
+});
+
+test('a command can be run on a collection of tenants', function () {
+ Tenant::create([
+ 'id' => 't1',
+ 'foo' => 'bar',
+ ]);
+ Tenant::create([
+ 'id' => 't2',
+ 'foo' => 'bar',
+ ]);
+
+ Tenant::all()->runForEach(function ($tenant) {
+ $tenant->update([
+ 'foo' => 'xyz',
]);
+ });
- tenancy()->initialize($tenant);
-
- $this->assertTrue(tenant() instanceof AnotherTenant);
- }
-
- /** @test */
- public function tenant_can_be_created_even_when_we_are_in_another_tenants_context()
- {
- config(['tenancy.bootstrappers' => [
- DatabaseTenancyBootstrapper::class,
- ]]);
-
- Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
- Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function ($event) {
- return $event->tenant;
- })->toListener());
-
- $tenant1 = Tenant::create([
- 'id' => 'foo',
- 'tenancy_db_name' => 'db' . Str::random(16),
- ]);
-
- tenancy()->initialize($tenant1);
-
- $tenant2 = Tenant::create([
- 'id' => 'bar',
- 'tenancy_db_name' => 'db' . Str::random(16),
- ]);
-
- tenancy()->end();
-
- $this->assertSame(2, Tenant::count());
- }
-
- /** @test */
- public function the_model_uses_TenantCollection()
- {
- Tenant::create();
- Tenant::create();
-
- $this->assertSame(2, Tenant::count());
- $this->assertTrue(Tenant::all() instanceof TenantCollection);
- }
-
- /** @test */
- public function a_command_can_be_run_on_a_collection_of_tenants()
- {
- Tenant::create([
- 'id' => 't1',
- 'foo' => 'bar',
- ]);
- Tenant::create([
- 'id' => 't2',
- 'foo' => 'bar',
- ]);
-
- Tenant::all()->runForEach(function ($tenant) {
- $tenant->update([
- 'foo' => 'xyz',
- ]);
- });
-
- $this->assertSame('xyz', Tenant::find('t1')->foo);
- $this->assertSame('xyz', Tenant::find('t2')->foo);
- }
-}
+ expect(Tenant::find('t1')->foo)->toBe('xyz');
+ expect(Tenant::find('t2')->foo)->toBe('xyz');
+});
class MyTenant extends Tenant
{
diff --git a/tests/TenantUserImpersonationTest.php b/tests/TenantUserImpersonationTest.php
index b50db84b..bfbe0851 100644
--- a/tests/TenantUserImpersonationTest.php
+++ b/tests/TenantUserImpersonationTest.php
@@ -2,13 +2,9 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Carbon\Carbon;
use Carbon\CarbonInterval;
-use Closure;
use Illuminate\Auth\SessionGuard;
-use Illuminate\Foundation\Auth\User as Authenticable;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Route;
@@ -26,249 +22,234 @@ use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
use Stancl\Tenancy\Tests\Etc\Tenant;
+use Illuminate\Foundation\Auth\User as Authenticable;
-class TenantUserImpersonationTest extends TestCase
+beforeEach(function () {
+ $this->artisan('migrate', [
+ '--path' => __DIR__ . '/../assets/impersonation-migrations',
+ '--realpath' => true,
+ ])->assertExitCode(0);
+
+ config([
+ 'tenancy.bootstrappers' => [
+ DatabaseTenancyBootstrapper::class,
+ ],
+ 'tenancy.features' => [
+ UserImpersonation::class,
+ ],
+ ]);
+
+ Event::listen(
+ TenantCreated::class,
+ JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
+ return $event->tenant;
+ })->toListener()
+ );
+
+ Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
+ Event::listen(TenancyEnded::class, RevertToCentralContext::class);
+
+ config(['auth.providers.users.model' => ImpersonationUser::class]);
+});
+
+test('tenant user can be impersonated on a tenant domain', function () {
+ Route::middleware(InitializeTenancyByDomain::class)->group(getRoutes());
+
+ $tenant = Tenant::create();
+ $tenant->domains()->create([
+ 'domain' => 'foo.localhost',
+ ]);
+ migrateTenants();
+ $user = $tenant->run(function () {
+ return ImpersonationUser::create([
+ 'name' => 'Joe',
+ 'email' => 'joe@local',
+ 'password' => bcrypt('secret'),
+ ]);
+ });
+
+ // We try to visit the dashboard directly, before impersonating the user.
+ $this->get('http://foo.localhost/dashboard')
+ ->assertRedirect('http://foo.localhost/login');
+
+ // We impersonate the user
+ $token = tenancy()->impersonate($tenant, $user->id, '/dashboard');
+ $this->get('http://foo.localhost/impersonate/' . $token->token)
+ ->assertRedirect('http://foo.localhost/dashboard');
+
+ // Now we try to visit the dashboard directly, after impersonating the user.
+ $this->get('http://foo.localhost/dashboard')
+ ->assertSuccessful()
+ ->assertSee('You are logged in as Joe');
+});
+
+test('tenant user can be impersonated on a tenant path', function () {
+ makeLoginRoute();
+
+ Route::middleware(InitializeTenancyByPath::class)->prefix('/{tenant}')->group(getRoutes(false));
+
+ $tenant = Tenant::create([
+ 'id' => 'acme',
+ 'tenancy_db_name' => 'db' . Str::random(16),
+ ]);
+ migrateTenants();
+ $user = $tenant->run(function () {
+ return ImpersonationUser::create([
+ 'name' => 'Joe',
+ 'email' => 'joe@local',
+ 'password' => bcrypt('secret'),
+ ]);
+ });
+
+ // We try to visit the dashboard directly, before impersonating the user.
+ $this->get('/acme/dashboard')
+ ->assertRedirect('/login');
+
+ // We impersonate the user
+ $token = tenancy()->impersonate($tenant, $user->id, '/acme/dashboard');
+ $this->get('/acme/impersonate/' . $token->token)
+ ->assertRedirect('/acme/dashboard');
+
+ // Now we try to visit the dashboard directly, after impersonating the user.
+ $this->get('/acme/dashboard')
+ ->assertSuccessful()
+ ->assertSee('You are logged in as Joe');
+});
+
+test('tokens have a limited ttl', function () {
+ Route::middleware(InitializeTenancyByDomain::class)->group(getRoutes());
+
+ $tenant = Tenant::create();
+ $tenant->domains()->create([
+ 'domain' => 'foo.localhost',
+ ]);
+ migrateTenants();
+ $user = $tenant->run(function () {
+ return ImpersonationUser::create([
+ 'name' => 'Joe',
+ 'email' => 'joe@local',
+ 'password' => bcrypt('secret'),
+ ]);
+ });
+
+ // We impersonate the user
+ $token = tenancy()->impersonate($tenant, $user->id, '/dashboard');
+ $token->update([
+ 'created_at' => Carbon::now()->subtract(CarbonInterval::make('100s')),
+ ]);
+
+ $this->followingRedirects()
+ ->get('http://foo.localhost/impersonate/' . $token->token)
+ ->assertStatus(403);
+});
+
+test('tokens are deleted after use', function () {
+ Route::middleware(InitializeTenancyByDomain::class)->group(getRoutes());
+
+ $tenant = Tenant::create();
+ $tenant->domains()->create([
+ 'domain' => 'foo.localhost',
+ ]);
+ migrateTenants();
+ $user = $tenant->run(function () {
+ return ImpersonationUser::create([
+ 'name' => 'Joe',
+ 'email' => 'joe@local',
+ 'password' => bcrypt('secret'),
+ ]);
+ });
+
+ // We impersonate the user
+ $token = tenancy()->impersonate($tenant, $user->id, '/dashboard');
+
+ $this->assertNotNull(ImpersonationToken::find($token->token));
+
+ $this->followingRedirects()
+ ->get('http://foo.localhost/impersonate/' . $token->token)
+ ->assertSuccessful()
+ ->assertSee('You are logged in as Joe');
+
+ expect(ImpersonationToken::find($token->token))->toBeNull();
+});
+
+test('impersonation works with multiple models and guards', function () {
+ config([
+ 'auth.guards.another' => [
+ 'driver' => 'session',
+ 'provider' => 'another_users',
+ ],
+ 'auth.providers.another_users' => [
+ 'driver' => 'eloquent',
+ 'model' => AnotherImpersonationUser::class,
+ ],
+ ]);
+
+ Auth::extend('another', function ($app, $name, array $config) {
+ return new SessionGuard($name, Auth::createUserProvider($config['provider']), session());
+ });
+
+ Route::middleware(InitializeTenancyByDomain::class)->group(getRoutes(true, 'another'));
+
+ $tenant = Tenant::create();
+ $tenant->domains()->create([
+ 'domain' => 'foo.localhost',
+ ]);
+ migrateTenants();
+ $user = $tenant->run(function () {
+ return AnotherImpersonationUser::create([
+ 'name' => 'Joe',
+ 'email' => 'joe@local',
+ 'password' => bcrypt('secret'),
+ ]);
+ });
+
+ // We try to visit the dashboard directly, before impersonating the user.
+ $this->get('http://foo.localhost/dashboard')
+ ->assertRedirect('http://foo.localhost/login');
+
+ // We impersonate the user
+ $token = tenancy()->impersonate($tenant, $user->id, '/dashboard', 'another');
+ $this->get('http://foo.localhost/impersonate/' . $token->token)
+ ->assertRedirect('http://foo.localhost/dashboard');
+
+ // Now we try to visit the dashboard directly, after impersonating the user.
+ $this->get('http://foo.localhost/dashboard')
+ ->assertSuccessful()
+ ->assertSee('You are logged in as Joe');
+
+ Tenant::first()->run(function () {
+ expect(auth()->guard('another')->user()->name)->toBe('Joe');
+ expect(auth()->guard('web')->user())->toBe(null);
+ });
+});
+
+function migrateTenants()
{
- protected function migrateTenants()
- {
- $this->artisan('tenants:migrate')->assertExitCode(0);
- }
+ test()->artisan('tenants:migrate')->assertExitCode(0);
+}
- public function setUp(): void
- {
- parent::setUp();
+function makeLoginRoute()
+{
+ Route::get('/login', function () {
+ return 'Please log in';
+ })->name('login');
+}
- $this->artisan('migrate', [
- '--path' => __DIR__ . '/../assets/impersonation-migrations',
- '--realpath' => true,
- ])->assertExitCode(0);
+function getRoutes($loginRoute = true, $authGuard = 'web'): Closure
+{
+ return function () use ($loginRoute, $authGuard) {
+ if ($loginRoute) {
+ test()->makeLoginRoute();
+ }
- config([
- 'tenancy.bootstrappers' => [
- DatabaseTenancyBootstrapper::class,
- ],
- 'tenancy.features' => [
- UserImpersonation::class,
- ],
- ]);
+ Route::get('/dashboard', function () use ($authGuard) {
+ return 'You are logged in as ' . auth()->guard($authGuard)->user()->name;
+ })->middleware('auth:' . $authGuard);
- Event::listen(
- TenantCreated::class,
- JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
- return $event->tenant;
- })->toListener()
- );
-
- Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
- Event::listen(TenancyEnded::class, RevertToCentralContext::class);
-
- config(['auth.providers.users.model' => ImpersonationUser::class]);
- }
-
- public function makeLoginRoute()
- {
- Route::get('/login', function () {
- return 'Please log in';
- })->name('login');
- }
-
- public function getRoutes($loginRoute = true, $authGuard = 'web'): Closure
- {
- return function () use ($loginRoute, $authGuard) {
- if ($loginRoute) {
- $this->makeLoginRoute();
- }
-
- Route::get('/dashboard', function () use ($authGuard) {
- return 'You are logged in as ' . auth()->guard($authGuard)->user()->name;
- })->middleware('auth:' . $authGuard);
-
- Route::get('/impersonate/{token}', function ($token) {
- return UserImpersonation::makeResponse($token);
- });
- };
- }
-
- /** @test */
- public function tenant_user_can_be_impersonated_on_a_tenant_domain()
- {
- Route::middleware(InitializeTenancyByDomain::class)->group($this->getRoutes());
-
- $tenant = Tenant::create();
- $tenant->domains()->create([
- 'domain' => 'foo.localhost',
- ]);
- $this->migrateTenants();
- $user = $tenant->run(function () {
- return ImpersonationUser::create([
- 'name' => 'Joe',
- 'email' => 'joe@local',
- 'password' => bcrypt('secret'),
- ]);
+ Route::get('/impersonate/{token}', function ($token) {
+ return UserImpersonation::makeResponse($token);
});
-
- // We try to visit the dashboard directly, before impersonating the user.
- $this->get('http://foo.localhost/dashboard')
- ->assertRedirect('http://foo.localhost/login');
-
- // We impersonate the user
- $token = tenancy()->impersonate($tenant, $user->id, '/dashboard');
- $this->get('http://foo.localhost/impersonate/' . $token->token)
- ->assertRedirect('http://foo.localhost/dashboard');
-
- // Now we try to visit the dashboard directly, after impersonating the user.
- $this->get('http://foo.localhost/dashboard')
- ->assertSuccessful()
- ->assertSee('You are logged in as Joe');
- }
-
- /** @test */
- public function tenant_user_can_be_impersonated_on_a_tenant_path()
- {
- $this->makeLoginRoute();
-
- Route::middleware(InitializeTenancyByPath::class)->prefix('/{tenant}')->group($this->getRoutes(false));
-
- $tenant = Tenant::create([
- 'id' => 'acme',
- 'tenancy_db_name' => 'db' . Str::random(16),
- ]);
- $this->migrateTenants();
- $user = $tenant->run(function () {
- return ImpersonationUser::create([
- 'name' => 'Joe',
- 'email' => 'joe@local',
- 'password' => bcrypt('secret'),
- ]);
- });
-
- // We try to visit the dashboard directly, before impersonating the user.
- $this->get('/acme/dashboard')
- ->assertRedirect('/login');
-
- // We impersonate the user
- $token = tenancy()->impersonate($tenant, $user->id, '/acme/dashboard');
- $this->get('/acme/impersonate/' . $token->token)
- ->assertRedirect('/acme/dashboard');
-
- // Now we try to visit the dashboard directly, after impersonating the user.
- $this->get('/acme/dashboard')
- ->assertSuccessful()
- ->assertSee('You are logged in as Joe');
- }
-
- /** @test */
- public function tokens_have_a_limited_ttl()
- {
- Route::middleware(InitializeTenancyByDomain::class)->group($this->getRoutes());
-
- $tenant = Tenant::create();
- $tenant->domains()->create([
- 'domain' => 'foo.localhost',
- ]);
- $this->migrateTenants();
- $user = $tenant->run(function () {
- return ImpersonationUser::create([
- 'name' => 'Joe',
- 'email' => 'joe@local',
- 'password' => bcrypt('secret'),
- ]);
- });
-
- // We impersonate the user
- $token = tenancy()->impersonate($tenant, $user->id, '/dashboard');
- $token->update([
- 'created_at' => Carbon::now()->subtract(CarbonInterval::make('100s')),
- ]);
-
- $this->followingRedirects()
- ->get('http://foo.localhost/impersonate/' . $token->token)
- ->assertStatus(403);
- }
-
- /** @test */
- public function tokens_are_deleted_after_use()
- {
- Route::middleware(InitializeTenancyByDomain::class)->group($this->getRoutes());
-
- $tenant = Tenant::create();
- $tenant->domains()->create([
- 'domain' => 'foo.localhost',
- ]);
- $this->migrateTenants();
- $user = $tenant->run(function () {
- return ImpersonationUser::create([
- 'name' => 'Joe',
- 'email' => 'joe@local',
- 'password' => bcrypt('secret'),
- ]);
- });
-
- // We impersonate the user
- $token = tenancy()->impersonate($tenant, $user->id, '/dashboard');
-
- $this->assertNotNull(ImpersonationToken::find($token->token));
-
- $this->followingRedirects()
- ->get('http://foo.localhost/impersonate/' . $token->token)
- ->assertSuccessful()
- ->assertSee('You are logged in as Joe');
-
- $this->assertNull(ImpersonationToken::find($token->token));
- }
-
- /** @test */
- public function impersonation_works_with_multiple_models_and_guards()
- {
- config([
- 'auth.guards.another' => [
- 'driver' => 'session',
- 'provider' => 'another_users',
- ],
- 'auth.providers.another_users' => [
- 'driver' => 'eloquent',
- 'model' => AnotherImpersonationUser::class,
- ],
- ]);
-
- Auth::extend('another', function ($app, $name, array $config) {
- return new SessionGuard($name, Auth::createUserProvider($config['provider']), session());
- });
-
- Route::middleware(InitializeTenancyByDomain::class)->group($this->getRoutes(true, 'another'));
-
- $tenant = Tenant::create();
- $tenant->domains()->create([
- 'domain' => 'foo.localhost',
- ]);
- $this->migrateTenants();
- $user = $tenant->run(function () {
- return AnotherImpersonationUser::create([
- 'name' => 'Joe',
- 'email' => 'joe@local',
- 'password' => bcrypt('secret'),
- ]);
- });
-
- // We try to visit the dashboard directly, before impersonating the user.
- $this->get('http://foo.localhost/dashboard')
- ->assertRedirect('http://foo.localhost/login');
-
- // We impersonate the user
- $token = tenancy()->impersonate($tenant, $user->id, '/dashboard', 'another');
- $this->get('http://foo.localhost/impersonate/' . $token->token)
- ->assertRedirect('http://foo.localhost/dashboard');
-
- // Now we try to visit the dashboard directly, after impersonating the user.
- $this->get('http://foo.localhost/dashboard')
- ->assertSuccessful()
- ->assertSee('You are logged in as Joe');
-
- Tenant::first()->run(function () {
- $this->assertSame('Joe', auth()->guard('another')->user()->name);
- $this->assertSame(null, auth()->guard('web')->user());
- });
- }
+ };
}
class ImpersonationUser extends Authenticable
diff --git a/tests/UniversalRouteTest.php b/tests/UniversalRouteTest.php
index c0852545..04d053cf 100644
--- a/tests/UniversalRouteTest.php
+++ b/tests/UniversalRouteTest.php
@@ -2,65 +2,76 @@
declare(strict_types=1);
-namespace Stancl\Tenancy\Tests;
-
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Features\UniversalRoutes;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Tests\Etc\Tenant;
-class UniversalRouteTest extends TestCase
-{
- public function tearDown(): void
- {
- InitializeTenancyByDomain::$onFail = null;
+afterEach(function () {
+ InitializeTenancyByDomain::$onFail = null;
+});
- parent::tearDown();
- }
+test('a route can work in both central and tenant context', function () {
+ Route::middlewareGroup('universal', []);
+ config(['tenancy.features' => [UniversalRoutes::class]]);
- /** @test */
- public function a_route_can_work_in_both_central_and_tenant_context()
- {
- Route::middlewareGroup('universal', []);
- config(['tenancy.features' => [UniversalRoutes::class]]);
+ Route::get('/foo', function () {
+ return tenancy()->initialized
+ ? 'Tenancy is initialized.'
+ : 'Tenancy is not initialized.';
+ })->middleware(['universal', InitializeTenancyByDomain::class]);
- Route::get('/foo', function () {
- return tenancy()->initialized
- ? 'Tenancy is initialized.'
- : 'Tenancy is not initialized.';
- })->middleware(['universal', InitializeTenancyByDomain::class]);
+ $this->get('http://localhost/foo')
+ ->assertSuccessful()
+ ->assertSee('Tenancy is not initialized.');
- $this->get('http://localhost/foo')
- ->assertSuccessful()
- ->assertSee('Tenancy is not initialized.');
+ $tenant = Tenant::create([
+ 'id' => 'acme',
+ ]);
+ $tenant->domains()->create([
+ 'domain' => 'acme.localhost',
+ ]);
- $tenant = Tenant::create([
- 'id' => 'acme',
- ]);
- $tenant->domains()->create([
- 'domain' => 'acme.localhost',
- ]);
+ $this->get('http://acme.localhost/foo')
+ ->assertSuccessful()
+ ->assertSee('Tenancy is initialized.');
+});
- $this->get('http://acme.localhost/foo')
- ->assertSuccessful()
- ->assertSee('Tenancy is initialized.');
- }
+test('making one route universal doesnt make all routes universal', function () {
+ Route::get('/bar', function () {
+ return tenant('id');
+ })->middleware(InitializeTenancyByDomain::class);
- /** @test */
- public function making_one_route_universal_doesnt_make_all_routes_universal()
- {
- Route::get('/bar', function () {
- return tenant('id');
- })->middleware(InitializeTenancyByDomain::class);
+ Route::middlewareGroup('universal', []);
+ config(['tenancy.features' => [UniversalRoutes::class]]);
- $this->a_route_can_work_in_both_central_and_tenant_context();
- tenancy()->end();
+ Route::get('/foo', function () {
+ return tenancy()->initialized
+ ? 'Tenancy is initialized.'
+ : 'Tenancy is not initialized.';
+ })->middleware(['universal', InitializeTenancyByDomain::class]);
- $this->get('http://localhost/bar')
- ->assertStatus(500);
+ $this->get('http://localhost/foo')
+ ->assertSuccessful()
+ ->assertSee('Tenancy is not initialized.');
- $this->get('http://acme.localhost/bar')
- ->assertSuccessful()
- ->assertSee('acme');
- }
-}
+ $tenant = Tenant::create([
+ 'id' => 'acme',
+ ]);
+ $tenant->domains()->create([
+ 'domain' => 'acme.localhost',
+ ]);
+
+ $this->get('http://acme.localhost/foo')
+ ->assertSuccessful()
+ ->assertSee('Tenancy is initialized.');
+
+ tenancy()->end();
+
+ $this->get('http://localhost/bar')
+ ->assertStatus(500);
+
+ $this->get('http://acme.localhost/bar')
+ ->assertSuccessful()
+ ->assertSee('acme');
+});
From 2f3d4b99539e2965b3a344fb4d2e2fd3265bf059 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Fri, 22 Jul 2022 19:48:56 +0200
Subject: [PATCH 47/51] Allow pest plugin
---
composer.json | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/composer.json b/composer.json
index 3143175a..68f621bb 100644
--- a/composer.json
+++ b/composer.json
@@ -61,5 +61,10 @@
"test": "PHP_VERSION=8.0.11 ./test"
},
"minimum-stability": "dev",
- "prefer-stable": true
+ "prefer-stable": true,
+ "config": {
+ "allow-plugins": {
+ "pestphp/pest-plugin": true
+ }
+ }
}
From 05f2a828a19dea5cfd3e74c826ed49393620964a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Fri, 22 Jul 2022 22:29:56 +0200
Subject: [PATCH 48/51] Better M1 DX
---
CONTRIBUTING.md | 17 ++++-------------
composer.json | 26 ++++++++++++++------------
docker-compose-m1.override.yml | 7 +++++++
3 files changed, 25 insertions(+), 25 deletions(-)
create mode 100644 docker-compose-m1.override.yml
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a5a6ec3f..12e5e55b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,23 +2,14 @@
## Code style
-StyleCI will flag code style violations in your pull requests.
+php-cs-fixer will fix code style violations in your pull requests.
## Running tests
-Run `docker-compose up -d` to start the containers. Then run `./test` to run the tests.
+Run `composer docker-up` to start the containers. Then run `composer test` to run the tests.
-When you're done testing, run `docker-compose down` to shut down the containers.
+When you're done testing, run `composer docker-down` to shut down the containers.
### Docker on M1
-You can add:
-```yaml
-services:
- mysql:
- platform: linux/amd64
- mysql2:
- platform: linux/amd64
-```
-
-to `docker-compose.override.yml` to make `docker-compose up-d` work on M1.
+Run `composer docker-m1` to symlink `docker-compose-m1.override.yml` to `docker-compose.override.yml`. This will reconfigure a few services in the docker compose config to be compatible with M1.
diff --git a/composer.json b/composer.json
index 68f621bb..8aca9ded 100644
--- a/composer.json
+++ b/composer.json
@@ -11,21 +11,22 @@
"authors": [
{
"name": "Samuel Å tancl",
- "email": "samuel.stancl@gmail.com"
+ "email": "samuel@archte.ch"
}
],
"require": {
+ "php": "^8.1",
"ext-json": "*",
- "illuminate/support": "^6.0|^7.0|^8.0|^9.0",
+ "illuminate/support": "^9.0",
"facade/ignition-contracts": "^1.0",
- "ramsey/uuid": "^3.7|^4.0",
- "stancl/jobpipeline": "dev-master",
- "stancl/virtualcolumn": "dev-master"
+ "ramsey/uuid": "^4.0",
+ "stancl/jobpipeline": "^1.6",
+ "stancl/virtualcolumn": "^1.2"
},
"require-dev": {
- "laravel/framework": "^6.0|^7.0|^8.0|^9.0",
- "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0",
- "league/flysystem-aws-s3-v3": "^1.0|^3.0",
+ "laravel/framework": "^9.0",
+ "orchestra/testbench": "^7.0",
+ "league/flysystem-aws-s3-v3": "^3.0",
"doctrine/dbal": "^2.10",
"spatie/valuestore": "^1.2.5",
"pestphp/pest": "^1.21"
@@ -55,10 +56,11 @@
}
},
"scripts": {
- "docker-up": "PHP_VERSION=8.0.11 docker-compose up -d",
- "docker-down": "PHP_VERSION=8.0.11 docker-compose down",
- "docker-rebuild": "PHP_VERSION=8.0.11 docker-compose up -d --no-deps --build",
- "test": "PHP_VERSION=8.0.11 ./test"
+ "docker-up": "PHP_VERSION=8.1 docker-compose up -d",
+ "docker-down": "PHP_VERSION=8.1 docker-compose down",
+ "docker-rebuild": "PHP_VERSION=8.1 docker-compose up -d --no-deps --build",
+ "docker-m1": "ln -s docker-compose-m1.override.yml docker-compose.override.yml",
+ "test": "PHP_VERSION=8.1 ./test"
},
"minimum-stability": "dev",
"prefer-stable": true,
diff --git a/docker-compose-m1.override.yml b/docker-compose-m1.override.yml
new file mode 100644
index 00000000..32e163e6
--- /dev/null
+++ b/docker-compose-m1.override.yml
@@ -0,0 +1,7 @@
+services:
+ mysql:
+ platform: linux/amd64
+ mysql2:
+ platform: linux/amd64
+ mssql:
+ image: mcr.microsoft.com/azure-sql-edge
From f9c9d8615f5c073502d31626929d3c2bfa22e7eb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Samuel=20=C5=A0tancl?=
Date: Sat, 23 Jul 2022 01:16:50 +0200
Subject: [PATCH 49/51] Refactor tests to use pest() helper
---
tests/AutomaticModeTest.php | 2 +-
tests/BootstrapperTest.php | 4 +-
tests/CacheManagerTest.php | 10 ++--
tests/CachedTenantResolverTest.php | 8 +--
...edDomainAndSubdomainIdentificationTest.php | 4 +-
tests/CommandsTest.php | 8 +--
tests/DatabaseUsersTest.php | 2 +-
tests/DomainTest.php | 8 +--
tests/EventListenerTest.php | 8 +--
tests/Features/RedirectTest.php | 2 +-
tests/Features/TenantConfigTest.php | 6 +--
tests/MaintenanceModeTest.php | 6 +--
tests/PathIdentificationTest.php | 14 +++---
tests/Pest.php | 9 +++-
tests/QueueTest.php | 50 ++++++++-----------
tests/ResourceSyncingTest.php | 12 ++---
tests/ScopeSessionsTest.php | 10 ++--
tests/SingleDatabaseTenancyTest.php | 6 +--
tests/SubdomainTest.php | 12 ++---
tests/TenantAssetTest.php | 6 +--
tests/TenantAwareCommandTest.php | 6 +--
tests/TenantDatabaseManagerTest.php | 16 +++---
tests/TenantModelTest.php | 2 +-
tests/TenantUserImpersonationTest.php | 30 +++++------
tests/TestCase.php | 2 +-
tests/UniversalRouteTest.php | 12 ++---
26 files changed, 128 insertions(+), 127 deletions(-)
diff --git a/tests/AutomaticModeTest.php b/tests/AutomaticModeTest.php
index 7b5d5ded..ab484ccf 100644
--- a/tests/AutomaticModeTest.php
+++ b/tests/AutomaticModeTest.php
@@ -62,7 +62,7 @@ test('central helper runs callbacks in the central state', function () {
test('central helper returns the value from the callback', function () {
tenancy()->initialize(Tenant::create());
- $this->assertSame('foo', tenancy()->central(function () {
+ pest()->assertSame('foo', tenancy()->central(function () {
return 'foo';
}));
});
diff --git a/tests/BootstrapperTest.php b/tests/BootstrapperTest.php
index 929c4e47..8f5407bc 100644
--- a/tests/BootstrapperTest.php
+++ b/tests/BootstrapperTest.php
@@ -42,7 +42,7 @@ test('database data is separated', function () {
$tenant1 = Tenant::create();
$tenant2 = Tenant::create();
- $this->artisan('tenants:migrate');
+ pest()->artisan('tenants:migrate');
tenancy()->initialize($tenant1);
@@ -175,7 +175,7 @@ test('filesystem data is separated', function () {
// Check that disk prefixes respect the root_override logic
expect(getDiskPrefix('local'))->toBe($expected_storage_path . '/app/');
expect(getDiskPrefix('public'))->toBe($expected_storage_path . '/app/public/');
- $this->assertSame('tenant' . tenant('id') . '/', getDiskPrefix('s3'), '/');
+ pest()->assertSame('tenant' . tenant('id') . '/', getDiskPrefix('s3'), '/');
// Check suffixing logic
$new_storage_path = storage_path();
diff --git a/tests/CacheManagerTest.php b/tests/CacheManagerTest.php
index 7b34a7df..03580fe1 100644
--- a/tests/CacheManagerTest.php
+++ b/tests/CacheManagerTest.php
@@ -19,7 +19,7 @@ beforeEach(function () {
test('default tag is automatically applied', function () {
tenancy()->initialize(Tenant::create());
- $this->assertArrayIsSubset([config('tenancy.cache.tag_base') . tenant('id')], cache()->tags('foo')->getTags()->getNames());
+ pest()->assertArrayIsSubset([config('tenancy.cache.tag_base') . tenant('id')], cache()->tags('foo')->getTags()->getNames());
});
test('tags are merged when array is passed', function () {
@@ -39,14 +39,14 @@ test('tags are merged when string is passed', function () {
test('exception is thrown when zero arguments are passed to tags method', function () {
tenancy()->initialize(Tenant::create());
- $this->expectException(\Exception::class);
+ pest()->expectException(\Exception::class);
cache()->tags();
});
test('exception is thrown when more than one argument is passed to tags method', function () {
tenancy()->initialize(Tenant::create());
- $this->expectException(\Exception::class);
+ pest()->expectException(\Exception::class);
cache()->tags(1, 2);
});
@@ -60,7 +60,7 @@ test('tags separate cache well enough', function () {
$tenant2 = Tenant::create();
tenancy()->initialize($tenant2);
- $this->assertNotSame('bar', cache()->get('foo'));
+ pest()->assertNotSame('bar', cache()->get('foo'));
cache()->put('foo', 'xyz', 1);
expect(cache()->get('foo'))->toBe('xyz');
@@ -76,7 +76,7 @@ test('invoking the cache helper works', function () {
$tenant2 = Tenant::create();
tenancy()->initialize($tenant2);
- $this->assertNotSame('bar', cache('foo'));
+ pest()->assertNotSame('bar', cache('foo'));
cache(['foo' => 'xyz'], 1);
expect(cache('foo'))->toBe('xyz');
diff --git a/tests/CachedTenantResolverTest.php b/tests/CachedTenantResolverTest.php
index dad0c010..d71375be 100644
--- a/tests/CachedTenantResolverTest.php
+++ b/tests/CachedTenantResolverTest.php
@@ -32,7 +32,7 @@ test('the underlying resolver is not touched when using the cached resolver', fu
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
DB::flushQueryLog();
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
- $this->assertNotEmpty(DB::getQueryLog()); // not empty
+ pest()->assertNotEmpty(DB::getQueryLog()); // not empty
DomainTenantResolver::$shouldCache = true;
@@ -63,7 +63,7 @@ test('cache is invalidated when the tenant is updated', function () {
DB::flushQueryLog();
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
- $this->assertNotEmpty(DB::getQueryLog()); // not empty
+ pest()->assertNotEmpty(DB::getQueryLog()); // not empty
});
test('cache is invalidated when a tenants domain is changed', function () {
@@ -87,9 +87,9 @@ test('cache is invalidated when a tenants domain is changed', function () {
DB::flushQueryLog();
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
- $this->assertNotEmpty(DB::getQueryLog()); // not empty
+ pest()->assertNotEmpty(DB::getQueryLog()); // not empty
DB::flushQueryLog();
expect($tenant->is(app(DomainTenantResolver::class)->resolve('bar')))->toBeTrue();
- $this->assertNotEmpty(DB::getQueryLog()); // not empty
+ pest()->assertNotEmpty(DB::getQueryLog()); // not empty
});
diff --git a/tests/CombinedDomainAndSubdomainIdentificationTest.php b/tests/CombinedDomainAndSubdomainIdentificationTest.php
index db01ef99..4e3c190b 100644
--- a/tests/CombinedDomainAndSubdomainIdentificationTest.php
+++ b/tests/CombinedDomainAndSubdomainIdentificationTest.php
@@ -32,7 +32,7 @@ test('tenant can be identified by subdomain', function () {
expect(tenancy()->initialized)->toBeFalse();
- $this
+ pest()
->get('http://foo.localhost/foo/abc/xyz')
->assertSee('abc + xyz');
@@ -53,7 +53,7 @@ test('tenant can be identified by domain', function () {
expect(tenancy()->initialized)->toBeFalse();
- $this
+ pest()
->get('http://foobar.localhost/foo/abc/xyz')
->assertSee('abc + xyz');
diff --git a/tests/CommandsTest.php b/tests/CommandsTest.php
index 8ad67538..5172d752 100644
--- a/tests/CommandsTest.php
+++ b/tests/CommandsTest.php
@@ -46,7 +46,7 @@ test('migrate command doesnt change the db connection', function () {
expect(Schema::hasTable('users'))->toBeFalse();
expect($new_connection_name)->toEqual($old_connection_name);
- $this->assertNotEquals('tenant', $new_connection_name);
+ pest()->assertNotEquals('tenant', $new_connection_name);
});
test('migrate command works without options', function () {
@@ -140,7 +140,7 @@ test('install command works', function () {
mkdir($dir, 0777, true);
}
- $this->artisan('tenancy:install');
+ pest()->artisan('tenancy:install');
expect(base_path('routes/tenant.php'))->toBeFile();
expect(base_path('config/tenancy.php'))->toBeFile();
expect(app_path('Providers/TenancyServiceProvider.php'))->toBeFile();
@@ -174,7 +174,7 @@ test('run command with array of tenants works', function () {
$tenantId2 = Tenant::create()->getTenantKey();
Artisan::call('tenants:migrate-fresh');
- $this->artisan("tenants:run foo --tenants=$tenantId1 --tenants=$tenantId2 --argument='a=foo' --option='b=bar' --option='c=xyz'")
+ pest()->artisan("tenants:run foo --tenants=$tenantId1 --tenants=$tenantId2 --argument='a=foo' --option='b=bar' --option='c=xyz'")
->expectsOutput('Tenant: ' . $tenantId1)
->expectsOutput('Tenant: ' . $tenantId2);
});
@@ -186,7 +186,7 @@ function runCommandWorks(): void
Artisan::call('tenants:migrate', ['--tenants' => [$id]]);
- test()->artisan("tenants:run foo --tenants=$id --argument='a=foo' --option='b=bar' --option='c=xyz'")
+ pest()->artisan("tenants:run foo --tenants=$id --argument='a=foo' --option='b=bar' --option='c=xyz'")
->expectsOutput("User's name is Test command")
->expectsOutput('foo')
->expectsOutput('xyz');
diff --git a/tests/DatabaseUsersTest.php b/tests/DatabaseUsersTest.php
index 93ac7ec3..2635c167 100644
--- a/tests/DatabaseUsersTest.php
+++ b/tests/DatabaseUsersTest.php
@@ -56,7 +56,7 @@ test('a tenants database cannot be created when the user already exists', functi
expect($manager->userExists($tenant->database()->getUsername()))->toBeTrue();
expect($manager->databaseExists($tenant->database()->getName()))->toBeTrue();
- $this->expectException(TenantDatabaseUserAlreadyExistsException::class);
+ pest()->expectException(TenantDatabaseUserAlreadyExistsException::class);
Event::fake([DatabaseCreated::class]);
$tenant2 = Tenant::create([
diff --git a/tests/DomainTest.php b/tests/DomainTest.php
index 907681ff..006faef9 100644
--- a/tests/DomainTest.php
+++ b/tests/DomainTest.php
@@ -47,14 +47,14 @@ test('a domain can belong to only one tenant', function () {
$tenant2 = DomainTenant::create();
- $this->expectException(DomainOccupiedByOtherTenantException::class);
+ pest()->expectException(DomainOccupiedByOtherTenantException::class);
$tenant2->domains()->create([
'domain' => 'foo.localhost',
]);
});
test('an exception is thrown if tenant cannot be identified', function () {
- $this->expectException(TenantCouldNotBeIdentifiedOnDomainException::class);
+ pest()->expectException(TenantCouldNotBeIdentifiedOnDomainException::class);
app(DomainTenantResolver::class)->resolve('foo.localhost');
});
@@ -70,7 +70,7 @@ test('tenant can be identified by domain', function () {
expect(tenancy()->initialized)->toBeFalse();
- $this
+ pest()
->get('http://foo.localhost/foo/abc/xyz')
->assertSee('abc + xyz');
@@ -83,7 +83,7 @@ test('onfail logic can be customized', function () {
return 'foo';
};
- $this
+ pest()
->get('http://foo.localhost/foo/abc/xyz')
->assertSee('foo');
});
diff --git a/tests/EventListenerTest.php b/tests/EventListenerTest.php
index 66dcedc8..85203f07 100644
--- a/tests/EventListenerTest.php
+++ b/tests/EventListenerTest.php
@@ -81,7 +81,7 @@ test('ing events can be used to cancel db creation', function () {
$tenant = Tenant::create();
dispatch_now(new CreateDatabase($tenant));
- $this->assertFalse($tenant->database()->manager()->databaseExists(
+ pest()->assertFalse($tenant->database()->manager()->databaseExists(
$tenant->database()->getName()
));
});
@@ -149,7 +149,7 @@ test('individual job pipelines can terminate while leaving others running', func
Tenant::create();
- $this->assertSame([
+ pest()->assertSame([
'P1J1',
'P1J2',
'P2J1', // termminated after this
@@ -163,7 +163,7 @@ test('database is not migrated if creation is disabled', function () {
JobPipeline::make([
CreateDatabase::class,
function () {
- $this->fail("The job pipeline didn't exit.");
+ pest()->fail("The job pipeline didn't exit.");
},
MigrateDatabase::class,
])->send(function (TenantCreated $event) {
@@ -176,7 +176,7 @@ test('database is not migrated if creation is disabled', function () {
'tenancy_db_name' => 'already_created',
]);
- expect($this->hasFailed())->toBeFalse();
+ expect(pest()->hasFailed())->toBeFalse();
});
class FooListener extends QueueableListener
diff --git a/tests/Features/RedirectTest.php b/tests/Features/RedirectTest.php
index 7686867e..7aca2e92 100644
--- a/tests/Features/RedirectTest.php
+++ b/tests/Features/RedirectTest.php
@@ -22,7 +22,7 @@ test('tenant redirect macro replaces only the hostname', function () {
$tenant = Tenant::create();
tenancy()->initialize($tenant);
- $this->get('/redirect')
+ pest()->get('/redirect')
->assertRedirect('http://abcd/foobar');
});
diff --git a/tests/Features/TenantConfigTest.php b/tests/Features/TenantConfigTest.php
index 21c92592..35df35ed 100644
--- a/tests/Features/TenantConfigTest.php
+++ b/tests/Features/TenantConfigTest.php
@@ -37,7 +37,7 @@ test('config is merged and removed', function () {
expect(config('services.paypal'))->toBe(['public' => 'foo', 'private' => 'bar']);
tenancy()->end();
- $this->assertSame([
+ pest()->assertSame([
'public' => null,
'private' => null,
], config('services.paypal'));
@@ -66,14 +66,14 @@ test('the value can be set to multiple config keys', function () {
]);
tenancy()->initialize($tenant);
- $this->assertSame([
+ pest()->assertSame([
'public1' => 'foo',
'public2' => 'foo',
'private' => 'bar',
], config('services.paypal'));
tenancy()->end();
- $this->assertSame([
+ pest()->assertSame([
'public1' => null,
'public2' => null,
'private' => null,
diff --git a/tests/MaintenanceModeTest.php b/tests/MaintenanceModeTest.php
index dace6c51..770dc5f2 100644
--- a/tests/MaintenanceModeTest.php
+++ b/tests/MaintenanceModeTest.php
@@ -19,15 +19,15 @@ test('tenant can be in maintenance mode', function () {
'domain' => 'acme.localhost',
]);
- $this->get('http://acme.localhost/foo')
+ pest()->get('http://acme.localhost/foo')
->assertSuccessful();
tenancy()->end(); // flush stored tenant instance
$tenant->putDownForMaintenance();
- $this->expectException(HttpException::class);
- $this->withoutExceptionHandling()
+ pest()->expectException(HttpException::class);
+ pest()->withoutExceptionHandling()
->get('http://acme.localhost/foo');
});
diff --git a/tests/PathIdentificationTest.php b/tests/PathIdentificationTest.php
index 4cd793d7..bda0cfcb 100644
--- a/tests/PathIdentificationTest.php
+++ b/tests/PathIdentificationTest.php
@@ -34,7 +34,7 @@ test('tenant can be identified by path', function () {
expect(tenancy()->initialized)->toBeFalse();
- $this->get('/acme/foo/abc/xyz');
+ pest()->get('/acme/foo/abc/xyz');
expect(tenancy()->initialized)->toBeTrue();
expect(tenant('id'))->toBe('acme');
@@ -47,7 +47,7 @@ test('route actions dont get the tenant id', function () {
expect(tenancy()->initialized)->toBeFalse();
- $this
+ pest()
->get('/acme/foo/abc/xyz')
->assertContent('abc + xyz');
@@ -56,7 +56,7 @@ test('route actions dont get the tenant id', function () {
});
test('exception is thrown when tenant cannot be identified by path', function () {
- $this->expectException(TenantCouldNotBeIdentifiedByPathException::class);
+ pest()->expectException(TenantCouldNotBeIdentifiedByPathException::class);
$this
->withoutExceptionHandling()
@@ -70,7 +70,7 @@ test('onfail logic can be customized', function () {
return 'foo';
};
- $this
+ pest()
->get('/acme/foo/abc/xyz')
->assertContent('foo');
});
@@ -89,7 +89,7 @@ test('an exception is thrown when the routes first parameter is not tenant', fun
'id' => 'acme',
]);
- $this->expectException(RouteIsMissingTenantParameterException::class);
+ pest()->expectException(RouteIsMissingTenantParameterException::class);
$this
->withoutExceptionHandling()
@@ -112,12 +112,12 @@ test('tenant parameter name can be customized', function () {
'id' => 'acme',
]);
- $this
+ pest()
->get('/acme/bar/abc/xyz')
->assertContent('abc + xyz');
// Parameter for resolver is changed, so the /{tenant}/foo route will no longer work.
- $this->expectException(RouteIsMissingTenantParameterException::class);
+ pest()->expectException(RouteIsMissingTenantParameterException::class);
$this
->withoutExceptionHandling()
diff --git a/tests/Pest.php b/tests/Pest.php
index 9325cf53..d7ca8c22 100644
--- a/tests/Pest.php
+++ b/tests/Pest.php
@@ -1,3 +1,10 @@
in(__DIR__);
+use Stancl\Tenancy\Tests\TestCase;
+
+uses(TestCase::class)->in(__DIR__);
+
+function pest(): TestCase
+{
+ return Pest\TestSuite::getInstance()->test;
+}
diff --git a/tests/QueueTest.php b/tests/QueueTest.php
index 1e662645..938af39f 100644
--- a/tests/QueueTest.php
+++ b/tests/QueueTest.php
@@ -7,7 +7,6 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
-use Illuminate\Support\Str;
use Spatie\Valuestore\Valuestore;
use Illuminate\Support\Facades\DB;
use Stancl\Tenancy\Tests\Etc\User;
@@ -43,7 +42,7 @@ beforeEach(function () {
});
afterEach(function () {
- $this->valuestore->flush();
+ pest()->valuestore->flush();
});
test('tenant id is passed to tenant queues', function () {
@@ -55,7 +54,7 @@ test('tenant id is passed to tenant queues', function () {
Event::fake([JobProcessing::class, JobProcessed::class]);
- dispatch(new TestJob($this->valuestore));
+ dispatch(new TestJob(pest()->valuestore));
Event::assertDispatched(JobProcessing::class, function ($event) {
return $event->job->payload()['tenant_id'] === tenant('id');
@@ -74,7 +73,7 @@ test('tenant id is not passed to central queues', function () {
'central' => true,
]]);
- dispatch(new TestJob($this->valuestore))->onConnection('central');
+ dispatch(new TestJob(pest()->valuestore))->onConnection('central');
Event::assertDispatched(JobProcessing::class, function ($event) {
return ! isset($event->job->payload()['tenant_id']);
@@ -93,21 +92,21 @@ test('tenancy is initialized inside queues', function (bool $shouldEndTenancy) {
$user = User::create(['name' => 'Foo', 'email' => 'foo@bar.com', 'password' => 'secret']);
- $this->valuestore->put('userName', 'Bar');
+ pest()->valuestore->put('userName', 'Bar');
- dispatch(new TestJob($this->valuestore, $user));
+ dispatch(new TestJob(pest()->valuestore, $user));
- expect($this->valuestore->has('tenant_id'))->toBeFalse();
+ expect(pest()->valuestore->has('tenant_id'))->toBeFalse();
if ($shouldEndTenancy) {
tenancy()->end();
}
- $this->artisan('queue:work --once');
+ pest()->artisan('queue:work --once');
expect(DB::connection('central')->table('failed_jobs')->count())->toBe(0);
- expect($this->valuestore->get('tenant_id'))->toBe('The current tenant id is: ' . $tenant->id);
+ expect(pest()->valuestore->get('tenant_id'))->toBe('The current tenant id is: ' . $tenant->id);
$tenant->run(function () use ($user) {
expect($user->fresh()->name)->toBe('Bar');
@@ -115,10 +114,6 @@ test('tenancy is initialized inside queues', function (bool $shouldEndTenancy) {
})->with([true, false]);;
test('tenancy is initialized when retrying jobs', function (bool $shouldEndTenancy) {
- if (! Str::startsWith(app()->version(), '8')) {
- $this->markTestSkipped('queue:retry tenancy is only supported in Laravel 8');
- }
-
withFailedJobs();
withTenantDatabases();
@@ -130,28 +125,28 @@ test('tenancy is initialized when retrying jobs', function (bool $shouldEndTenan
$user = User::create(['name' => 'Foo', 'email' => 'foo@bar.com', 'password' => 'secret']);
- $this->valuestore->put('userName', 'Bar');
- $this->valuestore->put('shouldFail', true);
+ pest()->valuestore->put('userName', 'Bar');
+ pest()->valuestore->put('shouldFail', true);
- dispatch(new TestJob($this->valuestore, $user));
+ dispatch(new TestJob(pest()->valuestore, $user));
- expect($this->valuestore->has('tenant_id'))->toBeFalse();
+ expect(pest()->valuestore->has('tenant_id'))->toBeFalse();
if ($shouldEndTenancy) {
tenancy()->end();
}
- $this->artisan('queue:work --once');
+ pest()->artisan('queue:work --once');
expect(DB::connection('central')->table('failed_jobs')->count())->toBe(1);
- expect($this->valuestore->get('tenant_id'))->toBeNull(); // job failed
+ expect(pest()->valuestore->get('tenant_id'))->toBeNull(); // job failed
- $this->artisan('queue:retry all');
- $this->artisan('queue:work --once');
+ pest()->artisan('queue:retry all');
+ pest()->artisan('queue:work --once');
expect(DB::connection('central')->table('failed_jobs')->count())->toBe(0);
- expect($this->valuestore->get('tenant_id'))->toBe('The current tenant id is: ' . $tenant->id); // job succeeded
+ expect(pest()->valuestore->get('tenant_id'))->toBe('The current tenant id is: ' . $tenant->id); // job succeeded
$tenant->run(function () use ($user) {
expect($user->fresh()->name)->toBe('Bar');
@@ -165,7 +160,7 @@ test('the tenant used by the job doesnt change when the current tenant changes',
tenancy()->initialize($tenant1);
- dispatch(new TestJob($this->valuestore));
+ dispatch(new TestJob(pest()->valuestore));
$tenant2 = Tenant::create([
'id' => 'foobar',
@@ -173,10 +168,10 @@ test('the tenant used by the job doesnt change when the current tenant changes',
tenancy()->initialize($tenant2);
- expect($this->valuestore->has('tenant_id'))->toBeFalse();
- $this->artisan('queue:work --once');
+ expect(pest()->valuestore->has('tenant_id'))->toBeFalse();
+ pest()->artisan('queue:work --once');
- expect($this->valuestore->get('tenant_id'))->toBe('The current tenant id is: acme');
+ expect(pest()->valuestore->get('tenant_id'))->toBe('The current tenant id is: acme');
});
function createValueStore(): void
@@ -192,7 +187,7 @@ function createValueStore(): void
file_put_contents($valueStorePath, '');
}
- test()->valuestore = Valuestore::make($valueStorePath)->flush();
+ pest()->valuestore = Valuestore::make($valueStorePath)->flush();
}
function withFailedJobs()
@@ -262,4 +257,3 @@ class TestJob implements ShouldQueue
}
}
}
-
diff --git a/tests/ResourceSyncingTest.php b/tests/ResourceSyncingTest.php
index 99b41f23..806e8706 100644
--- a/tests/ResourceSyncingTest.php
+++ b/tests/ResourceSyncingTest.php
@@ -47,7 +47,7 @@ beforeEach(function () {
UpdateSyncedResource::$shouldQueue = false; // global state cleanup
Event::listen(SyncedResourceSaved::class, UpdateSyncedResource::class);
- test()->artisan('migrate', [
+ pest()->artisan('migrate', [
'--path' => [
__DIR__ . '/Etc/synced_resource_migrations',
__DIR__ . '/Etc/synced_resource_migrations/users',
@@ -104,7 +104,7 @@ test('only the synced columns are updated in the central db', function () {
]);
// Assert new values
- $this->assertEquals([
+ pest()->assertEquals([
'id' => 1,
'global_id' => 'acme',
'name' => 'John Foo',
@@ -116,7 +116,7 @@ test('only the synced columns are updated in the central db', function () {
tenancy()->end();
// Assert changes bubbled up
- $this->assertEquals([
+ pest()->assertEquals([
'id' => 1,
'global_id' => 'acme',
'name' => 'John Foo', // synced
@@ -136,7 +136,7 @@ test('trying to update synced resources from central context using tenant models
tenancy()->end();
expect(tenancy()->initialized)->toBeFalse();
- $this->expectException(ModelNotSyncMasterException::class);
+ pest()->expectException(ModelNotSyncMasterException::class);
ResourceUser::first()->update(['role' => 'foobar']);
});
@@ -338,7 +338,7 @@ test('global id is generated using id generator when its not supplied', function
'role' => 'employee',
]);
- $this->assertNotNull($user->global_id);
+ pest()->assertNotNull($user->global_id);
});
test('when the resource doesnt exist in the tenant db non synced columns will cascade too', function () {
@@ -539,7 +539,7 @@ function creatingResourceInTenantDatabaseCreatesAndMapInCentralDatabase()
function migrateTenantsResource()
{
- test()->artisan('tenants:migrate', [
+ pest()->artisan('tenants:migrate', [
'--path' => __DIR__ . '/Etc/synced_resource_migrations/users',
'--realpath' => true,
])->assertExitCode(0);
diff --git a/tests/ScopeSessionsTest.php b/tests/ScopeSessionsTest.php
index b1b6a05e..27fa911f 100644
--- a/tests/ScopeSessionsTest.php
+++ b/tests/ScopeSessionsTest.php
@@ -35,7 +35,7 @@ test('tenant id is auto added to session if its missing', function () {
'id' => 'acme',
]);
- $this->get('http://acme.localhost/foo')
+ pest()->get('http://acme.localhost/foo')
->assertSessionHas(ScopeSessions::$tenantIdKey, 'acme');
});
@@ -44,12 +44,12 @@ test('changing tenant id in session will abort the request', function () {
'id' => 'acme',
]);
- $this->get('http://acme.localhost/foo')
+ pest()->get('http://acme.localhost/foo')
->assertSuccessful();
session()->put(ScopeSessions::$tenantIdKey, 'foobar');
- $this->get('http://acme.localhost/foo')
+ pest()->get('http://acme.localhost/foo')
->assertStatus(403);
});
@@ -62,6 +62,6 @@ test('an exception is thrown when the middleware is executed before tenancy is i
'id' => 'acme',
]);
- $this->expectException(TenancyNotInitializedException::class);
- $this->withoutExceptionHandling()->get('http://acme.localhost/bar');
+ pest()->expectException(TenancyNotInitializedException::class);
+ pest()->withoutExceptionHandling()->get('http://acme.localhost/bar');
});
diff --git a/tests/SingleDatabaseTenancyTest.php b/tests/SingleDatabaseTenancyTest.php
index 83807d14..34b12383 100644
--- a/tests/SingleDatabaseTenancyTest.php
+++ b/tests/SingleDatabaseTenancyTest.php
@@ -138,7 +138,7 @@ test('tenant id and relationship is auto added when creating primary resources i
});
test('tenant id is not auto added when creating primary resources in central context', function () {
- $this->expectException(QueryException::class);
+ pest()->expectException(QueryException::class);
Post::create(['text' => 'Foo']);
});
@@ -212,8 +212,8 @@ test('the model returned by the tenant helper has unique and exists validation r
])->fails();
// Assert that 'unique' and 'exists' aren't scoped by default
- // $this->assertFalse($uniqueFails); // todo get these two assertions to pass. for some reason, the validator is passing for both 'unique' and 'exists'
- // $this->assertTrue($existsFails); // todo get these two assertions to pass. for some reason, the validator is passing for both 'unique' and 'exists'
+ // pest()->assertFalse($uniqueFails); // todo get these two assertions to pass. for some reason, the validator is passing for both 'unique' and 'exists'
+ // pest()->assertTrue($existsFails); // todo get these two assertions to pass. for some reason, the validator is passing for both 'unique' and 'exists'
$uniqueFails = Validator::make($data, [
'slug' => tenant()->unique('posts'),
diff --git a/tests/SubdomainTest.php b/tests/SubdomainTest.php
index 6cbe1f05..00096d8c 100644
--- a/tests/SubdomainTest.php
+++ b/tests/SubdomainTest.php
@@ -34,7 +34,7 @@ test('tenant can be identified by subdomain', function () {
expect(tenancy()->initialized)->toBeFalse();
- $this
+ pest()
->get('http://foo.localhost/foo/abc/xyz')
->assertSee('abc + xyz');
@@ -47,13 +47,13 @@ test('onfail logic can be customized', function () {
return 'foo';
};
- $this
+ pest()
->get('http://foo.localhost/foo/abc/xyz')
->assertSee('foo');
});
test('localhost is not a valid subdomain', function () {
- $this->expectException(NotASubdomainException::class);
+ pest()->expectException(NotASubdomainException::class);
$this
->withoutExceptionHandling()
@@ -61,7 +61,7 @@ test('localhost is not a valid subdomain', function () {
});
test('ip address is not a valid subdomain', function () {
- $this->expectException(NotASubdomainException::class);
+ pest()->expectException(NotASubdomainException::class);
$this
->withoutExceptionHandling()
@@ -99,7 +99,7 @@ test('we cant use a subdomain that doesnt belong to our central domains', functi
'domain' => 'foo',
]);
- $this->expectException(NotASubdomainException::class);
+ pest()->expectException(NotASubdomainException::class);
$this
->withoutExceptionHandling()
@@ -119,7 +119,7 @@ test('central domain is not a subdomain', function () {
'domain' => 'acme',
]);
- $this->expectException(NotASubdomainException::class);
+ pest()->expectException(NotASubdomainException::class);
$this
->withoutExceptionHandling()
diff --git a/tests/TenantAssetTest.php b/tests/TenantAssetTest.php
index 93a0d3b3..2c5000f1 100644
--- a/tests/TenantAssetTest.php
+++ b/tests/TenantAssetTest.php
@@ -32,14 +32,14 @@ test('asset can be accessed using the url returned by the tenant asset helper',
$tenant = Tenant::create();
tenancy()->initialize($tenant);
- $filename = 'testfile' . $this->randomString(10);
+ $filename = 'testfile' . pest()->randomString(10);
Storage::disk('public')->put($filename, 'bar');
$path = storage_path("app/public/$filename");
// response()->file() returns BinaryFileResponse whose content is
// inaccessible via getContent, so ->assertSee() can't be used
expect($path)->toBeFile();
- $response = $this->get(tenant_asset($filename), [
+ $response = pest()->get(tenant_asset($filename), [
'X-Tenant' => $tenant->id,
]);
@@ -99,7 +99,7 @@ function getEnvironmentSetUp($app)
$app->booted(function () {
if (file_exists(base_path('routes/tenant.php'))) {
Route::middleware(['web'])
- ->namespace(test()->app['config']['tenancy.tenant_route_namespace'] ?? 'App\Http\Controllers')
+ ->namespace(pest()->app['config']['tenancy.tenant_route_namespace'] ?? 'App\Http\Controllers')
->group(base_path('routes/tenant.php'));
}
});
diff --git a/tests/TenantAwareCommandTest.php b/tests/TenantAwareCommandTest.php
index 1332ccfd..fe49685e 100644
--- a/tests/TenantAwareCommandTest.php
+++ b/tests/TenantAwareCommandTest.php
@@ -13,14 +13,14 @@ test('commands run globally are tenant aware and return valid exit code', functi
'--tenants' => [$tenant1['id'], $tenant2['id']],
]);
- $this->artisan('user:add')
+ pest()->artisan('user:add')
->assertExitCode(0);
tenancy()->initialize($tenant1);
- $this->assertNotEmpty(DB::table('users')->get());
+ pest()->assertNotEmpty(DB::table('users')->get());
tenancy()->end();
tenancy()->initialize($tenant2);
- $this->assertNotEmpty(DB::table('users')->get());
+ pest()->assertNotEmpty(DB::table('users')->get());
tenancy()->end();
});
diff --git a/tests/TenantDatabaseManagerTest.php b/tests/TenantDatabaseManagerTest.php
index 5a470972..bc09e888 100644
--- a/tests/TenantDatabaseManagerTest.php
+++ b/tests/TenantDatabaseManagerTest.php
@@ -33,7 +33,7 @@ test('databases can be created and deleted', function ($driver, $databaseManager
"tenancy.database.managers.$driver" => $databaseManager,
]);
- $name = 'db' . $this->randomString();
+ $name = 'db' . pest()->randomString();
$manager = app($databaseManager);
$manager->setConnection($driver);
@@ -57,7 +57,7 @@ test('dbs can be created when another driver is used for the central db', functi
return $event->tenant;
})->toListener());
- $database = 'db' . $this->randomString();
+ $database = 'db' . pest()->randomString();
$mysqlmanager = app(MySQLDatabaseManager::class);
$mysqlmanager->setConnection('mysql');
@@ -73,7 +73,7 @@ test('dbs can be created when another driver is used for the central db', functi
$postgresManager = app(PostgreSQLDatabaseManager::class);
$postgresManager->setConnection('pgsql');
- $database = 'db' . $this->randomString();
+ $database = 'db' . pest()->randomString();
expect($postgresManager->databaseExists($database))->toBeFalse();
Tenant::create([
@@ -101,14 +101,14 @@ test('the tenant connection is fully removed', function () {
$tenant = Tenant::create();
expect(array_keys(app('db')->getConnections()))->toBe(['central']);
- $this->assertArrayNotHasKey('tenant', config('database.connections'));
+ pest()->assertArrayNotHasKey('tenant', config('database.connections'));
tenancy()->initialize($tenant);
createUsersTable();
expect(array_keys(app('db')->getConnections()))->toBe(['central', 'tenant']);
- $this->assertArrayHasKey('tenant', config('database.connections'));
+ pest()->assertArrayHasKey('tenant', config('database.connections'));
tenancy()->end();
@@ -154,7 +154,7 @@ test('schema manager uses schema to separate tenant dbs', function () {
]);
tenancy()->initialize($tenant);
- $schemaConfig = version_compare(app()->version(), '9.0', '>=') ?
+ $schemaConfig = version_compare(app()->version(), '9.0', '>=') ?
config('database.connections.' . config('database.default') . '.search_path') :
config('database.connections.' . config('database.default') . '.schema');
@@ -175,7 +175,7 @@ test('a tenants database cannot be created when the database already exists', fu
$manager = $tenant->database()->manager();
expect($manager->databaseExists($tenant->database()->getName()))->toBeTrue();
- $this->expectException(TenantDatabaseAlreadyExistsException::class);
+ pest()->expectException(TenantDatabaseAlreadyExistsException::class);
$tenant2 = Tenant::create([
'tenancy_db_name' => $name,
]);
@@ -225,7 +225,7 @@ test('tenant database can be created on a foreign server', function () {
});
test('path used by sqlite manager can be customized', function () {
- $this->markTestIncomplete();
+ pest()->markTestIncomplete();
});
// Datasets
diff --git a/tests/TenantModelTest.php b/tests/TenantModelTest.php
index 346a208e..d50c9b6b 100644
--- a/tests/TenantModelTest.php
+++ b/tests/TenantModelTest.php
@@ -50,7 +50,7 @@ test('id is generated when no id is supplied', function () {
$tenant = Tenant::create();
- $this->assertNotNull($tenant->id);
+ pest()->assertNotNull($tenant->id);
});
test('autoincrement ids are supported', function () {
diff --git a/tests/TenantUserImpersonationTest.php b/tests/TenantUserImpersonationTest.php
index bfbe0851..65aa380d 100644
--- a/tests/TenantUserImpersonationTest.php
+++ b/tests/TenantUserImpersonationTest.php
@@ -25,7 +25,7 @@ use Stancl\Tenancy\Tests\Etc\Tenant;
use Illuminate\Foundation\Auth\User as Authenticable;
beforeEach(function () {
- $this->artisan('migrate', [
+ pest()->artisan('migrate', [
'--path' => __DIR__ . '/../assets/impersonation-migrations',
'--realpath' => true,
])->assertExitCode(0);
@@ -69,16 +69,16 @@ test('tenant user can be impersonated on a tenant domain', function () {
});
// We try to visit the dashboard directly, before impersonating the user.
- $this->get('http://foo.localhost/dashboard')
+ pest()->get('http://foo.localhost/dashboard')
->assertRedirect('http://foo.localhost/login');
// We impersonate the user
$token = tenancy()->impersonate($tenant, $user->id, '/dashboard');
- $this->get('http://foo.localhost/impersonate/' . $token->token)
+ pest()->get('http://foo.localhost/impersonate/' . $token->token)
->assertRedirect('http://foo.localhost/dashboard');
// Now we try to visit the dashboard directly, after impersonating the user.
- $this->get('http://foo.localhost/dashboard')
+ pest()->get('http://foo.localhost/dashboard')
->assertSuccessful()
->assertSee('You are logged in as Joe');
});
@@ -102,16 +102,16 @@ test('tenant user can be impersonated on a tenant path', function () {
});
// We try to visit the dashboard directly, before impersonating the user.
- $this->get('/acme/dashboard')
+ pest()->get('/acme/dashboard')
->assertRedirect('/login');
// We impersonate the user
$token = tenancy()->impersonate($tenant, $user->id, '/acme/dashboard');
- $this->get('/acme/impersonate/' . $token->token)
+ pest()->get('/acme/impersonate/' . $token->token)
->assertRedirect('/acme/dashboard');
// Now we try to visit the dashboard directly, after impersonating the user.
- $this->get('/acme/dashboard')
+ pest()->get('/acme/dashboard')
->assertSuccessful()
->assertSee('You are logged in as Joe');
});
@@ -138,7 +138,7 @@ test('tokens have a limited ttl', function () {
'created_at' => Carbon::now()->subtract(CarbonInterval::make('100s')),
]);
- $this->followingRedirects()
+ pest()->followingRedirects()
->get('http://foo.localhost/impersonate/' . $token->token)
->assertStatus(403);
});
@@ -162,9 +162,9 @@ test('tokens are deleted after use', function () {
// We impersonate the user
$token = tenancy()->impersonate($tenant, $user->id, '/dashboard');
- $this->assertNotNull(ImpersonationToken::find($token->token));
+ pest()->assertNotNull(ImpersonationToken::find($token->token));
- $this->followingRedirects()
+ pest()->followingRedirects()
->get('http://foo.localhost/impersonate/' . $token->token)
->assertSuccessful()
->assertSee('You are logged in as Joe');
@@ -204,16 +204,16 @@ test('impersonation works with multiple models and guards', function () {
});
// We try to visit the dashboard directly, before impersonating the user.
- $this->get('http://foo.localhost/dashboard')
+ pest()->get('http://foo.localhost/dashboard')
->assertRedirect('http://foo.localhost/login');
// We impersonate the user
$token = tenancy()->impersonate($tenant, $user->id, '/dashboard', 'another');
- $this->get('http://foo.localhost/impersonate/' . $token->token)
+ pest()->get('http://foo.localhost/impersonate/' . $token->token)
->assertRedirect('http://foo.localhost/dashboard');
// Now we try to visit the dashboard directly, after impersonating the user.
- $this->get('http://foo.localhost/dashboard')
+ pest()->get('http://foo.localhost/dashboard')
->assertSuccessful()
->assertSee('You are logged in as Joe');
@@ -225,7 +225,7 @@ test('impersonation works with multiple models and guards', function () {
function migrateTenants()
{
- test()->artisan('tenants:migrate')->assertExitCode(0);
+ pest()->artisan('tenants:migrate')->assertExitCode(0);
}
function makeLoginRoute()
@@ -239,7 +239,7 @@ function getRoutes($loginRoute = true, $authGuard = 'web'): Closure
{
return function () use ($loginRoute, $authGuard) {
if ($loginRoute) {
- test()->makeLoginRoute();
+ makeLoginRoute();
}
Route::get('/dashboard', function () use ($authGuard) {
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 75fe51fd..554aeb8d 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -23,7 +23,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
Redis::connection('cache')->flushdb();
file_put_contents(database_path('central.sqlite'), '');
- $this->artisan('migrate:fresh', [
+ pest()->artisan('migrate:fresh', [
'--force' => true,
'--path' => __DIR__ . '/../assets/migrations',
'--realpath' => true,
diff --git a/tests/UniversalRouteTest.php b/tests/UniversalRouteTest.php
index 04d053cf..20723cca 100644
--- a/tests/UniversalRouteTest.php
+++ b/tests/UniversalRouteTest.php
@@ -21,7 +21,7 @@ test('a route can work in both central and tenant context', function () {
: 'Tenancy is not initialized.';
})->middleware(['universal', InitializeTenancyByDomain::class]);
- $this->get('http://localhost/foo')
+ pest()->get('http://localhost/foo')
->assertSuccessful()
->assertSee('Tenancy is not initialized.');
@@ -32,7 +32,7 @@ test('a route can work in both central and tenant context', function () {
'domain' => 'acme.localhost',
]);
- $this->get('http://acme.localhost/foo')
+ pest()->get('http://acme.localhost/foo')
->assertSuccessful()
->assertSee('Tenancy is initialized.');
});
@@ -51,7 +51,7 @@ test('making one route universal doesnt make all routes universal', function ()
: 'Tenancy is not initialized.';
})->middleware(['universal', InitializeTenancyByDomain::class]);
- $this->get('http://localhost/foo')
+ pest()->get('http://localhost/foo')
->assertSuccessful()
->assertSee('Tenancy is not initialized.');
@@ -62,16 +62,16 @@ test('making one route universal doesnt make all routes universal', function ()
'domain' => 'acme.localhost',
]);
- $this->get('http://acme.localhost/foo')
+ pest()->get('http://acme.localhost/foo')
->assertSuccessful()
->assertSee('Tenancy is initialized.');
tenancy()->end();
- $this->get('http://localhost/bar')
+ pest()->get('http://localhost/bar')
->assertStatus(500);
- $this->get('http://acme.localhost/bar')
+ pest()->get('http://acme.localhost/bar')
->assertSuccessful()
->assertSee('acme');
});
From 233a1222bf72769c28edcb7558fef7bf3ef216a0 Mon Sep 17 00:00:00 2001
From: beezerk23 <84435010+beezerk23@users.noreply.github.com>
Date: Mon, 25 Jul 2022 18:37:52 +0200
Subject: [PATCH 50/51] =?UTF-8?q?feat(UniversalRoutes):=20Stop=20overwriti?=
=?UTF-8?q?ng=20the=20(maybe)=20customized=20onFail=E2=80=A6=20(#679)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat(UniversalRoutes): Stop overwriting the (maybe) customized onFail method and just call it in case of an exception
* throw correct exception when `$originalOnFail()` is null
* Update DomainTest.php
* convert test to pest and renamed
* Update tests/DomainTest.php
Co-authored-by: Samuel Å tancl
Co-authored-by: Abrar Ahmad
Co-authored-by: Samuel Å tancl
---
src/Features/UniversalRoutes.php | 8 +++++++-
tests/DomainTest.php | 12 ++++++++++++
2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/src/Features/UniversalRoutes.php b/src/Features/UniversalRoutes.php
index 6b729962..c73a5304 100644
--- a/src/Features/UniversalRoutes.php
+++ b/src/Features/UniversalRoutes.php
@@ -23,11 +23,17 @@ class UniversalRoutes implements Feature
public function bootstrap(Tenancy $tenancy): void
{
foreach (static::$identificationMiddlewares as $middleware) {
- $middleware::$onFail = function ($exception, $request, $next) {
+ $originalOnFail = $middleware::$onFail;
+
+ $middleware::$onFail = function ($exception, $request, $next) use ($originalOnFail) {
if (static::routeHasMiddleware($request->route(), static::$middlewareGroup)) {
return $next($request);
}
+ if ($originalOnFail) {
+ return $originalOnFail($exception, $request, $next);
+ }
+
throw $exception;
};
}
diff --git a/tests/DomainTest.php b/tests/DomainTest.php
index 006faef9..594270e1 100644
--- a/tests/DomainTest.php
+++ b/tests/DomainTest.php
@@ -8,6 +8,7 @@ use Stancl\Tenancy\Database\Models;
use Stancl\Tenancy\Database\Models\Domain;
use Stancl\Tenancy\Exceptions\DomainOccupiedByOtherTenantException;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
+use Stancl\Tenancy\Features\UniversalRoutes;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
@@ -88,6 +89,17 @@ test('onfail logic can be customized', function () {
->assertSee('foo');
});
+test('throw correct exception when onFail is null and universal routes are enabled', function () {
+ // un-define onFail logic
+ InitializeTenancyByDomain::$onFail = null;
+
+ // Enable UniversalRoute feature
+ Route::middlewareGroup('universal', []);
+ config(['tenancy.features' => [UniversalRoutes::class]]);
+
+ $this->withoutExceptionHandling()->get('http://foo.localhost/foo/abc/xyz');
+})->throws(TenantCouldNotBeIdentifiedOnDomainException::class);;
+
test('domains are always lowercase', function () {
$tenant = DomainTenant::create();
From 29634dda846c41e7dc398197ed4302f4f883c45c Mon Sep 17 00:00:00 2001
From: Abrar Ahmad
Date: Wed, 27 Jul 2022 04:35:55 +0500
Subject: [PATCH 51/51] added `$this->mockConsoleOutput` (#907)
---
tests/BootstrapperTest.php | 2 ++
tests/QueueTest.php | 2 ++
2 files changed, 4 insertions(+)
diff --git a/tests/BootstrapperTest.php b/tests/BootstrapperTest.php
index 8f5407bc..96afbc83 100644
--- a/tests/BootstrapperTest.php
+++ b/tests/BootstrapperTest.php
@@ -23,6 +23,8 @@ use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
beforeEach(function () {
+ $this->mockConsoleOutput = false;
+
Event::listen(
TenantCreated::class,
JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
diff --git a/tests/QueueTest.php b/tests/QueueTest.php
index 938af39f..c1fa24b8 100644
--- a/tests/QueueTest.php
+++ b/tests/QueueTest.php
@@ -27,6 +27,8 @@ use Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper;
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
beforeEach(function () {
+ $this->mockConsoleOutput = false;
+
config([
'tenancy.bootstrappers' => [
QueueTenancyBootstrapper::class,