Skip to content

Instantly share code, notes, and snippets.

@drfraker
Created October 19, 2019 16:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save drfraker/ff702e76d793abe14fb82dce1cc4b1fa to your computer and use it in GitHub Desktop.
Save drfraker/ff702e76d793abe14fb82dce1cc4b1fa to your computer and use it in GitHub Desktop.
Tenancy Testing Help
<?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 all database work. Of course
| you may use many connections at once using the Database library.
|
*/
'default' => env('DB_CONNECTION', 'system'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Here are each of the database connections setup for your application.
| Of course, examples of configuring each database platform that is
| supported by Laravel is shown below to make development simple.
|
|
| All database work in Laravel is done through the PHP PDO facilities
| so make sure you have the driver for your particular database of
| choice installed on your machine before you begin development.
|
*/
'connections' => [
'system' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'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'),
]) : [],
],
'tenant' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'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'),
]) : [],
],
],
/*
|--------------------------------------------------------------------------
| 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 in the database.
|
*/
'migrations' => 'migrations',
/*
|--------------------------------------------------------------------------
| 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 APC or Memcached. Laravel makes it easy to dig right in.
|
*/
'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'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_DB', 0),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_CACHE_DB', 1),
],
],
];
<?php
namespace App\Http\Controllers\Auth;
use App\Owner;
use App\Organization;
use App\Billing\PaymentGateway;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use App\Http\Requests\RegistrationRequest;
class RegisterController extends Controller
{
public function __construct()
{
$this->middleware('guest');
}
public function index()
{
return view('auth.register');
}
public function store(RegistrationRequest $request)
{
tenancy()->create([$request->subdomain]);
tenancy()->init($request->subdomain);
// Add info to the tenant's private database.
$this->createNewAccount($request);
return redirect(route('dashboard'));
}
protected function createNewAccount($request)
{
return DB::transaction(function () use ($request) {
$subscription = $this->createOnStripe($request);
$organization = Organization::create([
'name' => $request->business_name,
'email' => $request->business_email,
'phone' => $request->business_phone,
'subdomain' => $request->subdomain,
'stripe_id' => $subscription->customer,
]);
$organization->subscription()->create([
'name' => 'default',
'stripe_id' => $subscription->id,
'stripe_status' => $subscription->status,
'stripe_plan' => $subscription->plan->id,
'quantity' => $subscription->quantity,
'trial_ends_at' => $subscription->trial_end,
'ends_at' => $subscription->current_period_end,
]);
Owner::create([
'organization_id' => $organization->id,
'first_name' => $request->first_name,
'last_name' => $request->last_name,
'email' => $request->admin_email,
'password' => Hash::make($request->password),
]);
return $organization;
});
}
protected function createOnStripe($request)
{
$paymentGateway = app(PaymentGateway::class);
$stripeCustomer = $paymentGateway->createCustomer($request->payment_token, ['email' => $request->business_email]);
$subscription = $paymentGateway->createSubscription($stripeCustomer->id, 'main_plan');
return $subscription;
}
}
<?php
namespace Tests\Feature;
use App\Owner;
use Tests\TestCase;
use App\Organization;
use App\Billing\PaymentGateway;
use App\Billing\FakePaymentGateway;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Hash;
use Illuminate\Foundation\Testing\RefreshDatabase;
class RegisterOrganizationTest extends TestCase
{
use RefreshDatabase;
/**
* @var FakePaymentGateway
*/
protected $paymentGateway;
public function setUp(): void
{
parent::setUp();
Bus::fake();
$this->paymentGateway = new FakePaymentGateway();
$this->app->instance(PaymentGateway::class, $this->paymentGateway);
}
/** @test */
public function it_creates_an_organization_and_an_owner_when_registration_is_successful()
{
$response = $this->post(route('register'), $this->getValidRegisterFormData());
$response->assertRedirect(route('dashboard'));
$this->assertDatabaseHas('organizations', [
'name' => 'biz name',
'email' => 'biz@example.com',
'phone' => '444-444-4444',
]);
$this->assertDatabaseHas('owners', [
'first_name' => 'john',
'last_name' => 'doe',
'email' => 'john@example.com',
]);
$this->assertTrue(Hash::check('secret', Owner::first()->password));
}
/** @test */
public function after_successful_registration_owner_belongs_to_organization()
{
$this->withoutExceptionHandling();
$this->post(route('register'), $this->getValidRegisterFormData());
$organization = Organization::with('owner')->where('email', 'biz@example.com')->first();
$this->assertEquals("john@example.com", $organization->owner->email);
}
private function getValidRegisterFormData($overrides = [])
{
return array_merge([
'business_name' => 'biz name',
'business_email' => 'biz@example.com',
'business_phone' => '444-444-4444',
'subdomain' => 'acme',
'first_name' => 'john',
'last_name' => 'doe',
'admin_email' => 'john@example.com',
'password' => 'secret',
'payment_token' => 'tok_visa',
], $overrides);
}
}
<?php
declare(strict_types=1);
return [
'storage_driver' => 'db',
'storage_drivers' => [
'db' => [
'driver' => Stancl\Tenancy\StorageDrivers\Database\DatabaseStorageDriver::class,
'data_column' => 'data',
'custom_columns' => [
// 'plan',
],
'connection' => null,
'table_names' => [
'TenantModel' => 'tenants',
'DomainModel' => 'domains',
],
],
'redis' => [
'driver' => Stancl\Tenancy\StorageDrivers\RedisStorageDriver::class,
'connection' => 'tenancy',
],
],
'tenant_route_namespace' => 'App\Http\Controllers',
'exempt_domains' => [ // e.g. domains which host landing pages, sign up pages, etc
'qn2020.test',
'localhost',
],
'database' => [
'based_on' => 'tenant', // The connection that will be used as a base for the dynamically created tenant connection.
'prefix' => 'tenant_',
'suffix' => '',
],
'redis' => [
'prefix_base' => 'tenant',
'prefixed_connections' => [
// 'default',
],
],
'cache' => [
'tag_base' => 'tenant',
],
'filesystem' => [ // https://tenancy.samuelstancl.me/docs/v2/filesystem-tenancy/
'suffix_base' => 'tenant',
// Disks which should be suffixed with the suffix_base + tenant id.
'disks' => [
'local',
'public',
// 's3',
],
'root_override' => [
// Disks whose roots should be overriden after storage_path() is suffixed.
'local' => '%storage_path%/app/',
'public' => '%storage_path%/app/public/',
],
],
'database_managers' => [
// Tenant database managers handle the creation & deletion of tenant databases.
//'sqlite' => Stancl\Tenancy\TenantDatabaseManagers\SQLiteDatabaseManager::class,
'mysql' => Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager::class,
//'pgsql' => Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLDatabaseManager::class,
],
'database_manager_connections' => [
// Connections used by TenantDatabaseManagers. This tells, for example, the
// MySQLDatabaseManager to use the mysql connection to create databases.
//'sqlite' => 'sqlite',
'mysql' => 'system',
//'pgsql' => 'pgsql',
],
'bootstrappers' => [
// Tenancy bootstrappers are executed when tenancy is initialized.
// Their responsibility is making Laravel features tenant-aware.
'database' => Stancl\Tenancy\TenancyBootstrappers\DatabaseTenancyBootstrapper::class,
'cache' => Stancl\Tenancy\TenancyBootstrappers\CacheTenancyBootstrapper::class,
'filesystem' => Stancl\Tenancy\TenancyBootstrappers\FilesystemTenancyBootstrapper::class,
'queue' => Stancl\Tenancy\TenancyBootstrappers\QueueTenancyBootstrapper::class,
// 'redis' => Stancl\Tenancy\TenancyBootstrappers\RedisTenancyBootstrapper::class, // Note: phpredis is needed
],
'features' => [
// Features are classes that provide additional functionality
// not needed for tenancy to be bootstrapped. They are run
// regardless of whether tenancy has been initialized.
// Stancl\Tenancy\Features\TenantConfig::class,
// Stancl\Tenancy\Features\TelescopeTags::class,
// Stancl\Tenancy\Features\TenantRedirect::class,
],
'storage_to_config_map' => [ // Used by the TenantConfig feature
// 'paypal_api_key' => 'services.paypal.api_key',
],
'home_url' => '/dashboard',
'queue_database_creation' => false,
'migrate_after_creation' => true, // run migrations after creating a tenant
'seed_after_migration' => false, // should the seeder run after automatic migration
'seeder_parameters' => [
'--class' => 'DatabaseSeeder', // root seeder class to run after automatic migrations, eg: 'DatabaseSeeder'
],
'queue_database_deletion' => false,
'delete_database_after_tenant_deletion' => false, // delete the tenant's database after deleting the tenant
'unique_id_generator' => Stancl\Tenancy\UniqueIDGenerators\UUIDGenerator::class,
];
<?php
namespace Tests;
use Stancl\Tenancy\Tenant;
use Illuminate\Support\Facades\DB;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
public function setUp(): void
{
parent::setUp();
$this->beforeApplicationDestroyed(function () {
// Delete all tenant databases;
tenancy()->all()->each(function (Tenant $t) {
$dbName = $t->getDatabaseName();
DB::connection('tenant')->statement("DROP DATABASE IF EXISTS `$dbName`");
});
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment