Files
HRM-System/tests/Feature/TenantIsolationTest.php

170 lines
5.3 KiB
PHP

<?php
use App\Models\User;
use App\Models\Inquiry;
use App\Models\Tenant;
use App\Jobs\ProvisionTenantJob;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Stancl\Tenancy\Database\Models\Domain;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('subdomain inquiry requires a unique desired_subdomain', function () {
Tenant::create([
'id' => 'company-alpha',
'tenancy_db_name' => 'tenant_company-alpha',
'company_name' => 'Alpha Co',
'email' => 'alpha@company.com',
]);
Domain::create([
'domain' => 'company-alpha.localhost',
'tenant_id' => 'company-alpha',
]);
$response = $this->postJson('/api/inquiries', [
'company_name' => 'Alpha Copycat',
'contact_name' => 'John Doe',
'email' => 'john@copycat.com',
'phone' => '1234567890',
'desired_subdomain' => 'company-alpha',
]);
$response->assertStatus(422)
->assertJsonValidationErrors(['desired_subdomain']);
});
test('executive cannot approve inquiry with an already taken subdomain', function () {
$executive = User::factory()->create(['type' => 'company admin']);
Tenant::create([
'id' => 'existing-tenant',
'tenancy_db_name' => 'tenant_existing-tenant',
'company_name' => 'Existing',
'email' => 'existing@tenant.com',
]);
$inquiry = Inquiry::create([
'company_name' => 'New Corp',
'contact_name' => 'Jane Doe',
'email' => 'jane@newcorp.com',
'desired_subdomain' => 'new-corp',
'status' => 'pending_payment_approval',
]);
$response = $this->actingAs($executive, 'sanctum')
->postJson("/api/executive/inquiries/{$inquiry->id}/approve", [
'desired_subdomain' => 'existing-tenant',
]);
$response->assertStatus(422)
->assertJson(['success' => false]);
});
test('provisioning tenant creates isolated database file', function () {
$subdomain = 'beta-tech-' . strtolower(Str::random(4));
$inquiry = Inquiry::create([
'company_name' => 'Beta Tech',
'contact_name' => 'Beta Admin',
'email' => 'admin@betatech.com',
'desired_subdomain' => $subdomain,
'status' => 'approved',
]);
$job = new ProvisionTenantJob($inquiry);
$job->handle();
$this->assertDatabaseHas('tenants', [
'id' => $subdomain,
]);
$dbFile = database_path('tenant_' . $subdomain);
$this->assertTrue(file_exists($dbFile) || file_exists($dbFile . '.sqlite'));
if (file_exists($dbFile)) { @unlink($dbFile); }
if (file_exists($dbFile . '.sqlite')) { @unlink($dbFile . '.sqlite'); }
});
test('user from Tenant A cannot log in on Tenant B subdomain', function () {
$subA = 'ten-a-' . strtolower(Str::random(5));
$inquiryA = Inquiry::create([
'company_name' => 'Company A',
'contact_name' => 'Admin A',
'email' => 'admin@tenanta.com',
'desired_subdomain' => $subA,
'status' => 'approved',
]);
(new ProvisionTenantJob($inquiryA))->handle();
$subB = 'ten-b-' . strtolower(Str::random(5));
$inquiryB = Inquiry::create([
'company_name' => 'Company B',
'contact_name' => 'Admin B',
'email' => 'admin@tenantb.com',
'desired_subdomain' => $subB,
'status' => 'approved',
]);
(new ProvisionTenantJob($inquiryB))->handle();
$tenantB = Tenant::find($subB);
// Attempting login on Tenant B with Tenant A credentials
tenancy()->initialize($tenantB);
$loginRequest = new \App\Http\Requests\Auth\LoginRequest();
$loginRequest->merge([
'email' => 'admin@tenanta.com',
'password' => '12345678',
]);
try {
$loginRequest->authenticate();
$this->fail('Expected ValidationException was not thrown for cross-login attempt.');
} catch (\Illuminate\Validation\ValidationException $e) {
$this->assertArrayHasKey('email', $e->errors());
} finally {
tenancy()->end();
DB::purge('tenant');
}
});
test('HTTP POST login request from Tenant A user fails on Tenant B subdomain', function () {
$subA = 'ca-' . strtolower(Str::random(5));
$inquiryA = Inquiry::create([
'company_name' => 'Company A',
'contact_name' => 'Admin A',
'email' => 'admin@compa.com',
'desired_subdomain' => $subA,
'status' => 'approved',
]);
(new ProvisionTenantJob($inquiryA))->handle();
$subB = 'cb-' . strtolower(Str::random(5));
$inquiryB = Inquiry::create([
'company_name' => 'Company B',
'contact_name' => 'Admin B',
'email' => 'admin@compb.com',
'desired_subdomain' => $subB,
'status' => 'approved',
]);
(new ProvisionTenantJob($inquiryB))->handle();
try {
// Attempt POST to login on Company B domain with Company A credentials
$response = $this->withServerVariables(['HTTP_HOST' => "{$subB}.localhost"])
->post("http://{$subB}.localhost/login", [
'email' => 'admin@compa.com',
'password' => '12345678',
]);
$this->assertGuest();
$this->assertNotSame('/dashboard', parse_url($response->headers->get('Location', ''), PHP_URL_PATH));
} finally {
DB::purge('tenant');
}
});