mirror of
https://github.com/archtechx/tenancy.git
synced 2026-02-06 05:04:03 +00:00
Refactor cached tenant resolvers with decorator pattern
This commit is contained in:
parent
8e3b74f9d1
commit
97e45ab9cc
15 changed files with 424 additions and 251 deletions
|
|
@ -1,95 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
|
||||
afterEach(function () {
|
||||
DomainTenantResolver::$shouldCache = false;
|
||||
});
|
||||
|
||||
test('tenants can be resolved using the cached resolver', function () {
|
||||
$tenant = Tenant::create();
|
||||
$tenant->domains()->create([
|
||||
'domain' => 'acme',
|
||||
]);
|
||||
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue()->toBeTrue();
|
||||
});
|
||||
|
||||
test('the underlying resolver is not touched when using the cached resolver', function () {
|
||||
$tenant = Tenant::create();
|
||||
$tenant->domains()->create([
|
||||
'domain' => 'acme',
|
||||
]);
|
||||
|
||||
DB::enableQueryLog();
|
||||
|
||||
DomainTenantResolver::$shouldCache = false;
|
||||
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||
DB::flushQueryLog();
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||
pest()->assertNotEmpty(DB::getQueryLog()); // not empty
|
||||
|
||||
DomainTenantResolver::$shouldCache = true;
|
||||
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||
DB::flushQueryLog();
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||
expect(DB::getQueryLog())->toBeEmpty(); // empty
|
||||
});
|
||||
|
||||
test('cache is invalidated when the tenant is updated', function () {
|
||||
$tenant = Tenant::create();
|
||||
$tenant->createDomain([
|
||||
'domain' => 'acme',
|
||||
]);
|
||||
|
||||
DB::enableQueryLog();
|
||||
|
||||
DomainTenantResolver::$shouldCache = true;
|
||||
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||
DB::flushQueryLog();
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||
expect(DB::getQueryLog())->toBeEmpty(); // empty
|
||||
|
||||
$tenant->update([
|
||||
'foo' => 'bar',
|
||||
]);
|
||||
|
||||
DB::flushQueryLog();
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||
pest()->assertNotEmpty(DB::getQueryLog()); // not empty
|
||||
});
|
||||
|
||||
test('cache is invalidated when a tenants domain is changed', function () {
|
||||
$tenant = Tenant::create();
|
||||
$tenant->createDomain([
|
||||
'domain' => 'acme',
|
||||
]);
|
||||
|
||||
DB::enableQueryLog();
|
||||
|
||||
DomainTenantResolver::$shouldCache = true;
|
||||
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||
DB::flushQueryLog();
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||
expect(DB::getQueryLog())->toBeEmpty(); // empty
|
||||
|
||||
$tenant->createDomain([
|
||||
'domain' => 'bar',
|
||||
]);
|
||||
|
||||
DB::flushQueryLog();
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||
pest()->assertNotEmpty(DB::getQueryLog()); // not empty
|
||||
|
||||
DB::flushQueryLog();
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('bar')))->toBeTrue();
|
||||
pest()->assertNotEmpty(DB::getQueryLog()); // not empty
|
||||
});
|
||||
41
tests/Repository/InMemoryTenantRepository.php
Normal file
41
tests/Repository/InMemoryTenantRepository.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Stancl\Tenancy\Tests\Repository;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Stancl\Tenancy\Contracts\Tenant;
|
||||
use Stancl\Tenancy\Repository\TenantRepository;
|
||||
|
||||
class InMemoryTenantRepository implements TenantRepository
|
||||
{
|
||||
public function __construct(
|
||||
/** @var Collection<Tenant> */
|
||||
private Collection $tenants = new Collection(),
|
||||
) {
|
||||
}
|
||||
|
||||
public function find(int|string $id): ?Tenant
|
||||
{
|
||||
return $this->tenants->first(fn (Tenant $tenant) => $tenant->getTenantKey() == $id);
|
||||
}
|
||||
|
||||
public function findForDomain(string $domain): ?Tenant
|
||||
{
|
||||
return $this->tenants->firstWhere(fn (Tenant $tenant) => in_array($domain, $tenant->domains ?? []));
|
||||
}
|
||||
|
||||
public function all(): iterable
|
||||
{
|
||||
return $this->tenants->lazy();
|
||||
}
|
||||
|
||||
public function whereKeyIn(string|int ...$ids): iterable
|
||||
{
|
||||
return $this->tenants->filter(fn (Tenant $tenant) => in_array($tenant->getTenantKey(), $ids))->lazy();
|
||||
}
|
||||
|
||||
public function store(Tenant $tenant): void
|
||||
{
|
||||
$this->tenants->push($tenant);
|
||||
}
|
||||
}
|
||||
50
tests/Resolvers/CachedTenantResolverTest.php
Normal file
50
tests/Resolvers/CachedTenantResolverTest.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Cache\Repository;
|
||||
use Illuminate\Contracts\Cache\Store;
|
||||
use Stancl\Tenancy\Contracts\TenantResolver;
|
||||
use Stancl\Tenancy\Resolvers\CachedTenantResolver;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
|
||||
it('uses the underlying resolver if cache is stale', function () {
|
||||
$underlying = Mockery::mock(TenantResolver::class);
|
||||
$cache = new Repository($store = Mockery::mock(Store::class));
|
||||
|
||||
$args = [
|
||||
'id' => 1,
|
||||
];
|
||||
|
||||
$resolver = new CachedTenantResolver(
|
||||
tenantResolver: $underlying,
|
||||
cache: $cache,
|
||||
prefix: '_tenant_resolver',
|
||||
);
|
||||
|
||||
$store->expects('get')->withAnyArgs()->andReturnNull();
|
||||
$underlying->expects('resolve')->andReturn($tenant = new Tenant());
|
||||
$store->expects('put')->withSomeOfArgs($tenant);
|
||||
|
||||
expect($resolver->resolve($args))->toBe($tenant);
|
||||
});
|
||||
|
||||
it('skips the underlying resolver if cache is valid', function () {
|
||||
$underlying = Mockery::mock(TenantResolver::class);
|
||||
$cache = new Repository($store = Mockery::mock(Store::class));
|
||||
|
||||
$args = [
|
||||
'id' => 1,
|
||||
];
|
||||
|
||||
$resolver = new CachedTenantResolver(
|
||||
tenantResolver: $underlying,
|
||||
cache: $cache,
|
||||
prefix: '_tenant_resolver',
|
||||
);
|
||||
|
||||
$cache->expects('get')->withAnyArgs()->andReturn($tenant = new Tenant());
|
||||
$underlying->expects('resolve')->never();
|
||||
|
||||
expect($resolver->resolve($args))->toBe($tenant);
|
||||
});
|
||||
33
tests/Resolvers/DomainTenantResolverTest.php
Normal file
33
tests/Resolvers/DomainTenantResolverTest.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use Stancl\Tenancy\Tests\Repository\InMemoryTenantRepository;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->repository = new InMemoryTenantRepository();
|
||||
|
||||
$this->tenant = new Tenant();
|
||||
$this->tenant->domains = ['acme'];
|
||||
|
||||
$this->repository->store($this->tenant);
|
||||
});
|
||||
|
||||
it('resolves the tenant with domain', function () {
|
||||
$resolver = new DomainTenantResolver(
|
||||
tenantRepository: $this->repository,
|
||||
);
|
||||
|
||||
$result = $resolver->resolve('acme');
|
||||
|
||||
expect($result)->toBe($this->tenant);
|
||||
});
|
||||
|
||||
it('throws when unable to find tenant', function () {
|
||||
$resolver = new DomainTenantResolver(
|
||||
tenantRepository: $this->repository,
|
||||
);
|
||||
|
||||
$resolver->resolve('foo');
|
||||
})->throws(TenantCouldNotBeIdentifiedOnDomainException::class);
|
||||
40
tests/Resolvers/PathTenantResolverTest.php
Normal file
40
tests/Resolvers/PathTenantResolverTest.php
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Route;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByPathException;
|
||||
use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
||||
use Stancl\Tenancy\Tests\Repository\InMemoryTenantRepository;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->repository = new InMemoryTenantRepository();
|
||||
|
||||
$this->tenant = new Tenant();
|
||||
$this->tenant->id = 1;
|
||||
|
||||
$this->repository->store($this->tenant);
|
||||
});
|
||||
|
||||
it('resolves the tenant from path', function () {
|
||||
$resolver = new PathTenantResolver(
|
||||
tenantRepository: $this->repository,
|
||||
);
|
||||
|
||||
$route = (new Route('get', '/{tenant}/foo', fn () => null))
|
||||
->bind(Request::create('/1/foo'));
|
||||
|
||||
$result = $resolver->resolve($route);
|
||||
|
||||
expect($result)->toBe($this->tenant);
|
||||
});
|
||||
|
||||
it('throws when unable to find tenant', function () {
|
||||
$resolver = new PathTenantResolver(
|
||||
tenantRepository: new InMemoryTenantRepository(),
|
||||
);
|
||||
|
||||
$route = (new Route('GET', '/{tenant}/foo', fn () => null))
|
||||
->bind(Request::create('/2/foo'));
|
||||
|
||||
$resolver->resolve($route);
|
||||
})->throws(TenantCouldNotBeIdentifiedByPathException::class);
|
||||
33
tests/Resolvers/RequestDataTenantResolverTest.php
Normal file
33
tests/Resolvers/RequestDataTenantResolverTest.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use Stancl\Tenancy\Tests\Repository\InMemoryTenantRepository;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->repository = new InMemoryTenantRepository();
|
||||
|
||||
$this->tenant = new Tenant();
|
||||
$this->tenant->id = 1;
|
||||
|
||||
$this->repository->store($this->tenant);
|
||||
});
|
||||
|
||||
it('resolves the tenant', function () {
|
||||
$resolver = new DomainTenantResolver(
|
||||
tenantRepository: $this->repository,
|
||||
);
|
||||
|
||||
$result = $resolver->resolve(id: 1);
|
||||
|
||||
expect($result)->toBe($this->tenant);
|
||||
});
|
||||
|
||||
it('throws when unable to find tenant', function () {
|
||||
$resolver = new DomainTenantResolver(
|
||||
tenantRepository: $this->repository,
|
||||
);
|
||||
|
||||
$resolver->resolve('foo');
|
||||
})->throws(TenantCouldNotBeIdentifiedOnDomainException::class);
|
||||
Loading…
Add table
Add a link
Reference in a new issue