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

144 lines
5.2 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Inquiry;
use App\Jobs\ProvisionTenantJob;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\Tenant;
use Tests\TestCase;
class TenantApiAuthTest extends TestCase
{
use RefreshDatabase;
public function test_api_login_authenticates_against_isolated_tenant_database(): void
{
// 1. Create Tenant A
$inquiryA = Inquiry::create([
'company_name' => 'API Company A',
'contact_name' => 'API Admin A',
'email' => 'admin@api-comp-a.com',
'desired_subdomain' => 'api-comp-a',
'email_verified_at' => now(),
'status' => 'approved',
]);
(new ProvisionTenantJob($inquiryA))->handle();
// 2. Create Tenant B
$inquiryB = Inquiry::create([
'company_name' => 'API Company B',
'contact_name' => 'API Admin B',
'email' => 'admin@api-comp-b.com',
'desired_subdomain' => 'api-comp-b',
'email_verified_at' => now(),
'status' => 'approved',
]);
(new ProvisionTenantJob($inquiryB))->handle();
// 3. Login to Tenant A via API with X-Tenant header using Tenant A credentials -> SUCCESS
if (function_exists('tenancy') && tenancy()->initialized) {
tenancy()->end();
}
$responseA = $this->withHeaders(['X-Tenant' => 'api-comp-a'])
->postJson('/api/auth/login', [
'email' => 'admin@api-comp-a.com',
'password' => '12345678',
]);
$responseA->assertStatus(200)
->assertJsonPath('success', true);
// 4. Try logging into Tenant A via API using actual Tenant B credentials -> FAILS 401
if (function_exists('tenancy') && tenancy()->initialized) {
tenancy()->end();
}
$responseCross = $this->withHeaders(['X-Tenant' => 'api-comp-a'])
->postJson('/api/auth/login', [
'email' => 'admin@api-comp-b.com',
'password' => '12345678',
]);
$responseCross->assertStatus(401)
->assertJsonPath('success', false);
// 5. Try logging in without tenant context (no header, no subdomain, no domain host) -> FAILS 422
if (function_exists('tenancy') && tenancy()->initialized) {
tenancy()->end();
}
$responseNoTenant = $this->withHeaders(['X-Tenant' => ''])->postJson('/api/auth/login', [
'email' => 'admin@api-comp-a.com',
'password' => '12345678',
]);
$responseNoTenant->assertStatus(422)
->assertJsonPath('message', 'Tenant context is required.');
// 6. Verify API token is created inside Tenant A database
$tenantA = Tenant::find('api-comp-a');
$tenantA->run(function () {
$userA = \App\Models\User::where('email', 'admin@api-comp-a.com')->first();
$this->assertNotNull($userA);
$this->assertGreaterThan(0, $userA->tokens()->count());
});
}
public function test_host_based_mobile_login_and_authenticated_request_are_tenant_isolated(): void
{
$inquiryA = Inquiry::create([
'company_name' => 'Mobile Host Company A',
'contact_name' => 'Mobile Admin A',
'email' => 'admin@mobile-host-a.com',
'desired_subdomain' => 'mobile-host-a',
'email_verified_at' => now(),
'status' => 'approved',
]);
(new ProvisionTenantJob($inquiryA))->handle();
$inquiryB = Inquiry::create([
'company_name' => 'Mobile Host Company B',
'contact_name' => 'Mobile Admin B',
'email' => 'admin@mobile-host-b.com',
'desired_subdomain' => 'mobile-host-b',
'email_verified_at' => now(),
'status' => 'approved',
]);
(new ProvisionTenantJob($inquiryB))->handle();
tenancy()->end();
$domainHost = config('app.url_domain', parse_url(config('app.url'), PHP_URL_HOST) ?: 'localhost');
$hostA = "mobile-host-a.{$domainHost}";
$loginA = $this->withServerVariables(['HTTP_HOST' => $hostA])
->postJson("http://{$hostA}/api/auth/login", [
'email' => 'admin@mobile-host-a.com',
'password' => '12345678',
'device_name' => 'mobile-a',
]);
$loginA->assertStatus(200)->assertJsonPath('success', true);
$tokenA = $loginA->json('token');
$this->assertNotEmpty($tokenA);
$userA = $this->withServerVariables(['HTTP_HOST' => $hostA])
->withHeader('Authorization', "Bearer {$tokenA}")
->getJson("http://{$hostA}/api/user");
$userA->assertStatus(200)->assertJsonPath('user.email', 'admin@mobile-host-a.com');
tenancy()->end();
$crossLogin = $this->withServerVariables(['HTTP_HOST' => $hostA])
->postJson("http://{$hostA}/api/auth/login", [
'email' => 'admin@mobile-host-b.com',
'password' => '12345678',
'device_name' => 'mobile-cross-tenant',
]);
$crossLogin->assertStatus(401)->assertJsonPath('success', false);
}
}