mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-13 03:14:03 +00:00
Merge branch 'master' into cache-prefix
This commit is contained in:
commit
f8f0e1e5da
32 changed files with 440 additions and 105 deletions
|
|
@ -3,6 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Mail\MailManager;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Stancl\JobPipeline\JobPipeline;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
|
@ -24,6 +25,7 @@ use Stancl\Tenancy\Jobs\RemoveStorageSymlinks;
|
|||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||
use Stancl\Tenancy\Listeners\DeleteTenantStorage;
|
||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||
|
|
@ -331,20 +333,55 @@ test('local storage public urls are generated correctly', function() {
|
|||
expect(File::isDirectory($tenantStoragePath))->toBeFalse();
|
||||
});
|
||||
|
||||
test('MailTenancyBootstrapper maps tenant mail credentials to config as specified in the $credentialsMap property and makes the mailer use tenant credentials', function() {
|
||||
MailTenancyBootstrapper::$credentialsMap = [
|
||||
'mail.mailers.smtp.username' => 'smtp_username',
|
||||
'mail.mailers.smtp.password' => 'smtp_password'
|
||||
];
|
||||
|
||||
config([
|
||||
'mail.default' => 'smtp',
|
||||
'mail.mailers.smtp.username' => $defaultUsername = 'default username',
|
||||
'mail.mailers.smtp.password' => 'no password'
|
||||
]);
|
||||
|
||||
$tenant = Tenant::create(['smtp_password' => $password = 'testing password']);
|
||||
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
expect(array_key_exists('smtp_password', tenant()->getAttributes()))->toBeTrue();
|
||||
expect(array_key_exists('smtp_host', tenant()->getAttributes()))->toBeFalse();
|
||||
expect(config('mail.mailers.smtp.username'))->toBe($defaultUsername);
|
||||
expect(config('mail.mailers.smtp.password'))->toBe(tenant()->smtp_password);
|
||||
|
||||
// Assert that the current mailer uses tenant's smtp_password
|
||||
assertMailerTransportUsesPassword($password);
|
||||
});
|
||||
|
||||
test('MailTenancyBootstrapper reverts the config and mailer credentials to default when tenancy ends', function() {
|
||||
MailTenancyBootstrapper::$credentialsMap = ['mail.mailers.smtp.password' => 'smtp_password'];
|
||||
config(['mail.default' => 'smtp', 'mail.mailers.smtp.password' => $defaultPassword = 'no password']);
|
||||
|
||||
tenancy()->initialize(Tenant::create(['smtp_password' => $tenantPassword = 'testing password']));
|
||||
|
||||
expect(config('mail.mailers.smtp.password'))->toBe($tenantPassword);
|
||||
|
||||
assertMailerTransportUsesPassword($tenantPassword);
|
||||
|
||||
tenancy()->end();
|
||||
|
||||
expect(config('mail.mailers.smtp.password'))->toBe($defaultPassword);
|
||||
|
||||
// Assert that the current mailer uses the default SMTP password
|
||||
assertMailerTransportUsesPassword($defaultPassword);
|
||||
});
|
||||
|
||||
function getDiskPrefix(string $disk): string
|
||||
{
|
||||
/** @var FilesystemAdapter $disk */
|
||||
$disk = Storage::disk($disk);
|
||||
$adapter = $disk->getAdapter();
|
||||
$prefix = invade(invade($adapter)->prefixer)->prefix;
|
||||
|
||||
$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);
|
||||
return $prefix;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
|||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||
|
||||
beforeEach(function () {
|
||||
if (file_exists($schemaPath = database_path('schema/tenant-schema.dump'))) {
|
||||
if (file_exists($schemaPath = 'tests/Etc/tenant-schema-test.dump')) {
|
||||
unlink($schemaPath);
|
||||
}
|
||||
|
||||
|
|
@ -111,28 +111,44 @@ test('migrate command loads schema state', function () {
|
|||
|
||||
test('dump command works', function () {
|
||||
$tenant = Tenant::create();
|
||||
$schemaPath = 'tests/Etc/tenant-schema-test.dump';
|
||||
|
||||
Artisan::call('tenants:migrate');
|
||||
|
||||
tenancy()->initialize($tenant);
|
||||
expect($schemaPath)->not()->toBeFile();
|
||||
|
||||
Artisan::call('tenants:dump --path="tests/Etc/tenant-schema-test.dump"');
|
||||
expect('tests/Etc/tenant-schema-test.dump')->toBeFile();
|
||||
});
|
||||
|
||||
test('tenant dump file gets created as tenant-schema.dump in the database schema folder by default', function() {
|
||||
config(['tenancy.migration_parameters.--schema-path' => $schemaPath = database_path('schema/tenant-schema.dump')]);
|
||||
|
||||
$tenant = Tenant::create();
|
||||
Artisan::call('tenants:migrate');
|
||||
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
Artisan::call('tenants:dump');
|
||||
Artisan::call('tenants:dump ' . "--tenant='$tenant->id' --path='$schemaPath'");
|
||||
|
||||
expect($schemaPath)->toBeFile();
|
||||
});
|
||||
|
||||
test('migrate command uses the correct schema path by default', function () {
|
||||
test('dump command generates dump at the passed path', function() {
|
||||
$tenant = Tenant::create();
|
||||
|
||||
Artisan::call('tenants:migrate');
|
||||
|
||||
expect($schemaPath = 'tests/Etc/tenant-schema-test.dump')->not()->toBeFile();
|
||||
|
||||
Artisan::call("tenants:dump --tenant='$tenant->id' --path='$schemaPath'");
|
||||
|
||||
expect($schemaPath)->toBeFile();
|
||||
});
|
||||
|
||||
test('dump command generates dump at the path specified in the tenancy migration parameters config', function() {
|
||||
config(['tenancy.migration_parameters.--schema-path' => $schemaPath = 'tests/Etc/tenant-schema-test.dump']);
|
||||
|
||||
$tenant = Tenant::create();
|
||||
|
||||
Artisan::call('tenants:migrate');
|
||||
|
||||
expect($schemaPath)->not()->toBeFile();
|
||||
|
||||
Artisan::call("tenants:dump --tenant='$tenant->id'");
|
||||
|
||||
expect($schemaPath)->toBeFile();
|
||||
});
|
||||
|
||||
test('migrate command correctly uses the schema dump located at the configured schema path by default', function () {
|
||||
config(['tenancy.migration_parameters.--schema-path' => 'tests/Etc/tenant-schema.dump']);
|
||||
$tenant = Tenant::create();
|
||||
|
||||
|
|
@ -146,6 +162,7 @@ test('migrate command uses the correct schema path by default', function () {
|
|||
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
// schema_users is a table included in the tests/Etc/tenant-schema dump
|
||||
// Check for both tables to see if missing migrations also get executed
|
||||
expect(Schema::hasTable('schema_users'))->toBeTrue();
|
||||
expect(Schema::hasTable('users'))->toBeTrue();
|
||||
|
|
@ -355,7 +372,7 @@ function runCommandWorks(): void
|
|||
Artisan::call('tenants:migrate', ['--tenants' => [$id]]);
|
||||
|
||||
pest()->artisan("tenants:run --tenants=$id 'foo foo --b=bar --c=xyz' ")
|
||||
->expectsOutput("User's name is Test command")
|
||||
->expectsOutput("User's name is Test user")
|
||||
->expectsOutput('foo')
|
||||
->expectsOutput('xyz');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Stancl\Tenancy\Tests\Etc\Console;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ExampleCommand extends Command
|
||||
|
|
@ -22,14 +23,13 @@ class ExampleCommand extends Command
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
User::create([
|
||||
'id' => 999,
|
||||
'name' => 'Test command',
|
||||
'email' => 'test@command.com',
|
||||
$id = User::create([
|
||||
'name' => 'Test user',
|
||||
'email' => Str::random(8) . '@example.com',
|
||||
'password' => bcrypt('password'),
|
||||
]);
|
||||
])->id;
|
||||
|
||||
$this->line("User's name is " . User::find(999)->name);
|
||||
$this->line("User's name is " . User::find($id)->name);
|
||||
$this->line($this->argument('a'));
|
||||
$this->line($this->option('c'));
|
||||
}
|
||||
|
|
|
|||
72
tests/MailTest.php
Normal file
72
tests/MailTest.php
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Mail\MailManager;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Stancl\Tenancy\Events\TenancyEnded;
|
||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper;
|
||||
|
||||
beforeEach(function() {
|
||||
config(['mail.default' => 'smtp']);
|
||||
|
||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
|
||||
});
|
||||
|
||||
// Initialize tenancy as $tenant and assert that the smtp mailer's transport has the correct password
|
||||
function assertMailerTransportUsesPassword(string|null $password) {
|
||||
$manager = app(MailManager::class);
|
||||
$mailer = invade($manager)->get('smtp');
|
||||
$mailerPassword = invade($mailer->getSymfonyTransport())->password;
|
||||
|
||||
expect($mailerPassword)->toBe((string) $password);
|
||||
};
|
||||
|
||||
test('mailer transport uses the correct credentials', function() {
|
||||
config(['mail.default' => 'smtp', 'mail.mailers.smtp.password' => $defaultPassword = 'DEFAULT']);
|
||||
MailTenancyBootstrapper::$credentialsMap = ['mail.mailers.smtp.password' => 'smtp_password'];
|
||||
|
||||
tenancy()->initialize($tenant = Tenant::create());
|
||||
assertMailerTransportUsesPassword($defaultPassword); // $tenant->smtp_password is not set, so the default password should be used
|
||||
tenancy()->end();
|
||||
|
||||
// Assert mailer uses the updated password
|
||||
$tenant->update(['smtp_password' => $newPassword = 'changed']);
|
||||
|
||||
tenancy()->initialize($tenant);
|
||||
assertMailerTransportUsesPassword($newPassword);
|
||||
tenancy()->end();
|
||||
|
||||
// Assert mailer uses the correct password after switching to a different tenant
|
||||
tenancy()->initialize(Tenant::create(['smtp_password' => $newTenantPassword = 'updated']));
|
||||
assertMailerTransportUsesPassword($newTenantPassword);
|
||||
tenancy()->end();
|
||||
|
||||
// Assert mailer uses the default password after tenancy ends
|
||||
assertMailerTransportUsesPassword($defaultPassword);
|
||||
});
|
||||
|
||||
|
||||
test('initializing and ending tenancy binds a fresh MailManager instance without cached mailers', function() {
|
||||
$mailers = fn() => invade(app(MailManager::class))->mailers;
|
||||
|
||||
app(MailManager::class)->mailer('smtp');
|
||||
|
||||
expect($mailers())->toHaveCount(1);
|
||||
|
||||
tenancy()->initialize(Tenant::create());
|
||||
|
||||
expect($mailers())->toHaveCount(0);
|
||||
|
||||
app(MailManager::class)->mailer('smtp');
|
||||
|
||||
expect($mailers())->toHaveCount(1);
|
||||
|
||||
tenancy()->end();
|
||||
|
||||
expect($mailers())->toHaveCount(0);
|
||||
});
|
||||
45
tests/ManualModeTest.php
Normal file
45
tests/ManualModeTest.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Stancl\JobPipeline\JobPipeline;
|
||||
use Stancl\Tenancy\Events\TenancyEnded;
|
||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||
use Stancl\Tenancy\Events\TenantCreated;
|
||||
use Stancl\Tenancy\Jobs\CreateDatabase;
|
||||
use Stancl\Tenancy\Listeners\CreateTenantConnection;
|
||||
use Stancl\Tenancy\Listeners\UseCentralConnection;
|
||||
use Stancl\Tenancy\Listeners\UseTenantConnection;
|
||||
use \Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
|
||||
test('manual tenancy initialization works', function () {
|
||||
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
|
||||
return $event->tenant;
|
||||
})->toListener());
|
||||
|
||||
Event::listen(TenancyInitialized::class, CreateTenantConnection::class);
|
||||
Event::listen(TenancyInitialized::class, UseTenantConnection::class);
|
||||
Event::listen(TenancyEnded::class, UseCentralConnection::class);
|
||||
|
||||
$tenant = Tenant::create();
|
||||
|
||||
expect(app('db')->getDefaultConnection())->toBe('central');
|
||||
expect(array_keys(app('db')->getConnections()))->toBe(['central', 'tenant_host_connection']);
|
||||
pest()->assertArrayNotHasKey('tenant', config('database.connections'));
|
||||
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
// Trigger creation of the tenant connection
|
||||
createUsersTable();
|
||||
|
||||
expect(app('db')->getDefaultConnection())->toBe('tenant');
|
||||
expect(array_keys(app('db')->getConnections()))->toBe(['central', 'tenant']);
|
||||
pest()->assertArrayHasKey('tenant', config('database.connections'));
|
||||
|
||||
tenancy()->end();
|
||||
|
||||
expect(array_keys(app('db')->getConnections()))->toBe(['central']);
|
||||
expect(config('database.connections.tenant'))->toBeNull();
|
||||
expect(app('db')->getDefaultConnection())->toBe(config('tenancy.database.central_connection'));
|
||||
});
|
||||
|
|
@ -67,23 +67,6 @@ test('CreatePendingTenants command cannot run with both time constraints', funct
|
|||
->assertFailed();
|
||||
});
|
||||
|
||||
test('CreatePendingTenants commands all option overrides any config constraints', function () {
|
||||
Tenant::createPending();
|
||||
Tenant::createPending();
|
||||
|
||||
tenancy()->model()->query()->onlyPending()->first()->update([
|
||||
'pending_since' => now()->subDays(10)
|
||||
]);
|
||||
|
||||
config(['tenancy.pending.older_than_days' => 4]);
|
||||
|
||||
Artisan::call(ClearPendingTenants::class, [
|
||||
'--all' => true
|
||||
]);
|
||||
|
||||
expect(Tenant::onlyPending()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('tenancy can check if there are any pending tenants', function () {
|
||||
expect(Tenant::onlyPending()->exists())->toBeFalse();
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,19 @@ test('tenant user can be impersonated on a tenant domain', function () {
|
|||
pest()->get('http://foo.localhost/dashboard')
|
||||
->assertSuccessful()
|
||||
->assertSee('You are logged in as Joe');
|
||||
|
||||
expect(UserImpersonation::isImpersonating())->toBeTrue();
|
||||
expect(session('tenancy_impersonating'))->toBeTrue();
|
||||
|
||||
// Leave impersonation
|
||||
UserImpersonation::leave();
|
||||
|
||||
expect(UserImpersonation::isImpersonating())->toBeFalse();
|
||||
expect(session('tenancy_impersonating'))->toBeNull();
|
||||
|
||||
// Assert can't access the tenant dashboard
|
||||
pest()->get('http://foo.localhost/dashboard')
|
||||
->assertRedirect('http://foo.localhost/login');
|
||||
});
|
||||
|
||||
test('tenant user can be impersonated on a tenant path', function () {
|
||||
|
|
@ -116,6 +129,19 @@ test('tenant user can be impersonated on a tenant path', function () {
|
|||
pest()->get('/acme/dashboard')
|
||||
->assertSuccessful()
|
||||
->assertSee('You are logged in as Joe');
|
||||
|
||||
expect(UserImpersonation::isImpersonating())->toBeTrue();
|
||||
expect(session('tenancy_impersonating'))->toBeTrue();
|
||||
|
||||
// Leave impersonation
|
||||
UserImpersonation::leave();
|
||||
|
||||
expect(UserImpersonation::isImpersonating())->toBeFalse();
|
||||
expect(session('tenancy_impersonating'))->toBeNull();
|
||||
|
||||
// Assert can't access the tenant dashboard
|
||||
pest()->get('/acme/dashboard')
|
||||
->assertRedirect('/login');
|
||||
});
|
||||
|
||||
test('tokens have a limited ttl', function () {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use Stancl\Tenancy\Facades\GlobalCache;
|
|||
use Stancl\Tenancy\Facades\Tenancy;
|
||||
use Stancl\Tenancy\TenancyServiceProvider;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper;
|
||||
|
||||
abstract class TestCase extends \Orchestra\Testbench\TestCase
|
||||
{
|
||||
|
|
@ -104,6 +105,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
'--force' => true,
|
||||
],
|
||||
'tenancy.bootstrappers.redis' => RedisTenancyBootstrapper::class, // todo1 change this to []? two tests in TenantDatabaseManagerTest are failing with that
|
||||
'tenancy.bootstrappers.mail' => MailTenancyBootstrapper::class,
|
||||
'queue.connections.central' => [
|
||||
'driver' => 'sync',
|
||||
'central' => true,
|
||||
|
|
@ -114,6 +116,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
|
||||
$app->singleton(RedisTenancyBootstrapper::class); // todo (Samuel) use proper approach eg config for singleton registration
|
||||
$app->singleton(PrefixCacheTenancyBootstrapper::class); // todo (Samuel) use proper approach eg config for singleton registration
|
||||
$app->singleton(MailTenancyBootstrapper::class);
|
||||
}
|
||||
|
||||
protected function getPackageProviders($app)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue