table('saas_admin_users')->insertGetId([ 'email' => 'pd-svc-stub-'.uniqid().'@system.local', 'full_name' => 'PdErasureService Test Stub', 'password_hash' => '$2y$04$system-stub-not-loginable', 'role' => 'super_admin', 'is_active' => false, 'sso_provider' => 'local', 'is_break_glass' => false, ]); } /** Создать тенант через pgsql_supplier и вернуть id. */ function pdSvcCreateTenant(): int { return (int) DB::connection('pgsql_supplier')->table('tenants')->insertGetId([ 'subdomain' => 'pd-svc-'.uniqid(), 'organization_name' => 'PdErasureService Test Org', 'contact_email' => 'pd-svc@test.local', 'status' => 'active', 'balance_rub' => '0.00', 'balance_leads' => 0, 'is_trial' => false, 'chargeback_unrecovered_rub' => '0.00', 'created_at' => now(), ]); } // --------------------------------------------------------------------------- // Tests — описания содержат "PdErasureService" для точечного --filter. // --------------------------------------------------------------------------- it('PdErasureService surgically removes subject phone from a co-contact deal JSONB without touching scalar owner data (F-P1b)', function (): void { $adminId = pdSvcStubAdmin(); $tenantId = pdSvcCreateTenant(); $victimPhone = '+790'.random_int(1000000, 9999999); $ownerPhone = '+791'.random_int(1000000, 9999999); $coContactPhone = '+792'.random_int(1000000, 9999999); // Сделка принадлежит другому человеку (ownerPhone); телефон субъекта — // лишь дополнительный в JSONB phones рядом с телефоном со-контакта. $dealId = (int) DB::connection('pgsql_supplier')->table('deals')->insertGetId([ 'tenant_id' => $tenantId, 'project_id' => 1, 'phone' => $ownerPhone, 'phones' => json_encode([$victimPhone, $coContactPhone]), 'contact_name' => 'Владелец', 'received_at' => now(), 'created_at' => now(), ]); $counts = app(PdErasureService::class) ->eraseSubject(null, $victimPhone, $tenantId, $adminId, null); expect($counts['deals'])->toBeGreaterThanOrEqual(1); $deal = DB::connection('pgsql_supplier')->table('deals')->where('id', $dealId)->first(); // Скалярные ПДн владельца сделки НЕ трогаются. expect($deal->phone)->toBe($ownerPhone); expect($deal->contact_name)->toBe('Владелец'); // Телефон субъекта вычищен из массива, со-контакт сохранён (хирургично). $phones = json_decode($deal->phones ?? '[]', true); expect($phones)->not->toContain($victimPhone); expect($phones)->toContain($coContactPhone); }); it('PdErasureService preserves co-contact phones when erasing the deal owner (F-P1b)', function (): void { $adminId = pdSvcStubAdmin(); $tenantId = pdSvcCreateTenant(); $victimPhone = '+790'.random_int(1000000, 9999999); $coContactPhone = '+792'.random_int(1000000, 9999999); // Субъект — владелец сделки (скалярный phone), плюс его номер и со-контакт // в JSONB phones. $dealId = (int) DB::connection('pgsql_supplier')->table('deals')->insertGetId([ 'tenant_id' => $tenantId, 'project_id' => 1, 'phone' => $victimPhone, 'phones' => json_encode([$victimPhone, $coContactPhone]), 'contact_name' => 'Жертва', 'received_at' => now(), 'created_at' => now(), ]); $counts = app(PdErasureService::class) ->eraseSubject(null, $victimPhone, $tenantId, $adminId, null); expect($counts['deals'])->toBeGreaterThanOrEqual(1); $deal = DB::connection('pgsql_supplier')->table('deals')->where('id', $dealId)->first(); // Скалярные ПДн владельца-субъекта анонимизированы. expect($deal->phone)->toBe('+7000XXXXXXX'); expect($deal->contact_name)->toBe('Удалено'); // Телефон субъекта убран из массива, со-контакт сохранён. $phones = json_decode($deal->phones ?? '[]', true); expect($phones)->not->toContain($victimPhone); expect($phones)->toContain($coContactPhone); }); it('PdErasureService does not touch deals when only email is given — deals have no email (F-P1b no-op)', function (): void { $adminId = pdSvcStubAdmin(); $tenantId = pdSvcCreateTenant(); $dealPhone = '+790'.random_int(1000000, 9999999); $dealId = (int) DB::connection('pgsql_supplier')->table('deals')->insertGetId([ 'tenant_id' => $tenantId, 'project_id' => 1, 'phone' => $dealPhone, 'phones' => json_encode([$dealPhone]), 'contact_name' => 'Нетронутый', 'received_at' => now(), 'created_at' => now(), ]); // Удаление только по email — в deals нет email, сделки не сопоставляются. $counts = app(PdErasureService::class) ->eraseSubject('email-only-'.uniqid().'@example.com', null, $tenantId, $adminId, null); expect($counts['deals'])->toBe(0); $deal = DB::connection('pgsql_supplier')->table('deals')->where('id', $dealId)->first(); expect($deal->phone)->toBe($dealPhone); expect($deal->contact_name)->toBe('Нетронутый'); });