1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-13 23:54:03 +00:00

Get rid of v3 test namespace

This commit is contained in:
Samuel Štancl 2020-05-12 23:23:16 +02:00
parent 89936187ce
commit 6962ec29b9
20 changed files with 0 additions and 0 deletions

View file

@ -1,86 +0,0 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Tests\TestCase;
class AutomaticModeTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
}
/** @test */
public function context_is_switched_when_tenancy_is_initialized()
{
config(['tenancy.bootstrappers' => [
MyBootstrapper::class,
]]);
$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'));
}
}
class MyBootstrapper implements TenancyBootstrapper
{
public function start(\Stancl\Tenancy\Contracts\Tenant $tenant)
{
app()->instance('tenancy_initialized_for_tenant', $tenant->getTenantKey());
}
public function end()
{
app()->instance('tenancy_ended', true);
}
}

View file

@ -1,209 +0,0 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
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\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\Listeners\JobPipeline;
use Stancl\Tenancy\Events\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\TenancyBootstrappers\CacheTenancyBootstrapper;
use Stancl\Tenancy\TenancyBootstrappers\DatabaseTenancyBootstrapper;
use Stancl\Tenancy\TenancyBootstrappers\FilesystemTenancyBootstrapper;
use Stancl\Tenancy\TenancyBootstrappers\RedisTenancyBootstrapper;
use Stancl\Tenancy\Tests\TestCase;
class BootstrapperTest extends TestCase
{
public $mockConsoleOutput = false;
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);
}
/** @test */
public function database_data_is_separated()
{
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']);
$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'));
// Check suffixing logic
$new_storage_path = storage_path();
$this->assertEquals($old_storage_path . '/' . config('tenancy.filesystem.suffix_base') . tenant('id'), $new_storage_path);
foreach (config('tenancy.filesystem.disks') as $disk) {
$suffix = config('tenancy.filesystem.suffix_base') . tenant('id');
$current_path_prefix = Storage::disk($disk)->getAdapter()->getPathPrefix();
if ($override = config("tenancy.filesystem.root_override.{$disk}")) {
$correct_path_prefix = str_replace('%storage_path%', storage_path(), $override);
} else {
if ($base = $old_storage_facade_roots[$disk]) {
$correct_path_prefix = $base . "/$suffix/";
} else {
$correct_path_prefix = "$suffix/";
}
}
$this->assertSame($correct_path_prefix, $current_path_prefix);
}
}
// for queues see QueueTest
}

View file

@ -1,138 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\TenancyBootstrappers\CacheTenancyBootstrapper;
use Stancl\Tenancy\Tests\TestCase;
class CacheManagerTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
config(['tenancy.bootstrappers' => [
CacheTenancyBootstrapper::class,
]]);
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
}
/** @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());
}
/** @test */
public function tags_are_merged_when_array_is_passed()
{
tenancy()->initialize(Tenant::create());
$expected = [config('tenancy.cache.tag_base') . tenant('id'), 'foo', 'bar'];
$this->assertEquals($expected, cache()->tags(['foo', 'bar'])->getTags()->getNames());
}
/** @test */
public function tags_are_merged_when_string_is_passed()
{
tenancy()->initialize(Tenant::create());
$expected = [config('tenancy.cache.tag_base') . tenant('id'), 'foo'];
$this->assertEquals($expected, cache()->tags('foo')->getTags()->getNames());
}
/** @test */
public function exception_is_thrown_when_zero_arguments_are_passed_to_tags_method()
{
tenancy()->initialize(Tenant::create());
$this->expectException(\Exception::class);
cache()->tags();
}
/** @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);
}
/** @test */
public function tags_separate_cache_well_enough()
{
$tenant1 = Tenant::create();
tenancy()->initialize($tenant1);
cache()->put('foo', 'bar', 1);
$this->assertSame('bar', cache()->get('foo'));
$tenant2 = Tenant::create();
tenancy()->initialize($tenant2);
$this->assertNotSame('bar', cache()->get('foo'));
cache()->put('foo', 'xyz', 1);
$this->assertSame('xyz', cache()->get('foo'));
}
/** @test */
public function invoking_the_cache_helper_works()
{
$tenant1 = Tenant::create();
tenancy()->initialize($tenant1);
cache(['foo' => 'bar'], 1);
$this->assertSame('bar', cache('foo'));
$tenant2 = Tenant::create();
tenancy()->initialize($tenant2);
$this->assertNotSame('bar', cache('foo'));
cache(['foo' => 'xyz'], 1);
$this->assertSame('xyz', cache('foo'));
}
/** @test */
public function cache_is_persisted()
{
$tenant1 = Tenant::create();
tenancy()->initialize($tenant1);
cache(['foo' => 'bar'], 10);
$this->assertSame('bar', cache('foo'));
tenancy()->end();
tenancy()->initialize($tenant1);
$this->assertSame('bar', cache('foo'));
}
/** @test */
public function cache_is_persisted_when_reidentification_is_used()
{
$tenant1 = Tenant::create();
$tenant2 = Tenant::create();
tenancy()->initialize($tenant1);
cache(['foo' => 'bar'], 10);
$this->assertSame('bar', cache('foo'));
tenancy()->initialize($tenant2);
tenancy()->end();
tenancy()->initialize($tenant1);
$this->assertSame('bar', cache('foo'));
}
}

View file

@ -1,78 +0,0 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Database\Models;
use Stancl\Tenancy\Database\Models\Concerns\HasDomains;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain;
use Stancl\Tenancy\Tests\TestCase;
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";
});
});
config(['tenancy.tenant_model' => Tenant::class]);
}
/** @test */
public function tenant_can_be_identified_by_subdomain()
{
config(['tenancy.central_domains' => ['localhost']]);
$tenant = Tenant::create([
'id' => 'acme',
]);
$tenant->domains()->create([
'domain' => 'foo',
]);
$this->assertFalse(tenancy()->initialized);
$this
->get('http://foo.localhost/foo/abc/xyz')
->assertSee('abc + xyz');
$this->assertTrue(tenancy()->initialized);
$this->assertSame('acme', tenant('id'));
}
/** @test */
public function tenant_can_be_identified_by_domain()
{
config(['tenancy.central_domains' => []]);
$tenant = Tenant::create([
'id' => 'acme',
]);
$tenant->domains()->create([
'domain' => 'foobar.localhost',
]);
$this->assertFalse(tenancy()->initialized);
$this
->get('http://foobar.localhost/foo/abc/xyz')
->assertSee('abc + xyz');
$this->assertTrue(tenancy()->initialized);
$this->assertSame('acme', tenant('id'));
}
}
class Tenant extends Models\Tenant
{
use HasDomains;
}

View file

@ -1,183 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Schema;
use Stancl\Tenancy\Tests\Etc\ExampleSeeder;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\Listeners\JobPipeline;
use Stancl\Tenancy\Events\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\TenancyBootstrappers\DatabaseTenancyBootstrapper;
use Stancl\Tenancy\Tests\TestCase;
class CommandsTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
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);
}
/** @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 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()->id;
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(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());
}
}

View file

@ -1,135 +0,0 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
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\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\JobPipeline;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\Jobs\MigrateDatabase;
use Stancl\Tenancy\Jobs\SeedDatabase;
use Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager;
use Stancl\Tenancy\Tests\TestCase;
class DatabasePreparationTest extends TestCase
{
/** @test */
public function database_can_be_created_after_tenant_creation()
{
config(['tenancy.template_tenant_connection' => 'mysql']);
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
return $event->tenant;
})->toListener());
$tenant = Tenant::create();
$this->assertTrue(app(MySQLDatabaseManager::class)->databaseExists($tenant->database()->getName()));
}
/** @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());
$tenant = Tenant::create();
$tenant->run(function () {
$this->assertTrue(Schema::hasTable('users'));
});
}
/** @test */
public function database_can_be_seeded_after_tenant_creation()
{
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());
$tenant = Tenant::create();
$tenant->run(function () {
$this->assertSame('Seeded User', User::first()->name);
});
}
/** @test */
public function custom_job_can_be_added_to_the_pipeline()
{
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());
$tenant = Tenant::create();
$tenant->run(function () {
$this->assertSame('Foo', User::all()[1]->name);
});
}
}
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;
public function __construct(Tenant $tenant)
{
$this->tenant = $tenant;
}
public function handle()
{
$this->tenant->run(function () {
User::create(['name' => 'Foo', 'email' => 'foo@bar.com', 'password' => 'secret']);
});
}
}

View file

@ -1,122 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Str;
use Stancl\Tenancy\Contracts\ManagesDatabaseUsers;
use Stancl\Tenancy\Exceptions\TenantDatabaseUserAlreadyExistsException;
use Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager;
use Stancl\Tenancy\TenantDatabaseManagers\PermissionControlledMySQLDatabaseManager;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\JobPipeline;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\Tests\TestCase;
class DatabaseUsersTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
config([
'tenancy.database_managers.mysql' => PermissionControlledMySQLDatabaseManager::class,
'tenancy.database.suffix' => '',
'tenancy.template_tenant_connection' => 'mysql',
]);
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
return $event->tenant;
})->toListener());
}
/** @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();
$this->assertFalse($manager->userExists($tenant->database()->getUsername()));
$tenant->save();
$this->assertTrue($manager->userExists($tenant->database()->getUsername()));
}
/** @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();
$this->assertTrue($manager->userExists($tenant->database()->getUsername()));
$this->assertTrue($manager->databaseExists($tenant->database()->getName()));
$this->expectException(TenantDatabaseUserAlreadyExistsException::class);
$tenant2 = Tenant::create([
'tenancy_db_username' => $username,
]);
/** @var ManagesDatabaseUsers $manager */
$manager = $tenant2->database()->manager();
// database was not created because of DB transaction
$this->assertFalse($manager->databaseExists($tenant2->database()->getName()));
}
/** @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),
]);
$query = DB::connection('mysql')->select("SHOW GRANTS FOR `{$tenant->database()->getUsername()}`@`{$tenant->database()->connection()['host']}`")[1];
$this->assertStringStartsWith('GRANT CREATE, ALTER, ALTER ROUTINE ON', $query->{"Grants for {$user}@mysql"}); // @mysql because that's the hostname within the docker network
}
/** @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_connection' => 'mysql',
]);
$tenant = Tenant::create([
'id' => 'foo' . Str::random(10),
]);
$this->assertTrue($tenant->database()->manager() instanceof MySQLDatabaseManager);
$tenant = Tenant::create([
'id' => 'foo' . Str::random(10),
]);
tenancy()->initialize($tenant); // check if everything works
tenancy()->end();
config(['tenancy.database_managers.mysql' => PermissionControlledMySQLDatabaseManager::class]);
tenancy()->initialize($tenant); // check if everything works
$this->assertTrue($tenant->database()->manager() instanceof PermissionControlledMySQLDatabaseManager);
$this->assertSame('root', config('database.connections.tenant.username'));
}
}

View file

@ -1,110 +0,0 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Database\Models;
use Stancl\Tenancy\Database\Models\Concerns\HasDomains;
use Stancl\Tenancy\Exceptions\DomainOccupiedByOtherTenantException;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
use Stancl\Tenancy\Tests\TestCase;
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";
});
});
config(['tenancy.tenant_model' => Tenant::class]);
}
/** @test */
public function tenant_can_be_identified_using_hostname()
{
$tenant = Tenant::create();
$id = $tenant->id;
$tenant->domains()->create([
'domain' => 'foo.localhost',
]);
$resolvedTenant = app(DomainTenantResolver::class)->resolve('foo.localhost');
$this->assertSame($id, $resolvedTenant->id);
$this->assertSame(['foo.localhost'], $resolvedTenant->domains->pluck('domain')->toArray());
}
/** @test */
public function a_domain_can_belong_to_only_one_tenant()
{
$tenant = Tenant::create();
$tenant->domains()->create([
'domain' => 'foo.localhost',
]);
$tenant2 = Tenant::create();
$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);
app(DomainTenantResolver::class)->resolve('foo.localhost');
}
/** @test */
public function tenant_can_be_identified_by_domain()
{
$tenant = Tenant::create([
'id' => 'acme',
]);
$tenant->domains()->create([
'domain' => 'foo.localhost',
]);
$this->assertFalse(tenancy()->initialized);
$this
->get('http://foo.localhost/foo/abc/xyz')
->assertSee('abc + xyz');
$this->assertTrue(tenancy()->initialized);
$this->assertSame('acme', tenant('id'));
}
/** @test */
public function onfail_logic_can_be_customized()
{
InitializeTenancyByDomain::$onFail = function () {
return 'foo';
};
$this
->get('http://foo.localhost/foo/abc/xyz')
->assertSee('foo');
}
}
class Tenant extends Models\Tenant
{
use HasDomains;
}

