1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-03-22 17:54:03 +00:00

Merge branch 'archtechx:3.x' into 3.x

This commit is contained in:
Leandro Gehlen 2025-07-10 07:22:42 -03:00 committed by GitHub
commit 32e3b375ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 114 additions and 19 deletions

View file

@ -16,12 +16,13 @@ jobs:
strategy: strategy:
matrix: matrix:
include: include:
- laravel: 9
php: "8.0"
- laravel: 10 - laravel: 10
php: "8.1" php: "8.1"
- laravel: 11 - laravel: 11
php: "8.3" php: "8.3"
- laravel: 12
php: "8.3"
# Ideally we'd run at least one of these on PHP 8.4, however the Dockerfile seems to require some changes for that
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

35
.github/workflows/queue.yml vendored Normal file
View file

@ -0,0 +1,35 @@
name: Queue tests
on:
- push
jobs:
queue:
name: Queue application tests
runs-on: ubuntu-latest
steps:
- name: Prepare composer version constraint prefix
run: |
if [[ $GITHUB_REF == refs/tags/* ]]; then
# For refs like "refs/tags/v3.9.0", remove "refs/tags/v" prefix to get just "3.9.0"
VERSION=${GITHUB_REF#refs/tags/v}
echo "VERSION_PREFIX=${VERSION}" >> $GITHUB_ENV
else
BRANCH=${GITHUB_REF#refs/heads/}
if [[ $BRANCH =~ ^[0-9]\.x$ ]]; then
# Branches starting with %d.x need to use -dev suffix
echo "VERSION_PREFIX=${BRANCH}-dev" >> $GITHUB_ENV
else
# All other branches use dev-${branch} prefix
echo "VERSION_PREFIX=dev-${BRANCH}" >> $GITHUB_ENV
fi
fi
- name: Clone test suite
run: git clone https://github.com/archtechx/tenancy-queue-tester
- name: Run tests
run: |
cd tenancy-queue-tester
TENANCY_VERSION=${VERSION_PREFIX}#${GITHUB_SHA} ./setup.sh
TENANCY_VERSION=${VERSION_PREFIX}#${GITHUB_SHA} ./test.sh

View file

@ -194,6 +194,6 @@ return [
*/ */
'seeder_parameters' => [ 'seeder_parameters' => [
'--class' => 'DatabaseSeeder', // root seeder class '--class' => 'DatabaseSeeder', // root seeder class
// '--force' => true, // '--force' => true, // This needs to be true to seed tenant databases in production
], ],
]; ];

View file

@ -12,15 +12,15 @@
"require": { "require": {
"php": "^8.0", "php": "^8.0",
"ext-json": "*", "ext-json": "*",
"illuminate/support": "^9.0|^10.0|^11.0", "illuminate/support": "^10.0|^11.0|^12.0",
"facade/ignition-contracts": "^1.0.2", "facade/ignition-contracts": "^1.0.2",
"ramsey/uuid": "^4.7.3", "ramsey/uuid": "^4.7.3",
"stancl/jobpipeline": "^1.6.2", "stancl/jobpipeline": "^1.8.0",
"stancl/virtualcolumn": "^1.3.1" "stancl/virtualcolumn": "^1.5.0"
}, },
"require-dev": { "require-dev": {
"laravel/framework": "^9.0|^10.0|^11.0", "laravel/framework": "^10.0|^11.0|^12.0",
"orchestra/testbench": "^7.0|^8.0|^9.0", "orchestra/testbench": "^8.0|^9.0|^10.0",
"league/flysystem-aws-s3-v3": "^3.12.2", "league/flysystem-aws-s3-v3": "^3.12.2",
"doctrine/dbal": "^3.6.0", "doctrine/dbal": "^3.6.0",
"spatie/valuestore": "^1.3.2" "spatie/valuestore": "^1.3.2"

View file

@ -18,13 +18,16 @@ trait InvalidatesResolverCache
public static function bootInvalidatesResolverCache() public static function bootInvalidatesResolverCache()
{ {
static::saved(function (Tenant $tenant) { $invalidateCache = static function (Tenant $tenant) {
foreach (static::$resolvers as $resolver) { foreach (static::$resolvers as $resolver) {
/** @var CachedTenantResolver $resolver */ /** @var CachedTenantResolver $resolver */
$resolver = app($resolver); $resolver = app($resolver);
$resolver->invalidateCache($tenant); $resolver->invalidateCache($tenant);
} }
}); };
static::saved($invalidateCache);
static::deleting($invalidateCache);
} }
} }

View file

@ -21,13 +21,16 @@ trait InvalidatesTenantsResolverCache
public static function bootInvalidatesTenantsResolverCache() public static function bootInvalidatesTenantsResolverCache()
{ {
static::saved(function (Model $model) { $invalidateCache = static function (Model $model) {
foreach (static::$resolvers as $resolver) { foreach (static::$resolvers as $resolver) {
/** @var CachedTenantResolver $resolver */ /** @var CachedTenantResolver $resolver */
$resolver = app($resolver); $resolver = app($resolver);
$resolver->invalidateCache($model->tenant); $resolver->invalidateCache($model->tenant);
} }
}); };
static::saved($invalidateCache);
static::deleting($invalidateCache);
} }
} }

View file

@ -10,6 +10,6 @@ class ModelNotSyncMasterException extends Exception
{ {
public function __construct(string $class) public function __construct(string $class)
{ {
parent::__construct("Model of $class class is not an SyncMaster model. Make sure you're using the central model to make changes to synced resources when you're in the central context"); parent::__construct("Model of $class class is not a SyncMaster model. Make sure you're using the central model to make changes to synced resources when you're in the central context.");
} }
} }

View file

@ -18,7 +18,7 @@ class UserImpersonation implements Feature
public function bootstrap(Tenancy $tenancy): void public function bootstrap(Tenancy $tenancy): void
{ {
$tenancy->macro('impersonate', function (Tenant $tenant, string $userId, string $redirectUrl, string $authGuard = null): ImpersonationToken { $tenancy->macro('impersonate', function (Tenant $tenant, string $userId, string $redirectUrl, ?string $authGuard = null): ImpersonationToken {
return ImpersonationToken::create([ return ImpersonationToken::create([
'tenant_id' => $tenant->getTenantKey(), 'tenant_id' => $tenant->getTenantKey(),
'user_id' => $userId, 'user_id' => $userId,
@ -32,10 +32,10 @@ class UserImpersonation implements Feature
* Impersonate a user and get an HTTP redirect response. * Impersonate a user and get an HTTP redirect response.
* *
* @param string|ImpersonationToken $token * @param string|ImpersonationToken $token
* @param int $ttl * @param int|null $ttl
* @return RedirectResponse * @return RedirectResponse
*/ */
public static function makeResponse($token, int $ttl = null): RedirectResponse public static function makeResponse($token, ?int $ttl = null): RedirectResponse
{ {
$token = $token instanceof ImpersonationToken ? $token : ImpersonationToken::findOrFail($token); $token = $token instanceof ImpersonationToken ? $token : ImpersonationToken::findOrFail($token);

View file

@ -40,10 +40,8 @@ class InitializeTenancyByPath extends IdentificationMiddleware
return $this->initializeTenancy( return $this->initializeTenancy(
$request, $next, $route $request, $next, $route
); );
} else {
throw new RouteIsMissingTenantParameterException;
} }
return $next($request); throw new RouteIsMissingTenantParameterException;
} }
} }

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Tests; namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
use Stancl\Tenancy\Resolvers\DomainTenantResolver; use Stancl\Tenancy\Resolvers\DomainTenantResolver;
use Stancl\Tenancy\Tests\Etc\Tenant; use Stancl\Tenancy\Tests\Etc\Tenant;
@ -80,6 +81,33 @@ class CachedTenantResolverTest extends TestCase
$this->assertNotEmpty(DB::getQueryLog()); // not empty $this->assertNotEmpty(DB::getQueryLog()); // not empty
} }
/** @test */
public function cache_is_invalidated_when_the_tenant_is_deleted()
{
$tenant = Tenant::create();
$tenant->createDomain([
'domain' => 'acme',
]);
DB::enableQueryLog();
DomainTenantResolver::$shouldCache = true;
$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->delete();
DB::flushQueryLog();
$this->assertThrows(function () {
app(DomainTenantResolver::class)->resolve('acme');
}, TenantCouldNotBeIdentifiedOnDomainException::class);
$this->assertNotEmpty(DB::getQueryLog()); // not empty - cache cleared so the DB was queried
}
/** @test */ /** @test */
public function cache_is_invalidated_when_a_tenants_domain_is_changed() public function cache_is_invalidated_when_a_tenants_domain_is_changed()
{ {
@ -109,4 +137,31 @@ class CachedTenantResolverTest extends TestCase
$this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('bar'))); $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('bar')));
$this->assertNotEmpty(DB::getQueryLog()); // not empty $this->assertNotEmpty(DB::getQueryLog()); // not empty
} }
/** @test */
public function cache_is_invalidated_when_a_tenants_domain_is_deleted()
{
$tenant = Tenant::create();
$tenant->createDomain([
'domain' => 'acme',
]);
DB::enableQueryLog();
DomainTenantResolver::$shouldCache = true;
$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->domains->first()->delete();
DB::flushQueryLog();
$this->assertThrows(function () {
app(DomainTenantResolver::class)->resolve('acme');
}, TenantCouldNotBeIdentifiedOnDomainException::class);
$this->assertNotEmpty(DB::getQueryLog()); // not empty - cache cleared so the DB was queried
}
} }