toBeTrue(); foreach ([ 'id', 'tenant_id', 'deal_id', 'deal_received_at', 'tier_no', 'price_per_lead_kopecks', 'charged_at', 'created_at', ] as $col) { expect(Schema::hasColumn('lead_charges', $col))->toBeTrue("column {$col} missing"); } }); test('lead_charges has FK to deals (composite id, received_at)', function () { $fk = DB::selectOne( "SELECT pg_get_constraintdef(c.oid) AS def FROM pg_constraint c JOIN pg_class t ON c.conrelid = t.oid WHERE t.relname = 'lead_charges' AND c.contype = 'f' AND pg_get_constraintdef(c.oid) LIKE '%deals%' LIMIT 1" ); expect($fk)->not->toBeNull(); expect($fk->def) ->toContain('deals') ->toContain('received_at') ->toContain('id'); }); test('lead_charges enforces RLS on tenant_id', function () { // Готовим tenant + project + deal под superuser (BYPASSRLS), чтобы FK прошёл. $a = Tenant::factory()->create(); $b = Tenant::factory()->create(); $projectA = Project::factory()->for($a)->create(); $dealA = Deal::factory()->for($a)->for($projectA)->create(); // Переключаемся на NO-BYPASSRLS роль для проверки изоляции. DB::statement('SET LOCAL ROLE testing_rls_user'); DB::statement("SET LOCAL app.current_tenant_id = '{$a->id}'"); DB::table('lead_charges')->insert([ 'tenant_id' => $a->id, 'deal_id' => $dealA->id, 'deal_received_at' => $dealA->received_at, 'tier_no' => 1, 'price_per_lead_kopecks' => 6000, 'charged_at' => now(), 'created_at' => now(), ]); // Под tenant A — видно $countA = DB::table('lead_charges')->count(); // Переключаемся на tenant B — RLS должен скрыть DB::statement("SET LOCAL app.current_tenant_id = '{$b->id}'"); $countB = DB::table('lead_charges')->count(); expect($countA)->toBe(1); expect($countB)->toBe(0, 'tenant B must not see tenant A charges (RLS leak)'); });