mirror of
https://github.com/archtechx/tenancy-queue-tester.git
synced 2025-12-12 09:24:03 +00:00
add persistence, sync, and forceRefresh assertions
This commit is contained in:
parent
fd830beaf7
commit
6a047d3686
10 changed files with 562 additions and 29 deletions
37
README.md
37
README.md
|
|
@ -4,12 +4,43 @@ In addition to the tests we can write using testbench, we have this repository w
|
||||||
1. Creates a new Laravel application
|
1. Creates a new Laravel application
|
||||||
2. Sets up Tenancy
|
2. Sets up Tenancy
|
||||||
3. Creates a sample job
|
3. Creates a sample job
|
||||||
4. Asserts that the queue worker is working as expected
|
4. Asserts that the queue worker is working as expected -- running in the correct context and responding to restart signals
|
||||||
|
|
||||||
This is mostly due to some past bugs that were hard to catch in our test suite.
|
This is mostly due to some past bugs that were hard to catch in our test suite.
|
||||||
|
|
||||||
With this repo, we can have a separate CI job validating queue behavior _in a real application_.
|
With this repo, we can have a separate CI job validating queue behavior _in a real application_.
|
||||||
|
|
||||||
## TODOs
|
## Persistence tests
|
||||||
|
|
||||||
- Verify how `queue:restart` works in v4
|
Additionally, we can also test for _queue worker persistence_. This refers to the worker staying in the context of the tenant
|
||||||
|
used in the last job. The benefit of that is significantly better third-party package support (especially in cases where said
|
||||||
|
packages unserialize job payloads on e.g. `JobProcessed`).
|
||||||
|
|
||||||
|
In versions prior to v4:
|
||||||
|
- 3.8.5 handles restarts correctly but is not persistent
|
||||||
|
- 3.8.4 is persistent but doesn't respond to restarts correctly (if the last processed job was in the tenant context)
|
||||||
|
|
||||||
|
In v4, there's `QueueTenancyBootstrapper` that works similarly to 3.8.5 and `PersistentQueueTenancyBootstrapper` that works
|
||||||
|
similarly to 3.8.4.
|
||||||
|
|
||||||
|
For the different setups:
|
||||||
|
- 3.x should have only warns on missing persistence
|
||||||
|
- 3.8.4 fails the restart-related assertions. The alternative config (./alternative_config.sh) makes them pass.
|
||||||
|
- 3.8.4 fails the FORCEREFRESH-related assertions. Either run with FORCEREFRESH=0 or set `QueueTenancyBootstrapper::$forceRefresh = true` in a service provider.
|
||||||
|
- 4.x should only show warns on missing persistence
|
||||||
|
- With the alternative config, it should pass ALL tests without any warnings.
|
||||||
|
|
||||||
|
3.x (3.8.5+) tests:
|
||||||
|
```bash
|
||||||
|
./setup.sh
|
||||||
|
./test.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
4.x tests:
|
||||||
|
```bash
|
||||||
|
./setup.sh
|
||||||
|
./test.sh
|
||||||
|
|
||||||
|
./alternative_config.sh
|
||||||
|
PERSISTENT=1 ./test.sh
|
||||||
|
```
|
||||||
|
|
|
||||||
7
alternative_config.sh
Executable file
7
alternative_config.sh
Executable file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Sets up the "alternative config" mentioned here https://github.com/archtechx/tenancy/issues/1260#issuecomment-2572951587
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
docker run --rm -v .:/var/www/html tenancy-queue-test-cli bash setup/alternative/_alternative_setup.sh
|
||||||
28
setup/AppServiceProvider.php
Executable file
28
setup/AppServiceProvider.php
Executable file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Queue\Events\JobProcessed;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
class AppServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register any application services.
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap any application services.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
Event::listen(JobProcessed::class, function () {
|
||||||
|
file_put_contents(base_path('jobprocessed_context'), tenant() ? ('tenant_' . tenant('id')) : 'central');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
20
setup/LogAbcJob.php
Executable file
20
setup/LogAbcJob.php
Executable file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
|
||||||
|
class LogAbcJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
file_put_contents(base_path('abc'), tenant('abc'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -39,3 +39,7 @@ cp database/migrations/*create_users*.php database/migrations/tenant
|
||||||
|
|
||||||
mkdir app/Jobs
|
mkdir app/Jobs
|
||||||
cp ../setup/FooJob.php app/Jobs/FooJob.php
|
cp ../setup/FooJob.php app/Jobs/FooJob.php
|
||||||
|
cp ../setup/LogAbcJob.php app/Jobs/LogAbcJob.php
|
||||||
|
|
||||||
|
rm app/Providers/AppServiceProvider.php
|
||||||
|
cp ../setup/AppServiceProvider.php app/Providers/AppServiceProvider.php
|
||||||
|
|
|
||||||
35
setup/alternative/AppServiceProvider.php
Executable file
35
setup/alternative/AppServiceProvider.php
Executable file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Queue\Console\RestartCommand;
|
||||||
|
use Illuminate\Queue\Console\WorkCommand;
|
||||||
|
use Illuminate\Queue\Events\JobProcessed;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
class AppServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register any application services.
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->app->extend(RestartCommand::class, function ($_, $app) {
|
||||||
|
return new RestartCommand($app['cache']->store('global_redis'));
|
||||||
|
});
|
||||||
|
$this->app->extend(WorkCommand::class, function ($_, $app) {
|
||||||
|
return new WorkCommand($app['queue.worker'], $app['cache']->store('global_redis'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap any application services.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
Event::listen(JobProcessed::class, function () {
|
||||||
|
file_put_contents(base_path('jobprocessed_context'), tenant() ? ('tenant_' . tenant('id')) : 'central');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
14
setup/alternative/_alternative_setup.sh
Executable file
14
setup/alternative/_alternative_setup.sh
Executable file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd src/
|
||||||
|
|
||||||
|
rm config/cache.php
|
||||||
|
cp ../setup/alternative/cache.php config/cache.php
|
||||||
|
|
||||||
|
rm config/database.php
|
||||||
|
cp ../setup/alternative/database.php config/database.php
|
||||||
|
|
||||||
|
rm app/Providers/AppServiceProvider.php
|
||||||
|
cp ../setup/alternative/AppServiceProvider.php app/Providers/AppServiceProvider.php
|
||||||
114
setup/alternative/cache.php
Executable file
114
setup/alternative/cache.php
Executable file
|
|
@ -0,0 +1,114 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Cache Store
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option controls the default cache store that will be used by the
|
||||||
|
| framework. This connection is utilized if another isn't explicitly
|
||||||
|
| specified when running a cache operation inside the application.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('CACHE_STORE', 'database'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cache Stores
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may define all of the cache "stores" for your application as
|
||||||
|
| well as their drivers. You may even define multiple stores for the
|
||||||
|
| same cache driver to group types of items stored in your caches.
|
||||||
|
|
|
||||||
|
| Supported drivers: "array", "database", "file", "memcached",
|
||||||
|
| "redis", "dynamodb", "octane", "null"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'stores' => [
|
||||||
|
|
||||||
|
'array' => [
|
||||||
|
'driver' => 'array',
|
||||||
|
'serialize' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
'database' => [
|
||||||
|
'driver' => 'database',
|
||||||
|
'connection' => env('DB_CACHE_CONNECTION'),
|
||||||
|
'table' => env('DB_CACHE_TABLE', 'cache'),
|
||||||
|
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
|
||||||
|
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'file' => [
|
||||||
|
'driver' => 'file',
|
||||||
|
'path' => storage_path('framework/cache/data'),
|
||||||
|
'lock_path' => storage_path('framework/cache/data'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'memcached' => [
|
||||||
|
'driver' => 'memcached',
|
||||||
|
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
|
||||||
|
'sasl' => [
|
||||||
|
env('MEMCACHED_USERNAME'),
|
||||||
|
env('MEMCACHED_PASSWORD'),
|
||||||
|
],
|
||||||
|
'options' => [
|
||||||
|
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
|
||||||
|
],
|
||||||
|
'servers' => [
|
||||||
|
[
|
||||||
|
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('MEMCACHED_PORT', 11211),
|
||||||
|
'weight' => 100,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'redis' => [
|
||||||
|
'driver' => 'redis',
|
||||||
|
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
|
||||||
|
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'global_redis' => [
|
||||||
|
'driver' => 'redis',
|
||||||
|
'connection' => 'global_cache',
|
||||||
|
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'dynamodb' => [
|
||||||
|
'driver' => 'dynamodb',
|
||||||
|
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||||
|
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||||
|
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||||
|
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
|
||||||
|
'endpoint' => env('DYNAMODB_ENDPOINT'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'octane' => [
|
||||||
|
'driver' => 'octane',
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cache Key Prefix
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
|
||||||
|
| stores, there might be other applications using the same cache. For
|
||||||
|
| that reason, you may prefix every cache key to avoid collisions.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
|
||||||
|
|
||||||
|
];
|
||||||
191
setup/alternative/database.php
Executable file
191
setup/alternative/database.php
Executable file
|
|
@ -0,0 +1,191 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Database Connection Name
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify which of the database connections below you wish
|
||||||
|
| to use as your default connection for database operations. This is
|
||||||
|
| the connection which will be utilized unless another connection
|
||||||
|
| is explicitly specified when you execute a query / statement.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('DB_CONNECTION', 'sqlite'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Database Connections
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Below are all of the database connections defined for your application.
|
||||||
|
| An example configuration is provided for each database system which
|
||||||
|
| is supported by Laravel. You're free to add / remove connections.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'connections' => [
|
||||||
|
|
||||||
|
'sqlite' => [
|
||||||
|
'driver' => 'sqlite',
|
||||||
|
'url' => env('DB_URL'),
|
||||||
|
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
||||||
|
'prefix' => '',
|
||||||
|
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
||||||
|
'busy_timeout' => null,
|
||||||
|
'journal_mode' => null,
|
||||||
|
'synchronous' => null,
|
||||||
|
],
|
||||||
|
|
||||||
|
'mysql' => [
|
||||||
|
'driver' => 'mysql',
|
||||||
|
'url' => env('DB_URL'),
|
||||||
|
'host' => env('DB_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('DB_PORT', '3306'),
|
||||||
|
'database' => env('DB_DATABASE', 'laravel'),
|
||||||
|
'username' => env('DB_USERNAME', 'root'),
|
||||||
|
'password' => env('DB_PASSWORD', ''),
|
||||||
|
'unix_socket' => env('DB_SOCKET', ''),
|
||||||
|
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||||
|
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||||
|
'prefix' => '',
|
||||||
|
'prefix_indexes' => true,
|
||||||
|
'strict' => true,
|
||||||
|
'engine' => null,
|
||||||
|
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||||
|
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||||
|
]) : [],
|
||||||
|
],
|
||||||
|
|
||||||
|
'mariadb' => [
|
||||||
|
'driver' => 'mariadb',
|
||||||
|
'url' => env('DB_URL'),
|
||||||
|
'host' => env('DB_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('DB_PORT', '3306'),
|
||||||
|
'database' => env('DB_DATABASE', 'laravel'),
|
||||||
|
'username' => env('DB_USERNAME', 'root'),
|
||||||
|
'password' => env('DB_PASSWORD', ''),
|
||||||
|
'unix_socket' => env('DB_SOCKET', ''),
|
||||||
|
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||||
|
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||||
|
'prefix' => '',
|
||||||
|
'prefix_indexes' => true,
|
||||||
|
'strict' => true,
|
||||||
|
'engine' => null,
|
||||||
|
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||||
|
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||||
|
]) : [],
|
||||||
|
],
|
||||||
|
|
||||||
|
'pgsql' => [
|
||||||
|
'driver' => 'pgsql',
|
||||||
|
'url' => env('DB_URL'),
|
||||||
|
'host' => env('DB_HOST', '127.0.0.1'),
|
||||||
|
'port' => env('DB_PORT', '5432'),
|
||||||
|
'database' => env('DB_DATABASE', 'laravel'),
|
||||||
|
'username' => env('DB_USERNAME', 'root'),
|
||||||
|
'password' => env('DB_PASSWORD', ''),
|
||||||
|
'charset' => env('DB_CHARSET', 'utf8'),
|
||||||
|
'prefix' => '',
|
||||||
|
'prefix_indexes' => true,
|
||||||
|
'search_path' => 'public',
|
||||||
|
'sslmode' => 'prefer',
|
||||||
|
],
|
||||||
|
|
||||||
|
'sqlsrv' => [
|
||||||
|
'driver' => 'sqlsrv',
|
||||||
|
'url' => env('DB_URL'),
|
||||||
|
'host' => env('DB_HOST', 'localhost'),
|
||||||
|
'port' => env('DB_PORT', '1433'),
|
||||||
|
'database' => env('DB_DATABASE', 'laravel'),
|
||||||
|
'username' => env('DB_USERNAME', 'root'),
|
||||||
|
'password' => env('DB_PASSWORD', ''),
|
||||||
|
'charset' => env('DB_CHARSET', 'utf8'),
|
||||||
|
'prefix' => '',
|
||||||
|
'prefix_indexes' => true,
|
||||||
|
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
|
||||||
|
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Migration Repository Table
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This table keeps track of all the migrations that have already run for
|
||||||
|
| your application. Using this information, we can determine which of
|
||||||
|
| the migrations on disk haven't actually been run on the database.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'migrations' => [
|
||||||
|
'table' => 'migrations',
|
||||||
|
'update_date_on_publish' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Redis Databases
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Redis is an open source, fast, and advanced key-value store that also
|
||||||
|
| provides a richer body of commands than a typical key-value system
|
||||||
|
| such as Memcached. You may define your connection settings here.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'redis' => [
|
||||||
|
|
||||||
|
'client' => env('REDIS_CLIENT', 'phpredis'),
|
||||||
|
|
||||||
|
'options' => [
|
||||||
|
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
||||||
|
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'default' => [
|
||||||
|
'url' => env('REDIS_URL'),
|
||||||
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||||
|
'username' => env('REDIS_USERNAME'),
|
||||||
|
'password' => env('REDIS_PASSWORD'),
|
||||||
|
'port' => env('REDIS_PORT', '6379'),
|
||||||
|
'database' => env('REDIS_DB', '0'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'queue' => [
|
||||||
|
'url' => env('REDIS_URL'),
|
||||||
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||||
|
'username' => env('REDIS_USERNAME'),
|
||||||
|
'password' => env('REDIS_PASSWORD'),
|
||||||
|
'port' => env('REDIS_PORT', '6379'),
|
||||||
|
'database' => env('REDIS_DB', '0'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'cache' => [
|
||||||
|
'url' => env('REDIS_URL'),
|
||||||
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||||
|
'username' => env('REDIS_USERNAME'),
|
||||||
|
'password' => env('REDIS_PASSWORD'),
|
||||||
|
'port' => env('REDIS_PORT', '6379'),
|
||||||
|
'database' => env('REDIS_CACHE_DB', '1'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'global_cache' => [
|
||||||
|
'url' => env('REDIS_URL'),
|
||||||
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||||
|
'username' => env('REDIS_USERNAME'),
|
||||||
|
'password' => env('REDIS_PASSWORD'),
|
||||||
|
'port' => env('REDIS_PORT', '6379'),
|
||||||
|
'database' => 3,
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
141
test.sh
141
test.sh
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
PERSISTENT=${PERSISTENT:-"0"}
|
||||||
|
FORCEREFRESH=${FORCEREFRESH:-"1"} # No config needed for this from 3.8.5/4.0 on
|
||||||
|
|
||||||
assert_queue_worker_running() {
|
assert_queue_worker_running() {
|
||||||
if docker compose ps -a --format '{{.Status}}' queue | grep -q "Exited"; then
|
if docker compose ps -a --format '{{.Status}}' queue | grep -q "Exited"; then
|
||||||
echo "ERR: Queue worker has exited!"
|
echo "ERR: Queue worker has exited!"
|
||||||
|
|
@ -28,14 +31,15 @@ assert_no_queue_failures() {
|
||||||
|
|
||||||
assert_tenant_users() {
|
assert_tenant_users() {
|
||||||
assert_no_queue_failures
|
assert_no_queue_failures
|
||||||
local expected_count=$1
|
local tenant=$1
|
||||||
test "$(sqlite3 src/database/tenantfoo.sqlite 'SELECT count(*) from USERS')" -eq "$expected_count" || { echo "ERR: Tenant DB expects $expected_count user(s)."; exit 1; }
|
local expected_count=$2
|
||||||
|
test "$(sqlite3 src/database/tenant${tenant}.sqlite 'SELECT count(*) from users')" -eq "$expected_count" || { echo "ERR: Tenant DB $tenant expects $expected_count user(s)."; exit 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_central_users() {
|
assert_central_users() {
|
||||||
assert_no_queue_failures
|
assert_no_queue_failures
|
||||||
local expected_count=$1
|
local expected_count=$1
|
||||||
test "$(sqlite3 src/database/database.sqlite 'SELECT count(*) from USERS')" -eq "$expected_count" || { echo "ERR: Central DB expects $expected_count user(s)."; exit 1; }
|
test "$(sqlite3 src/database/database.sqlite 'SELECT count(*) from users')" -eq "$expected_count" || { echo "ERR: Central DB expects $expected_count user(s)."; exit 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
without_queue_assertions() {
|
without_queue_assertions() {
|
||||||
|
|
@ -58,21 +62,39 @@ dispatch_central_job() {
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch_tenant_job() {
|
dispatch_tenant_job() {
|
||||||
echo "Dispatching job from tenant context..."
|
local tenant=$1
|
||||||
docker compose exec -T queue php artisan tinker --execute "App\\Models\\Tenant::first()->run(function () { dispatch(new App\Jobs\FooJob); });"
|
echo "Dispatching job from tenant ${tenant} context..."
|
||||||
|
docker compose exec -T queue php artisan tinker --execute "App\\Models\\Tenant::find('${tenant}')->run(function () { dispatch(new App\Jobs\FooJob); });"
|
||||||
sleep 5
|
sleep 5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expect_worker_context() {
|
||||||
|
expected_context="$1"
|
||||||
|
|
||||||
|
actual_context=$(cat src/jobprocessed_context)
|
||||||
|
|
||||||
|
if [ "$actual_context" = "$expected_context" ]; then
|
||||||
|
echo "OK: JobProcessed context is $expected_context"
|
||||||
|
else
|
||||||
|
if [ "$PERSISTENT" -eq 1 ]; then
|
||||||
|
echo "ERR: JobProcessed context is NOT $expected_context"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "WARN: JobProcessed context is NOT $expected_context"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
###################################### SETUP ######################################
|
###################################### SETUP ######################################
|
||||||
|
|
||||||
rm -f src/database.sqlite
|
rm -f src/database.sqlite
|
||||||
rm -f src/database/tenantfoo.sqlite
|
rm -f src/database/tenantfoo.sqlite
|
||||||
|
rm -f src/database/tenantbar.sqlite
|
||||||
|
|
||||||
docker compose up -d redis # in case it's not running - the below setup code needs Redis to be running
|
docker compose up -d redis # in case it's not running - the below setup code needs Redis to be running
|
||||||
|
|
||||||
docker compose run --rm queue php artisan migrate:fresh >/dev/null
|
docker compose run --rm queue php artisan migrate:fresh >/dev/null
|
||||||
docker compose run --rm queue php artisan tinker -v --execute "App\\Models\\Tenant::create(['id' => 'foo', 'tenancy_db_name' => 'tenantfoo.sqlite']);"
|
docker compose run --rm queue php artisan tinker -v --execute "App\\Models\\Tenant::create(['id' => 'foo', 'tenancy_db_name' => 'tenantfoo.sqlite']);App\\Models\\Tenant::create(['id' => 'bar', 'tenancy_db_name' => 'tenantbar.sqlite']);"
|
||||||
|
|
||||||
docker compose down; docker compose up -d --wait
|
docker compose down; docker compose up -d --wait
|
||||||
docker compose logs -f queue &
|
docker compose logs -f queue &
|
||||||
|
|
@ -80,27 +102,46 @@ docker compose logs -f queue &
|
||||||
# Kill any log watchers that may still be alive
|
# Kill any log watchers that may still be alive
|
||||||
trap "docker compose stop queue" EXIT
|
trap "docker compose stop queue" EXIT
|
||||||
|
|
||||||
echo "Setup complete, starting tests...\n"
|
echo "Setup complete, starting tests..."
|
||||||
|
|
||||||
################### BASIC PHASE: Assert jobs use the right context ###################
|
################### BASIC PHASE: Assert jobs use the right context ###################
|
||||||
|
echo
|
||||||
|
echo "-------- BASIC PHASE --------"
|
||||||
|
echo
|
||||||
|
|
||||||
dispatch_tenant_job
|
dispatch_tenant_job foo
|
||||||
assert_tenant_users 1
|
assert_tenant_users foo 1
|
||||||
|
assert_tenant_users bar 0
|
||||||
assert_central_users 0
|
assert_central_users 0
|
||||||
echo "OK: User created in tenant\n"
|
echo "OK: User created in tenant foo"
|
||||||
|
expect_worker_context tenant_foo
|
||||||
|
|
||||||
|
# Assert that the worker correctly distinguishes not just between tenant and central
|
||||||
|
# contexts, but also between different tenants.
|
||||||
|
dispatch_tenant_job bar
|
||||||
|
assert_tenant_users foo 1
|
||||||
|
assert_tenant_users bar 1
|
||||||
|
assert_central_users 0
|
||||||
|
echo "OK: User created in tenant bar"
|
||||||
|
expect_worker_context tenant_bar
|
||||||
|
|
||||||
dispatch_central_job
|
dispatch_central_job
|
||||||
assert_tenant_users 1
|
assert_tenant_users foo 1
|
||||||
|
assert_tenant_users bar 1
|
||||||
assert_central_users 1
|
assert_central_users 1
|
||||||
echo "OK: User created in central\n"
|
echo "OK: User created in central"
|
||||||
|
expect_worker_context central
|
||||||
|
|
||||||
############# RESTART PHASE: Assert the worker always responds to signals #############
|
############# RESTART PHASE: Assert the worker always responds to signals #############
|
||||||
|
echo
|
||||||
|
echo "-------- RESTART PHASE --------"
|
||||||
|
echo
|
||||||
|
|
||||||
echo "Running queue:restart (after a central job)..."
|
echo "Running queue:restart (after a central job)..."
|
||||||
docker compose exec -T queue php artisan queue:restart >/dev/null
|
docker compose exec -T queue php artisan queue:restart >/dev/null
|
||||||
sleep 5
|
sleep 5
|
||||||
assert_queue_worker_exited
|
assert_queue_worker_exited
|
||||||
echo "OK: Queue worker has exited\n"
|
echo "OK: Queue worker has exited"
|
||||||
|
|
||||||
echo "Starting queue worker again..."
|
echo "Starting queue worker again..."
|
||||||
docker compose restart queue
|
docker compose restart queue
|
||||||
|
|
@ -109,7 +150,7 @@ docker compose logs -f queue &
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
|
||||||
dispatch_tenant_job
|
dispatch_tenant_job foo
|
||||||
# IMPORTANT:
|
# IMPORTANT:
|
||||||
# If the worker remains in the tenant context after running a job
|
# If the worker remains in the tenant context after running a job
|
||||||
# it not only fails the final assertion here by not responding to queue:restart.
|
# it not only fails the final assertion here by not responding to queue:restart.
|
||||||
|
|
@ -119,12 +160,13 @@ dispatch_tenant_job
|
||||||
# Then, if the queue worker has shut down, we simply start it up again and continue
|
# Then, if the queue worker has shut down, we simply start it up again and continue
|
||||||
# with the tests. That said, if the warning has been printed, it should be pretty much
|
# with the tests. That said, if the warning has been printed, it should be pretty much
|
||||||
# guaranteed that the assertion about queue:restart post-tenant job will fail too.
|
# guaranteed that the assertion about queue:restart post-tenant job will fail too.
|
||||||
without_queue_assertions assert_tenant_users 2
|
without_queue_assertions assert_tenant_users foo 2
|
||||||
without_queue_assertions assert_central_users 1
|
without_queue_assertions assert_central_users 1
|
||||||
echo "OK: User created in tenant\n"
|
echo "OK: User created in tenant foo"
|
||||||
|
expect_worker_context tenant_foo
|
||||||
|
|
||||||
if docker compose ps -a --format '{{.Status}}' queue | grep -q "Exited"; then
|
if docker compose ps -a --format '{{.Status}}' queue | grep -q "Exited"; then
|
||||||
echo "WARN: Queue worker restarted after running a tenant job post-restart (https://github.com/archtechx/tenancy/issues/1229#issuecomment-2566111616) following assertions will likely fail."
|
echo "WARN: Queue worker restarted after running a tenant job post-restart (https://github.com/archtechx/tenancy/issues/1229#issuecomment-2566111616), following assertions will likely fail."
|
||||||
docker compose start queue # Start the worker back up
|
docker compose start queue # Start the worker back up
|
||||||
sleep 5
|
sleep 5
|
||||||
docker compose logs -f queue &
|
docker compose logs -f queue &
|
||||||
|
|
@ -140,14 +182,16 @@ fi
|
||||||
# This time, just to add more context, we can try to dispatch a central job first
|
# This time, just to add more context, we can try to dispatch a central job first
|
||||||
# in case it changes anything. But odds are that in broken setups we'll see both warnings.
|
# in case it changes anything. But odds are that in broken setups we'll see both warnings.
|
||||||
dispatch_central_job
|
dispatch_central_job
|
||||||
without_queue_assertions assert_tenant_users 2
|
without_queue_assertions assert_tenant_users foo 2
|
||||||
without_queue_assertions assert_central_users 2
|
without_queue_assertions assert_central_users 2
|
||||||
echo "OK: User created in central\n"
|
echo "OK: User created in central"
|
||||||
|
expect_worker_context central
|
||||||
|
|
||||||
dispatch_tenant_job
|
dispatch_tenant_job foo
|
||||||
without_queue_assertions assert_tenant_users 3
|
without_queue_assertions assert_tenant_users foo 3
|
||||||
without_queue_assertions assert_central_users 2
|
without_queue_assertions assert_central_users 2
|
||||||
echo "OK: User created in tenant\n"
|
echo "OK: User created in tenant foo"
|
||||||
|
expect_worker_context tenant_foo
|
||||||
|
|
||||||
if docker compose ps -a --format '{{.Status}}' queue | grep -q "Exited"; then
|
if docker compose ps -a --format '{{.Status}}' queue | grep -q "Exited"; then
|
||||||
echo "WARN: ANOTHER extra restart took place after running a tenant job"
|
echo "WARN: ANOTHER extra restart took place after running a tenant job"
|
||||||
|
|
@ -167,16 +211,61 @@ docker compose exec redis redis-cli -n 1 DEL laravel_database_illuminate:queue:r
|
||||||
|
|
||||||
# Also make the queue worker reload the value from cache
|
# Also make the queue worker reload the value from cache
|
||||||
docker compose restart queue
|
docker compose restart queue
|
||||||
docker compose logs -f queue &
|
# restart doesn't kill log watchers, so we don't need to create another one
|
||||||
|
|
||||||
# Finally, we dispatch a tenant job *immediately* before a restart.
|
# Finally, we dispatch a tenant job *immediately* before a restart.
|
||||||
dispatch_tenant_job
|
dispatch_tenant_job foo
|
||||||
assert_tenant_users 4
|
assert_tenant_users foo 4
|
||||||
assert_central_users 2
|
assert_central_users 2
|
||||||
echo "OK: User created in tenant\n"
|
echo "OK: User created in tenant foo"
|
||||||
|
expect_worker_context tenant_foo
|
||||||
|
|
||||||
echo "Running queue:restart (after a tenant job)..."
|
echo "Running queue:restart (after a tenant job)..."
|
||||||
docker compose exec -T queue php artisan queue:restart >/dev/null
|
docker compose exec -T queue php artisan queue:restart >/dev/null
|
||||||
sleep 5
|
sleep 5
|
||||||
assert_queue_worker_exited
|
assert_queue_worker_exited
|
||||||
echo "OK: Queue worker has exited"
|
echo "OK: Queue worker has exited"
|
||||||
|
|
||||||
|
############# SYNC PHASE: Assert that dispatching sync jobs doesn't affect outer context #############
|
||||||
|
echo
|
||||||
|
echo "-------- SYNC PHASE --------"
|
||||||
|
echo
|
||||||
|
|
||||||
|
docker compose run --rm queue php artisan tinker -v --execute "tenancy()->initialize('foo'); App\Jobs\FooJob::dispatchSync(); file_put_contents('sync_context', tenant() ? ('tenant_' . tenant('id')) : 'central');"
|
||||||
|
without_queue_assertions assert_tenant_users foo 5
|
||||||
|
without_queue_assertions assert_tenant_users bar 1
|
||||||
|
without_queue_assertions assert_central_users 2
|
||||||
|
|
||||||
|
if grep -q 'tenant_foo' src/sync_context; then
|
||||||
|
echo "OK: Sync dispatch preserved context"
|
||||||
|
else
|
||||||
|
echo "ERR: Sync dispatch changed context"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
######## REFRESH PHASE: Assert that the worker doesn't hold on to an outdated tenant instance ########
|
||||||
|
echo
|
||||||
|
echo "-------- REFRESH PHASE --------"
|
||||||
|
echo
|
||||||
|
|
||||||
|
docker compose start queue
|
||||||
|
sleep 5
|
||||||
|
docker compose logs -f queue &
|
||||||
|
dispatch_tenant_job bar
|
||||||
|
assert_tenant_users bar 2
|
||||||
|
assert_central_users 2
|
||||||
|
echo "OK: User created in tenant bar"
|
||||||
|
|
||||||
|
docker compose exec -T queue php artisan tinker --execute "\$tenant = App\Models\Tenant::find('bar'); \$tenant->update(['abc' => 'def']); \$tenant->run(function () { dispatch(new App\Jobs\LogAbcJob); });"
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
if grep -q 'def' src/abc; then
|
||||||
|
echo "OK: Worker notices changes made to the current tenant outside the worker"
|
||||||
|
else
|
||||||
|
if [ "$FORCEREFRESH" -eq 1 ]; then
|
||||||
|
echo "ERR: Worker does NOT notice changes made to the current tenant outside the worker"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "WARN: Worker does NOT notice changes made to the current tenant outside the worker"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue