mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 18:24:04 +00:00
Merge dev branch (minor breaking changes)
From the perspective of the master branch, this commit merges in a few small breaking changes from the dev branch:6b0066c5ef- Make pullPendingFromPool() $firstOrCreate arg default to false (pullPending() is now a direct alias for pullPendingFromPool() with default $firstOrCreate=true) - See full commit message for other changes. They shouldn't be breaking though.13a2209f11- Remove $WAL static property. We instead just let Laravel use its journal_mode config now This merge also adds a deprecation:b320f8f33d- Deprecate TenantConfig feature in favor of TenantConfigBootstrapper
This commit is contained in:
commit
151e81b412
43 changed files with 443 additions and 223 deletions
|
|
@ -115,8 +115,6 @@ test('files can get fetched using the storage url', function() {
|
|||
test('storage_path helper does not change if suffix_storage_path is off', function() {
|
||||
$originalStoragePath = storage_path();
|
||||
|
||||
// todo@tests https://github.com/tenancy-for-laravel/v4/pull/44#issue-2228530362
|
||||
|
||||
config([
|
||||
'tenancy.bootstrappers' => [FilesystemTenancyBootstrapper::class],
|
||||
'tenancy.filesystem.suffix_storage_path' => false,
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ test('the clone action can clone specific routes either using name or route inst
|
|||
false,
|
||||
]);
|
||||
|
||||
test('the clone action prefixes already prefixed routes correctly', function () {
|
||||
test('the clone action prefixes already prefixed routes correctly', function (bool $tenantParameterBeforePrefix) {
|
||||
$routes = [
|
||||
RouteFacade::get('/home', fn () => true)
|
||||
->middleware(['clone'])
|
||||
|
|
@ -195,7 +195,12 @@ test('the clone action prefixes already prefixed routes correctly', function ()
|
|||
->prefix('prefix/'),
|
||||
];
|
||||
|
||||
app(CloneRoutesAsTenant::class)->handle();
|
||||
$cloneAction = app(CloneRoutesAsTenant::class);
|
||||
$cloneAction
|
||||
->tenantParameterBeforePrefix($tenantParameterBeforePrefix)
|
||||
->handle();
|
||||
|
||||
$expectedPrefix = $tenantParameterBeforePrefix ? '{tenant}/prefix' : 'prefix/{tenant}';
|
||||
|
||||
$clonedRoutes = [
|
||||
RouteFacade::getRoutes()->getByName('tenant.home'),
|
||||
|
|
@ -206,9 +211,10 @@ test('the clone action prefixes already prefixed routes correctly', function ()
|
|||
|
||||
// The cloned route is prefixed correctly
|
||||
foreach ($clonedRoutes as $key => $route) {
|
||||
expect($route->getPrefix())->toBe("prefix/{tenant}");
|
||||
expect($route->getPrefix())->toBe($expectedPrefix);
|
||||
|
||||
$clonedRouteUrl = route($route->getName(), ['tenant' => $tenant = Tenant::create()]);
|
||||
$expectedPrefixInUrl = $tenantParameterBeforePrefix ? "{$tenant->id}/prefix" : "prefix/{$tenant->id}";
|
||||
|
||||
expect($clonedRouteUrl)
|
||||
// Original prefix does not occur in the cloned route's URL
|
||||
|
|
@ -216,14 +222,14 @@ test('the clone action prefixes already prefixed routes correctly', function ()
|
|||
->not()->toContain("//prefix")
|
||||
->not()->toContain("prefix//")
|
||||
// Instead, the route is prefixed correctly
|
||||
->toBe("http://localhost/prefix/{$tenant->id}/{$routes[$key]->getName()}");
|
||||
->toBe("http://localhost/{$expectedPrefixInUrl}/{$routes[$key]->getName()}");
|
||||
|
||||
// The cloned route is accessible
|
||||
pest()->get($clonedRouteUrl)->assertOk();
|
||||
}
|
||||
});
|
||||
})->with([true, false]);
|
||||
|
||||
test('clone action trims trailing slashes from prefixes given to nested route groups', function () {
|
||||
test('clone action trims trailing slashes from prefixes given to nested route groups', function (bool $tenantParameterBeforePrefix) {
|
||||
RouteFacade::prefix('prefix')->group(function () {
|
||||
RouteFacade::prefix('')->group(function () {
|
||||
// This issue seems to only happen when there's a group with a prefix, then a group with an empty prefix, and then a / route
|
||||
|
|
@ -237,7 +243,10 @@ test('clone action trims trailing slashes from prefixes given to nested route gr
|
|||
});
|
||||
});
|
||||
|
||||
app(CloneRoutesAsTenant::class)->handle();
|
||||
$cloneAction = app(CloneRoutesAsTenant::class);
|
||||
$cloneAction
|
||||
->tenantParameterBeforePrefix($tenantParameterBeforePrefix)
|
||||
->handle();
|
||||
|
||||
$clonedLandingUrl = route('tenant.landing', ['tenant' => $tenant = Tenant::create()]);
|
||||
$clonedHomeRouteUrl = route('tenant.home', ['tenant' => $tenant]);
|
||||
|
|
@ -245,17 +254,20 @@ test('clone action trims trailing slashes from prefixes given to nested route gr
|
|||
$landingRoute = RouteFacade::getRoutes()->getByName('tenant.landing');
|
||||
$homeRoute = RouteFacade::getRoutes()->getByName('tenant.home');
|
||||
|
||||
expect($landingRoute->uri())->toBe('prefix/{tenant}');
|
||||
expect($homeRoute->uri())->toBe('prefix/{tenant}/home');
|
||||
$expectedPrefix = $tenantParameterBeforePrefix ? '{tenant}/prefix' : 'prefix/{tenant}';
|
||||
$expectedPrefixInUrl = $tenantParameterBeforePrefix ? "{$tenant->id}/prefix" : "prefix/{$tenant->id}";
|
||||
|
||||
expect($landingRoute->uri())->toBe($expectedPrefix);
|
||||
expect($homeRoute->uri())->toBe("{$expectedPrefix}/home");
|
||||
|
||||
expect($clonedLandingUrl)
|
||||
->not()->toContain("prefix//")
|
||||
->toBe("http://localhost/prefix/{$tenant->id}");
|
||||
->toBe("http://localhost/{$expectedPrefixInUrl}");
|
||||
|
||||
expect($clonedHomeRouteUrl)
|
||||
->not()->toContain("prefix//")
|
||||
->toBe("http://localhost/prefix/{$tenant->id}/home");
|
||||
});
|
||||
->toBe("http://localhost/{$expectedPrefixInUrl}/home");
|
||||
})->with([true, false]);
|
||||
|
||||
test('tenant routes are ignored from cloning and clone middleware in groups causes no issues', function () {
|
||||
// Should NOT be cloned, already has tenant parameter
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use Illuminate\Contracts\Http\Kernel;
|
|||
use Illuminate\Support\Facades\Event;
|
||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||
use Illuminate\Support\Facades\Route as RouteFacade;
|
||||
use Illuminate\Support\Str;
|
||||
use Stancl\Tenancy\Actions\CloneRoutesAsTenant;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||
|
|
@ -120,7 +121,7 @@ test('early identification works with path identification', function (bool $useK
|
|||
RouteFacade::get('/{post}/comment/{comment}/edit', [$controller, 'computePost']);
|
||||
});
|
||||
|
||||
$tenant = Tenant::create(['tenancy_db_name' => pest()->randomString()]);
|
||||
$tenant = Tenant::create(['tenancy_db_name' => Str::random(10)]);
|
||||
|
||||
// Migrate users and comments tables on tenant connection
|
||||
pest()->artisan('tenants:migrate', [
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ test('sqlite ATTACH statements can be blocked', function (bool $disallow) {
|
|||
return json_encode(DB::select(request('q2')));
|
||||
});
|
||||
|
||||
tenancy(); // trigger features: todo@samuel remove after feature refactor
|
||||
tenancy()->bootstrapFeatures();
|
||||
|
||||
if ($disallow) {
|
||||
expect(fn () => pest()->post('/central-sqli', [
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ test('tenant redirect macro replaces only the hostname', function () {
|
|||
'tenancy.features' => [CrossDomainRedirect::class],
|
||||
]);
|
||||
|
||||
tenancy()->bootstrapFeatures();
|
||||
|
||||
Route::get('/foobar', function () {
|
||||
return 'Foo';
|
||||
})->name('home');
|
||||
|
|
|
|||
|
|
@ -2,29 +2,27 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Stancl\Tenancy\Events\TenancyEnded;
|
||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||
use Stancl\Tenancy\Features\TenantConfig;
|
||||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use Stancl\Tenancy\Bootstrappers\TenantConfigBootstrapper;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
use function Stancl\Tenancy\Tests\withBootstrapping;
|
||||
|
||||
beforeEach(function () {
|
||||
config([
|
||||
'tenancy.bootstrappers' => [TenantConfigBootstrapper::class],
|
||||
]);
|
||||
|
||||
withBootstrapping();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
TenantConfig::$storageToConfigMap = [];
|
||||
TenantConfigBootstrapper::$storageToConfigMap = [];
|
||||
});
|
||||
|
||||
test('nested tenant values are merged', function () {
|
||||
expect(config('whitelabel.theme'))->toBeNull();
|
||||
config([
|
||||
'tenancy.features' => [TenantConfig::class],
|
||||
'tenancy.bootstrappers' => [],
|
||||
]);
|
||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
|
||||
|
||||
TenantConfig::$storageToConfigMap = [
|
||||
TenantConfigBootstrapper::$storageToConfigMap = [
|
||||
'whitelabel.config.theme' => 'whitelabel.theme',
|
||||
];
|
||||
|
||||
|
|
@ -39,14 +37,8 @@ test('nested tenant values are merged', function () {
|
|||
|
||||
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);
|
||||
|
||||
TenantConfig::$storageToConfigMap = [
|
||||
TenantConfigBootstrapper::$storageToConfigMap = [
|
||||
'paypal_api_public' => 'services.paypal.public',
|
||||
'paypal_api_private' => 'services.paypal.private',
|
||||
];
|
||||
|
|
@ -68,14 +60,8 @@ test('config is merged and removed', function () {
|
|||
|
||||
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);
|
||||
|
||||
TenantConfig::$storageToConfigMap = [
|
||||
TenantConfigBootstrapper::$storageToConfigMap = [
|
||||
'paypal_api_public' => [
|
||||
'services.paypal.public1',
|
||||
'services.paypal.public2',
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ beforeEach(function () {
|
|||
|
||||
test('vite bundler ensures vite assets use global_asset when asset_helper_override is enabled', function () {
|
||||
config(['tenancy.features' => [ViteBundler::class]]);
|
||||
tenancy()->bootstrapFeatures();
|
||||
|
||||
withBootstrapping();
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use Illuminate\Http\Request;
|
|||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Stancl\Tenancy\Actions\CloneRoutesAsTenant;
|
||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||
|
|
@ -44,7 +45,7 @@ test('asset can be accessed using the url returned by the tenant asset helper',
|
|||
$tenant = Tenant::create();
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
$filename = 'testfile' . pest()->randomString(10);
|
||||
$filename = 'testfile' . Str::random(8);
|
||||
Storage::disk('public')->put($filename, 'bar');
|
||||
$path = storage_path("app/public/$filename");
|
||||
|
||||
|
|
@ -136,7 +137,7 @@ test('TenantAssetController headers are configurable', function () {
|
|||
tenancy()->initialize($tenant);
|
||||
$tenant->createDomain('foo.localhost');
|
||||
|
||||
$filename = 'testfile' . pest()->randomString(10);
|
||||
$filename = 'testfile' . Str::random(10);
|
||||
Storage::disk('public')->put($filename, 'bar');
|
||||
|
||||
$this->withoutExceptionHandling();
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledPostgreSQ
|
|||
use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledPostgreSQLDatabaseManager;
|
||||
use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledMicrosoftSQLServerDatabaseManager;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
use function Stancl\Tenancy\Tests\withBootstrapping;
|
||||
use function Stancl\Tenancy\Tests\withTenantDatabases;
|
||||
|
||||
beforeEach(function () {
|
||||
SQLiteDatabaseManager::$path = null;
|
||||
|
|
@ -43,7 +45,7 @@ test('databases can be created and deleted', function ($driver, $databaseManager
|
|||
"tenancy.database.managers.$driver" => $databaseManager,
|
||||
]);
|
||||
|
||||
$name = 'db' . pest()->randomString();
|
||||
$name = 'db' . Str::random(10);
|
||||
|
||||
$manager = app($databaseManager);
|
||||
|
||||
|
|
@ -70,7 +72,7 @@ test('dbs can be created when another driver is used for the central db', functi
|
|||
return $event->tenant;
|
||||
})->toListener());
|
||||
|
||||
$database = 'db' . pest()->randomString();
|
||||
$database = 'db' . Str::random(10);
|
||||
|
||||
$mysqlmanager = app(MySQLDatabaseManager::class);
|
||||
$mysqlmanager->setConnection('mysql');
|
||||
|
|
@ -86,7 +88,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' . pest()->randomString();
|
||||
$database = 'db' . Str::random(10);
|
||||
expect($postgresManager->databaseExists($database))->toBeFalse();
|
||||
|
||||
Tenant::create([
|
||||
|
|
@ -146,18 +148,15 @@ test('db name is prefixed with db path when sqlite is used', function () {
|
|||
expect(database_path('foodb'))->toBe(config('database.connections.tenant.database'));
|
||||
});
|
||||
|
||||
test('sqlite databases use the WAL journal mode by default', function (bool|null $wal) {
|
||||
$expected = $wal ? 'wal' : 'delete';
|
||||
if ($wal !== null) {
|
||||
SQLiteDatabaseManager::$WAL = $wal;
|
||||
} else {
|
||||
// default behavior
|
||||
$expected = 'wal';
|
||||
}
|
||||
|
||||
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
|
||||
return $event->tenant;
|
||||
})->toListener());
|
||||
test('sqlite databases respect the template journal_mode config', function (string $journal_mode) {
|
||||
withTenantDatabases();
|
||||
withBootstrapping();
|
||||
config([
|
||||
'database.connections.sqlite.journal_mode' => $journal_mode,
|
||||
'tenancy.bootstrappers' => [
|
||||
DatabaseTenancyBootstrapper::class,
|
||||
],
|
||||
]);
|
||||
|
||||
$tenant = Tenant::create([
|
||||
'tenancy_db_connection' => 'sqlite',
|
||||
|
|
@ -170,11 +169,18 @@ test('sqlite databases use the WAL journal mode by default', function (bool|null
|
|||
$db = new PDO('sqlite:' . $dbPath);
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
expect($db->query('pragma journal_mode')->fetch(PDO::FETCH_ASSOC)['journal_mode'])->toBe($expected);
|
||||
// Before we connect to the DB using Laravel, it will be in default delete mode
|
||||
expect($db->query('pragma journal_mode')->fetch(PDO::FETCH_ASSOC)['journal_mode'])->toBe('delete');
|
||||
|
||||
// cleanup
|
||||
SQLiteDatabaseManager::$WAL = true;
|
||||
})->with([true, false, null]);
|
||||
// This will trigger the logic in Laravel's SQLiteConnector
|
||||
$tenant->run(fn () => DB::select('select 1'));
|
||||
|
||||
$db = new PDO('sqlite:' . $dbPath);
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
// Once we connect to the DB, it will be in the configured journal mode
|
||||
expect($db->query('pragma journal_mode')->fetch(PDO::FETCH_ASSOC)['journal_mode'])->toBe($journal_mode);
|
||||
})->with(['delete', 'wal']);
|
||||
|
||||
test('schema manager uses schema to separate tenant dbs', function () {
|
||||
config([
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ beforeEach(function () {
|
|||
],
|
||||
]);
|
||||
|
||||
tenancy()->bootstrapFeatures();
|
||||
|
||||
Event::listen(
|
||||
TenantCreated::class,
|
||||
JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ use Stancl\Tenancy\Bootstrappers\BroadcastChannelPrefixBootstrapper;
|
|||
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
use Stancl\Tenancy\Bootstrappers\DatabaseCacheBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\TenantConfigBootstrapper;
|
||||
|
||||
abstract class TestCase extends \Orchestra\Testbench\TestCase
|
||||
{
|
||||
|
|
@ -193,6 +194,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
$app->singleton(RootUrlBootstrapper::class);
|
||||
$app->singleton(UrlGeneratorBootstrapper::class);
|
||||
$app->singleton(FilesystemTenancyBootstrapper::class);
|
||||
$app->singleton(TenantConfigBootstrapper::class);
|
||||
}
|
||||
|
||||
protected function getPackageProviders($app)
|
||||
|
|
@ -236,11 +238,6 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
$app->singleton('Illuminate\Contracts\Console\Kernel', Etc\Console\ConsoleKernel::class);
|
||||
}
|
||||
|
||||
public function randomString(int $length = 10)
|
||||
{
|
||||
return substr(str_shuffle(str_repeat($x = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', (int) (ceil($length / strlen($x))))), 1, $length);
|
||||
}
|
||||
|
||||
public function assertArrayIsSubset($subset, $array, string $message = ''): void
|
||||
{
|
||||
parent::assertTrue(array_intersect($subset, $array) == $subset, $message);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue