chore: add pgsql FK isolation checks

This commit is contained in:
Ahmed Darrazi 2026-02-15 13:39:39 +01:00
parent c905f211a5
commit f45e0f5cf1
4 changed files with 203 additions and 0 deletions

View File

@ -55,6 +55,9 @@
"@php artisan config:clear --ansi",
"@php artisan test"
],
"test:pgsql": [
"@php vendor/bin/pest -c phpunit.pgsql.xml"
],
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi",

View File

@ -0,0 +1,102 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* @return array<int, string>
*/
private function tenantOwnedTables(): array
{
return [
'policies',
'policy_versions',
'backup_sets',
'backup_items',
'restore_runs',
'backup_schedules',
'inventory_items',
'inventory_links',
'entra_groups',
'findings',
'entra_role_definitions',
'tenant_permissions',
];
}
private function ensureWorkspaceForeignKey(string $tableName, string $constraintName): void
{
// Add as NOT VALID (fast) then VALIDATE (safe) — Postgres best practice.
DB::unprepared(<<<SQL
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_constraint
WHERE conname = '{$constraintName}'
) THEN
EXECUTE format(
'ALTER TABLE %I ADD CONSTRAINT %I FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE NOT VALID',
'{$tableName}',
'{$constraintName}'
);
END IF;
IF EXISTS (
SELECT 1
FROM pg_constraint
WHERE conname = '{$constraintName}'
AND convalidated = false
) THEN
EXECUTE format(
'ALTER TABLE %I VALIDATE CONSTRAINT %I',
'{$tableName}',
'{$constraintName}'
);
END IF;
END
$$;
SQL);
}
public function up(): void
{
if (DB::getDriverName() !== 'pgsql') {
return;
}
foreach ($this->tenantOwnedTables() as $tableName) {
$constraintName = sprintf('%s_workspace_fk', $tableName);
$this->ensureWorkspaceForeignKey($tableName, $constraintName);
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
if (DB::getDriverName() !== 'pgsql') {
return;
}
foreach ($this->tenantOwnedTables() as $tableName) {
$constraintName = sprintf('%s_workspace_fk', $tableName);
DB::unprepared(<<<SQL
DO $$
BEGIN
EXECUTE format(
'ALTER TABLE %I DROP CONSTRAINT IF EXISTS %I',
'{$tableName}',
'{$constraintName}'
);
END
$$;
SQL);
}
}
};

50
phpunit.pgsql.xml Normal file
View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
</include>
</source>
<php>
<ini name="memory_limit" value="2048M"/>
<env name="APP_ENV" value="testing"/>
<env name="APP_KEY" value="base64:z63PQuXp3rUOQ0L4o8xp76xeakrn5X3owja1qFX3ccY="/>
<env name="INTUNE_TENANT_ID" value="" force="true"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="BROADCAST_CONNECTION" value="null"/>
<env name="CACHE_STORE" value="array"/>
<!-- Postgres-backed test configuration (for schema-level assertions, FK validation, etc.) -->
<env name="DB_CONNECTION" value="pgsql"/>
<env name="DB_HOST" value="pgsql"/>
<env name="DB_PORT" value="5432"/>
<env name="DB_DATABASE" value="tenantatlas_testing"/>
<env name="DB_USERNAME" value="root"/>
<env name="DB_PASSWORD" value="postgres"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="PULSE_ENABLED" value="false"/>
<env name="TELESCOPE_ENABLED" value="false"/>
<env name="NIGHTWATCH_ENABLED" value="false"/>
</php>
</phpunit>

View File

@ -0,0 +1,48 @@
<?php
use Illuminate\Support\Facades\DB;
it('enforces workspace_id foreign keys on tenant-owned tables', function () {
if (DB::getDriverName() !== 'pgsql') {
$this->markTestSkipped('Postgres-only: validates FK constraints via pg_constraint.');
}
$tables = [
'policies',
'policy_versions',
'backup_sets',
'backup_items',
'restore_runs',
'backup_schedules',
'inventory_items',
'inventory_links',
'entra_groups',
'findings',
'entra_role_definitions',
'tenant_permissions',
];
foreach ($tables as $table) {
$sql = <<<'SQL'
SELECT c.conname, c.convalidated
FROM pg_constraint c
JOIN pg_class rel ON rel.oid = c.conrelid
JOIN pg_class ref ON ref.oid = c.confrelid
JOIN pg_attribute att ON att.attrelid = rel.oid AND att.attnum = ANY(c.conkey)
WHERE c.contype = 'f'
AND rel.relname = ?
AND ref.relname = 'workspaces'
AND att.attname = 'workspace_id'
SQL;
$constraints = DB::select(
$sql,
[$table],
);
expect($constraints)->not->toBeEmpty();
$allValidated = collect($constraints)->every(fn ($c): bool => (bool) $c->convalidated);
expect($allValidated)->toBeTrue();
}
});