View file

@ -1,56 +0,0 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Events\CallQueuedListener;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\QueueableListener;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Tests\TestCase;
class EventListenerTest extends TestCase
{
/** @test */
public function listeners_can_be_synchronous()
{
Queue::fake();
Event::listen(TenantCreated::class, FooListener::class);
Tenant::create();
Queue::assertNothingPushed();
$this->assertSame('bar', app('foo'));
}
/** @test */
public function listeners_can_be_queued_by_setting_a_static_property()
{
Queue::fake();
Event::listen(TenantCreated::class, FooListener::class);
FooListener::$shouldQueue = true;
Tenant::create();
Queue::assertPushed(CallQueuedListener::class, function (CallQueuedListener $job) {
return $job->class === FooListener::class;
});
$this->assertFalse(app()->bound('foo'));
}
// todo test that the way the published SP registers events works
}
class FooListener extends QueueableListener
{
public static $shouldQueue = false;
public function handle()
{
app()->instance('foo', 'bar');
}
}

View file

@ -1,59 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Facades\GlobalCache;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\TenancyBootstrappers\CacheTenancyBootstrapper;
use Stancl\Tenancy\Tests\TestCase;
class GlobalCacheTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
config(['tenancy.bootstrappers' => [
CacheTenancyBootstrapper::class,
]]);
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
}
/** @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);
$this->assertSame('bar', GlobalCache::get('foo'));
GlobalCache::put(['abc' => 'xyz'], 1);
cache(['def' => 'ghi'], 10);
$this->assertSame('ghi', cache('def'));
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);
$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);
$this->assertSame('ghi', cache('def'));
}
}

View file

@ -1,180 +0,0 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue;
use Spatie\Valuestore\Valuestore;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\JobPipeline;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Tests\TestCase;
class JobPipelineTest extends TestCase
{
public $mockConsoleOutput = false;
/** @var Valuestore */
protected $valuestore;
public function setUp(): void
{
parent::setUp();
config(['queue.default' => 'redis']);
$this->valuestore = Valuestore::make(__DIR__ . '/../Etc/tmp/jobpipelinetest.json')->flush();
}
/** @test */
public function job_pipeline_can_listen_to_any_event()
{
Event::listen(TenantCreated::class, JobPipeline::make([
FooJob::class,
])->send(function () {
return $this->valuestore;
})->toListener());
$this->assertFalse($this->valuestore->has('foo'));
Tenant::create();
$this->assertSame('bar', $this->valuestore->get('foo'));
}
/** @test */
public function job_pipeline_can_be_queued()
{
Queue::fake();
Event::listen(TenantCreated::class, JobPipeline::make([
FooJob::class,
])->send(function () {
return $this->valuestore;
})->shouldBeQueued(true)->toListener());
Queue::assertNothingPushed();
Tenant::create();
$this->assertFalse($this->valuestore->has('foo'));
Queue::pushed(JobPipeline::class, function (JobPipeline $pipeline) {
$this->assertSame([FooJob::class], $pipeline->jobs);
});
}
/** @test */
public function job_pipelines_run_when_queued()
{
Event::listen(TenantCreated::class, JobPipeline::make([
FooJob::class,
])->send(function () {
return $this->valuestore;
})->shouldBeQueued(true)->toListener());
$this->assertFalse($this->valuestore->has('foo'));
Tenant::create();
$this->artisan('queue:work --once');
$this->assertSame('bar', $this->valuestore->get('foo'));
}
/** @test */
public function job_pipeline_executes_jobs_and_passes_the_object_sequentially()
{
Event::listen(TenantCreated::class, JobPipeline::make([
FirstJob::class,
SecondJob::class,
])->send(function (TenantCreated $event) {
return [$event->tenant, $this->valuestore];
})->toListener());
$this->assertFalse($this->valuestore->has('foo'));
Tenant::create();
$this->assertSame('first job changed property', $this->valuestore->get('foo'));
}
/** @test */
public function send_can_return_multiple_arguments()
{
Event::listen(TenantCreated::class, JobPipeline::make([
JobWithMultipleArguments::class
])->send(function () {
return ['a', 'b'];
})->toListener());
$this->assertFalse(app()->bound('test_args'));
Tenant::create();
$this->assertSame(['a', 'b'], app('test_args'));
}
}
class FooJob
{
protected $valuestore;
public function __construct(Valuestore $valuestore)
{
$this->valuestore = $valuestore;
}
public function handle()
{
$this->valuestore->put('foo', 'bar');
}
};
class FirstJob
{
public $tenant;
public function __construct(Tenant $tenant)
{
$this->tenant = $tenant;
}
public function handle()
{
$this->tenant->foo = 'first job changed property';
}
}
class SecondJob
{
public $tenant;
protected $valuestore;
public function __construct(Tenant $tenant, Valuestore $valuestore)
{
$this->tenant = $tenant;
$this->valuestore = $valuestore;
}
public function handle()
{
$this->valuestore->put('foo', $this->tenant->foo);
}
}
class JobWithMultipleArguments
{
protected $first;
protected $second;
public function __construct($first, $second)
{
$this->first = $first;
$this->second = $second;
}
public function handle()
{
// we dont queue this job so no need to use valuestore here
app()->instance('test_args', [$this->first, $this->second]);
}
}

View file

@ -1,140 +0,0 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByPathException;
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
use Stancl\Tenancy\Resolvers\PathTenantResolver;
use Stancl\Tenancy\Tests\TestCase;
class PathIdentificationTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
PathTenantResolver::$tenantParameterName = 'tenant';
Route::group([
'prefix' => '/{tenant}',
'middleware' => InitializeTenancyByPath::class,
], function () {
Route::get('/foo/{a}/{b}', function ($a, $b) {
return "$a + $b";
});
});
}
/** @test */
public function tenant_can_be_identified_by_path()
{
Tenant::create([
'id' => 'acme',
]);
$this->assertFalse(tenancy()->initialized);
$this->get('/acme/foo/abc/xyz');
$this->assertTrue(tenancy()->initialized);
$this->assertSame('acme', tenant('id'));
}
/** @test */
public function route_actions_dont_get_the_tenant_id()
{
Tenant::create([
'id' => 'acme',
]);
$this->assertFalse(tenancy()->initialized);
$this
->get('/acme/foo/abc/xyz')
->assertContent('abc + xyz');
$this->assertTrue(tenancy()->initialized);
$this->assertSame('acme', tenant('id'));
}
/** @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->assertFalse(tenancy()->initialized);
}
/** @test */
public function onfail_logic_can_be_customized()
{
InitializeTenancyByPath::$onFail = function () {
return '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";
});
});
Tenant::create([
'id' => 'acme',
]);
$this->expectException(RouteIsMissingTenantParameterException::class);
$this
->withoutExceptionHandling()
->get('/bar/foo/bar');
}
/** @test */
public function tenant_parameter_name_can_be_customized()
{
PathTenantResolver::$tenantParameterName = 'team';
Route::group([
'prefix' => '/{team}',
'middleware' => InitializeTenancyByPath::class,
], function () {
Route::get('/bar/{a}/{b}', function ($a, $b) {
return "$a + $b";
});
});
Tenant::create([
'id' => 'acme',
]);
$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);
$this
->withoutExceptionHandling()
->get('/acme/foo/abc/xyz');
}
}

View file

@ -1,136 +0,0 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\Events\JobProcessing;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Event;
use Spatie\Valuestore\Valuestore;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\TenancyBootstrappers\QueueTenancyBootstrapper;
use Stancl\Tenancy\Tests\TestCase;
class QueueTest extends TestCase
{
public $mockConsoleOutput = false;
/** @var Valuestore */
protected $valuestore;
public function setUp(): void
{
parent::setUp();
config([
'tenancy.bootstrappers' => [
QueueTenancyBootstrapper::class,
],
'queue.default' => 'redis',
]);
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
$this->valuestore = Valuestore::make(__DIR__ . '/../Etc/tmp/queuetest.json')->flush();
}
/** @test */
public function tenant_id_is_passed_to_tenant_queues()
{
$tenant = Tenant::create();
tenancy()->initialize($tenant);
Event::fake();
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 */
public function tenancy_is_initialized_inside_queues()
{
$tenant = Tenant::create([
'id' => 'acme',
]);
tenancy()->initialize($tenant);
dispatch(new TestJob($this->valuestore));
$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'));
}
/** @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'));
}
}
class TestJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** @var Valuestore */
protected $valuestore;
public function __construct(Valuestore $valuestore)
{
$this->valuestore = $valuestore;
}
public function handle()
{
$this->valuestore->put('tenant_id', "The current tenant id is: " . tenant('id'));
}
}

View file

@ -1,61 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
use Stancl\Tenancy\Tests\TestCase;
class RequestDataIdentificationTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
config([
'tenancy.central_domains' => [
'localhost',
],
]);
InitializeTenancyByRequestData::$header = 'X-Tenant';
InitializeTenancyByRequestData::$queryParameter = 'tenant';
Route::middleware(InitializeTenancyByRequestData::class)->get('/test', function () {
return 'Tenant id: ' . tenant('id');
});
}
/** @test */
public function header_identification_works()
{
InitializeTenancyByRequestData::$header = 'X-Tenant';
$tenant = Tenant::create();
$tenant2 = Tenant::create();
$this
->withoutExceptionHandling()
->get('test', [
'X-Tenant' => $tenant->id,
])
->assertSee($tenant->id);
}
/** @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);
}
}

View file

@ -1,127 +0,0 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Database\Models;
use Stancl\Tenancy\Database\Models\Concerns\HasDomains;
use Stancl\Tenancy\Exceptions\NotASubdomainException;
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
use Stancl\Tenancy\Tests\TestCase;
class SubdomainTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
// Global state cleanup after some tests
InitializeTenancyBySubdomain::$onInvalidSubdomain = null;
Route::group([
'middleware' => InitializeTenancyBySubdomain::class,
], function () {
Route::get('/foo/{a}/{b}', function ($a, $b) {
return "$a + $b";
});
});
config(['tenancy.tenant_model' => Tenant::class]);
}
/** @test */
public function tenant_can_be_identified_by_subdomain()
{
$tenant = Tenant::create([
'id' => 'acme',
]);
$tenant->domains()->create([
'domain' => 'foo',
]);
$this->assertFalse(tenancy()->initialized);
$this
->get('http://foo.localhost/foo/abc/xyz')
->assertSee('abc + xyz');
$this->assertTrue(tenancy()->initialized);
$this->assertSame('acme', tenant('id'));
}
/** @test */
public function onfail_logic_can_be_customized()
{
InitializeTenancyBySubdomain::$onFail = function () {
return 'foo';
};
$this
->get('http://foo.localhost/foo/abc/xyz')
->assertSee('foo');
}
/** @test */
public function localhost_is_not_a_valid_subdomain()
{
$this->expectException(NotASubdomainException::class);
$this
->withoutExceptionHandling()
->get('http://localhost/foo/abc/xyz');
}
/** @test */
public function ip_address_is_not_a_valid_subdomain()
{
$this->expectException(NotASubdomainException::class);
$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::$onInvalidSubdomain = function () {
return response('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'
]]);
$tenant = Tenant::create([
'id' => 'acme',
]);
$tenant->domains()->create([
'domain' => 'foo',
]);
$this->expectException(NotASubdomainException::class);
$this
->withoutExceptionHandling()
->get('http://foo.localhost/foo/abc/xyz');
}
}
class Tenant extends Models\Tenant
{
use HasDomains;
}

