Add unique constraints (one migration, no dup data in either DB): - users.email, users.national_code (M16) — NULLs still allowed. - payments.gateway_token (M17). - date_overrides (doctor_id, date) (M18) — was a non-unique index. - financial_breakdowns (payment_id, source) (M19) — anti double-accounting. Regression: tests/Database/UniqueConstraintsTest (4 duplicate-insert cases). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
75 lines
2.5 KiB
PHP
75 lines
2.5 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\Database;
|
|
|
|
use App\Appointment\Entity\DateOverride;
|
|
use App\Doctor\Entity\Doctor;
|
|
use App\Payment\Entity\Payment;
|
|
use App\Settlement\Entity\FinancialBreakdown;
|
|
use App\Tests\ApiTestCase;
|
|
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
|
|
|
/**
|
|
* Business-key uniqueness: duplicate email / national_code / gateway_token,
|
|
* a second date-override for the same (doctor, date), and a second financial
|
|
* breakdown for the same (payment, source) must all be rejected at the DB.
|
|
*/
|
|
class UniqueConstraintsTest extends ApiTestCase
|
|
{
|
|
public function testDuplicateEmailRejected(): void
|
|
{
|
|
$email = 'dup' . bin2hex(random_bytes(5)) . '@test.local';
|
|
$this->createUser()->setEmail($email);
|
|
$this->createUser()->setEmail($email);
|
|
|
|
$this->expectException(UniqueConstraintViolationException::class);
|
|
$this->em->flush();
|
|
}
|
|
|
|
public function testDuplicateGatewayTokenRejected(): void
|
|
{
|
|
$token = 'tok-' . bin2hex(random_bytes(6));
|
|
$a = new Payment($this->createUser(), 1000, 'mellat', Payment::TYPE_SUBSCRIPTION);
|
|
$a->setGatewayToken($token);
|
|
$b = new Payment($this->createUser(), 1000, 'mellat', Payment::TYPE_SUBSCRIPTION);
|
|
$b->setGatewayToken($token);
|
|
$this->em->persist($a);
|
|
$this->em->persist($b);
|
|
|
|
$this->expectException(UniqueConstraintViolationException::class);
|
|
$this->em->flush();
|
|
}
|
|
|
|
public function testDuplicateDateOverrideRejected(): void
|
|
{
|
|
$doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر');
|
|
$this->em->persist($doctor);
|
|
$this->em->flush();
|
|
|
|
$date = time();
|
|
$this->em->persist(new DateOverride($doctor, $date));
|
|
$this->em->persist(new DateOverride($doctor, $date));
|
|
|
|
$this->expectException(UniqueConstraintViolationException::class);
|
|
$this->em->flush();
|
|
}
|
|
|
|
public function testDuplicateBreakdownPaymentSourceRejected(): void
|
|
{
|
|
$user = $this->createUser();
|
|
$payment = new Payment($user, 100_000, 'mellat', Payment::TYPE_APPOINTMENT);
|
|
$this->em->persist($payment);
|
|
$this->em->flush();
|
|
|
|
$make = fn () => new FinancialBreakdown(
|
|
$payment, FinancialBreakdown::SOURCE_APPOINTMENT, $user,
|
|
100_000, 0, '0.00', 0, 100_000, '10.00', 10_000, 90_000, null, null, null,
|
|
);
|
|
$this->em->persist($make());
|
|
$this->em->persist($make());
|
|
|
|
$this->expectException(UniqueConstraintViolationException::class);
|
|
$this->em->flush();
|
|
}
|
|
}
|