View file

@ -1,116 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Storage;
use Stancl\Tenancy\Controllers\TenantAssetsController;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
use Stancl\Tenancy\TenancyBootstrappers\FilesystemTenancyBootstrapper;
use Stancl\Tenancy\Tests\TestCase;
class TenantAssetTest extends TestCase
{
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'));
}
}

View file

@ -1,34 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Tests\TestCase;
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']],
]);
$this->artisan('user:add')
->assertExitCode(0);
tenancy()->initialize($tenant1);
$this->assertNotEmpty(DB::table('users')->get());
tenancy()->end();
tenancy()->initialize($tenant2);
$this->assertNotEmpty(DB::table('users')->get());
tenancy()->end();
}
}

View file

@ -1,144 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\DatabaseManager;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\Listeners\JobPipeline;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\TenancyBootstrappers\DatabaseTenancyBootstrapper;
use Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager;
use Stancl\Tenancy\TenantDatabaseManagers\PermissionControlledMySQLDatabaseManager;
use Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLDatabaseManager;
use Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLSchemaManager;
use Stancl\Tenancy\TenantDatabaseManagers\SQLiteDatabaseManager;
use Stancl\Tenancy\Tests\TestCase;
class TenantDatabaseManagerTest extends TestCase
{
/**
* @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,
'tenancy.internal_prefix' => 'tenancy_',
]);
$name = 'db' . $this->randomString();
$this->assertFalse(app($databaseManager)->databaseExists($name));
$tenant = Tenant::create([
'tenancy_db_name' => $name,
'tenancy_db_connection' => $driver,
]);
$this->assertTrue(app($databaseManager)->databaseExists($name));
app($databaseManager)->deleteDatabase($tenant);
$this->assertFalse(app($databaseManager)->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());
// todo if the prefix is _tenancy_, this blows up. write a tenantmodel test that the prefix can be _tenancy_
config(['tenancy.internal_prefix' => 'tenancy_',]);
$database = 'db' . $this->randomString();
$this->assertFalse(app(MySQLDatabaseManager::class)->databaseExists($database));
Tenant::create([
'tenancy_db_name' => $database,
'tenancy_db_connection' => 'mysql',
]);
$this->assertTrue(app(MySQLDatabaseManager::class)->databaseExists($database));
$database = 'db' . $this->randomString();
$this->assertFalse(app(PostgreSQLDatabaseManager::class)->databaseExists($database));
Tenant::create([
'tenancy_db_name' => $database,
'tenancy_db_connection' => 'pgsql',
]);
$this->assertTrue(app(PostgreSQLDatabaseManager::class)->databaseExists($database));
}
public function database_manager_provider()
{
return [
['mysql', MySQLDatabaseManager::class],
['mysql', PermissionControlledMySQLDatabaseManager::class],
['sqlite', SQLiteDatabaseManager::class],
['pgsql', PostgreSQLDatabaseManager::class],
['pgsql', PostgreSQLSchemaManager::class],
];
}
/** @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',
'tenancy.internal_prefix' => 'tenancy_',
]);
$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);
$this->assertSame($tenant->database()->getName(), config('database.connections.' . config('database.default') . '.schema'));
$this->assertSame($originalDatabaseName, config(['database.connections.pgsql.database']));
}
}

View file

@ -1,163 +0,0 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Schema;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Tests\TestCase;
use Stancl\Tenancy\UniqueIDGenerators\UUIDGenerator;
use Stancl\Tenancy\Contracts;
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
class TenantModelTest extends TestCase
{
/** @test */
public function created_event_is_dispatched()
{
Event::fake([TenantCreated::class]);
Event::assertNotDispatched(TenantCreated::class);
Tenant::create();
Event::assertDispatched(TenantCreated::class);
}
/** @test */
public function current_tenant_can_be_resolved_from_service_container_using_typehint()
{
$tenant = Tenant::create();
tenancy()->initialize($tenant);
$this->assertSame($tenant->id, app(Contracts\Tenant::class)->id);
tenancy()->end();
$this->assertSame(null, app(Tenant::class));
}
/** @test */
public function keys_which_dont_have_their_own_column_go_into_data_json_column()
{
$tenant = Tenant::create([
'foo' => 'bar',
]);
// Test that model works correctly
$this->assertSame('bar', $tenant->foo);
$this->assertSame(null, $tenant->data);
// Low level test to test database structure
$this->assertSame(json_encode(['foo' => 'bar']), DB::table('tenants')->where('id', $tenant->id)->first()->data);
$this->assertSame(null, DB::table('tenants')->where('id', $tenant->id)->first()->foo ?? null);
// Model has the correct structure when retrieved
$tenant = Tenant::first();
$this->assertSame('bar', $tenant->foo);
$this->assertSame(null, $tenant->data);
// Model can be updated
$tenant->update([
'foo' => 'baz',
'abc' => 'xyz',
]);
$this->assertSame('baz', $tenant->foo);
$this->assertSame('xyz', $tenant->abc);
$this->assertSame(null, $tenant->data);
// Model can be retrieved after update & is structure correctly
$tenant = Tenant::first();
$this->assertSame('baz', $tenant->foo);
$this->assertSame('xyz', $tenant->abc);
$this->assertSame(null, $tenant->data);
}
/** @test */
public function id_is_generated_when_no_id_is_supplied()
{
config(['tenancy.id_generator' => UUIDGenerator::class]);
$this->mock(UUIDGenerator::class, function ($mock) {
return $mock->shouldReceive('generate')->once();
});
$tenant = Tenant::create();
$this->assertNotNull($tenant->id);
}
/** @test */
public function autoincrement_ids_are_supported()
{
Schema::table('tenants', function (Blueprint $table) {
$table->bigIncrements('id')->change();
});
unset(app()[UniqueIdentifierGenerator::class]);
$tenant1 = MyTenant::create();
$tenant2 = MyTenant::create();
$this->assertSame(1, $tenant1->id);
$this->assertSame(2, $tenant2->id);
}
/** @test */
public function custom_tenant_model_can_be_used()
{
$tenant = MyTenant::create();
tenancy()->initialize($tenant);
$this->assertTrue(tenant() instanceof MyTenant);
}
/** @test */
public function custom_tenant_model_that_doesnt_extend_vendor_Tenant_model_can_be_used()
{
$tenant = AnotherTenant::create([
'id' => 'acme',
]);
tenancy()->initialize($tenant);
$this->assertTrue(tenant() instanceof AnotherTenant);
}
// todo test that tenant can be created even in another DB context - that the central trait works
}
class MyTenant extends Tenant
{
protected $table = 'tenants';
public $increments = true;
}
class AnotherTenant extends Model implements Contracts\Tenant
{
protected $guarded = [];
protected $table = 'tenants';
public function getTenantKeyName(): string
{
return 'id';
}
public function getTenantKey(): string
{
return $this->getAttribute('id');
}
public function run(callable $callback)
{
$callback();
}
}