From 37418424895e2eed68235f86e44f749a56c3d5a1 Mon Sep 17 00:00:00 2001 From: kirill Date: Sun, 7 Dec 2025 16:27:54 +0300 Subject: [PATCH 01/31] . --- src/ContactPersons/Entity/ContactPerson.php | 2 +- .../UseCase/Install/Command.php | 44 +++++ .../UseCase/Install/Handler.php | 68 ++++++++ .../Builders/ContactPersonBuilder.php | 132 +++++++++++++++ .../UseCase/InstallStart/HandlerTest.php | 150 ++++++++++++++++++ 5 files changed, 395 insertions(+), 1 deletion(-) create mode 100644 src/ContactPersons/UseCase/Install/Command.php create mode 100644 src/ContactPersons/UseCase/Install/Handler.php create mode 100644 tests/Functional/ContactPersons/Builders/ContactPersonBuilder.php create mode 100644 tests/Functional/ContactPersons/UseCase/InstallStart/HandlerTest.php diff --git a/src/ContactPersons/Entity/ContactPerson.php b/src/ContactPersons/Entity/ContactPerson.php index 2b250ef..e9d70a1 100644 --- a/src/ContactPersons/Entity/ContactPerson.php +++ b/src/ContactPersons/Entity/ContactPerson.php @@ -47,7 +47,7 @@ public function __construct( private readonly ?int $bitrix24UserId, private ?Uuid $bitrix24PartnerId, private readonly ?UserAgentInfo $userAgentInfo, - private bool $isEmitContactPersonCreatedEvent = false, + private readonly bool $isEmitContactPersonCreatedEvent = false, ) { $this->createdAt = new CarbonImmutable(); $this->updatedAt = new CarbonImmutable(); diff --git a/src/ContactPersons/UseCase/Install/Command.php b/src/ContactPersons/UseCase/Install/Command.php new file mode 100644 index 0000000..0243b3c --- /dev/null +++ b/src/ContactPersons/UseCase/Install/Command.php @@ -0,0 +1,44 @@ +validate(); + } + + private function validate(): void + { + if ('' === $this->fullName->name) { + throw new \InvalidArgumentException('Full name cannot be empty.'); + } + + if (null !== $this->email && '' === trim($this->email)) { + throw new \InvalidArgumentException('Email cannot be empty if provided.'); + } + + if (null !== $this->externalId && '' === trim($this->externalId)) { + throw new \InvalidArgumentException('External ID cannot be empty if provided.'); + } + } +} diff --git a/src/ContactPersons/UseCase/Install/Handler.php b/src/ContactPersons/UseCase/Install/Handler.php new file mode 100644 index 0000000..5853985 --- /dev/null +++ b/src/ContactPersons/UseCase/Install/Handler.php @@ -0,0 +1,68 @@ +logger->info('ContactPerson.Install.start', [ + 'externalId' => $command->externalId, + ]); + + // Проверяем, существует ли контакт с таким externalId + if (null !== $command->externalId) { + $existing = $this->contactPersonRepository->findByExternalId($command->externalId); + if ([] !== $existing) { + throw new InvalidArgumentException('Contact with this external ID already exists.'); + } + } + + $userAgentInfo = new UserAgentInfo($command->userAgentIp, $command->userAgent, $command->userAgentReferrer); + + $uuidV7 = Uuid::v7(); + + $contactPerson = new ContactPerson( + $uuidV7, + ContactPersonStatus::active, + $command->fullName, + $command->email, + null, + $command->mobilePhoneNumber, + null, + $command->comment, + $command->externalId, + $command->bitrix24UserId, + $command->bitrix24PartnerId, + $userAgentInfo, + true + ); + + $this->contactPersonRepository->save($contactPerson); + $this->flusher->flush($contactPerson); + + $this->logger->info('ContactPerson.Install.finish', [ + 'contact_person_id' => $uuidV7, + 'externalId' => $command->externalId, + ]); + + return $contactPerson; + } +} diff --git a/tests/Functional/ContactPersons/Builders/ContactPersonBuilder.php b/tests/Functional/ContactPersons/Builders/ContactPersonBuilder.php new file mode 100644 index 0000000..460c409 --- /dev/null +++ b/tests/Functional/ContactPersons/Builders/ContactPersonBuilder.php @@ -0,0 +1,132 @@ +id = Uuid::v7(); + $this->fullName = new FullName('John', 'Doe', 'Smith'); + $this->bitrix24UserId = random_int(1, 1_000_000); + $this->bitrix24PartnerId = Uuid::v7(); + } + + public function withStatus(ContactPersonStatus $contactPersonStatus): self + { + $this->status = $contactPersonStatus; + + return $this; + } + + public function withFullName(FullName $fullName): self + { + $this->fullName = $fullName; + + return $this; + } + + public function withEmail(string $email): self + { + $this->email = $email; + + return $this; + } + + public function withMobilePhoneNumber(PhoneNumber $mobilePhoneNumber): self + { + $this->mobilePhoneNumber = $mobilePhoneNumber; + + return $this; + } + + public function withComment(string $comment): self + { + $this->comment = $comment; + + return $this; + } + + public function withExternalId(string $externalId): self + { + $this->externalId = $externalId; + + return $this; + } + + public function withBitrix24UserId(int $bitrix24UserId): self + { + $this->bitrix24UserId = $bitrix24UserId; + + return $this; + } + + public function withBitrix24PartnerId(Uuid $uuid): self + { + $this->bitrix24PartnerId = $uuid; + + return $this; + } + + public function withUserAgentInfo(UserAgentInfo $userAgentInfo): self + { + $this->userAgentInfo = $userAgentInfo; + + return $this; + } + + public function build(): ContactPerson + { + $userAgentInfo = $this->userAgentInfo ?? new UserAgentInfo( + IP::factory('192.168.1.1'), + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + ); + + return new ContactPerson( + $this->id, + $this->status, + $this->fullName, + $this->email, + null, + $this->mobilePhoneNumber, + null, + $this->comment, + $this->externalId, + $this->bitrix24UserId, + $this->bitrix24PartnerId, + $userAgentInfo, + true + ); + } +} \ No newline at end of file diff --git a/tests/Functional/ContactPersons/UseCase/InstallStart/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/InstallStart/HandlerTest.php new file mode 100644 index 0000000..3b5d897 --- /dev/null +++ b/tests/Functional/ContactPersons/UseCase/InstallStart/HandlerTest.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\Install; + +use Bitrix24\Lib\ContactPersons\UseCase\Install\Handler; +use Bitrix24\Lib\ContactPersons\UseCase\Install\Command; +use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; +use Bitrix24\Lib\Services\Flusher; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonCreatedEvent; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\Lib\Tests\EntityManagerFactory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Uid\Uuid; + +use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; + +/** + * @internal + */ +#[CoversClass(Handler::class)] +class HandlerTest extends TestCase +{ + private Handler $handler; + + private Flusher $flusher; + + private ContactPersonRepository $repository; + + private TraceableEventDispatcher $eventDispatcher; + + #[\Override] + protected function setUp(): void + { + $entityManager = EntityManagerFactory::get(); + $this->eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $this->repository = new ContactPersonRepository($entityManager); + $this->flusher = new Flusher($entityManager, $this->eventDispatcher); + $this->handler = new Handler( + $this->repository, + $this->flusher, + new NullLogger() + ); + } + + /** + * @throws InvalidArgumentException + */ + #[Test] + public function testNewContactPerson(): void + { + $contactPersonBuilder = new ContactPersonBuilder(); + $externalId = Uuid::v7()->toRfc4122(); + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId($externalId) + ->withBitrix24UserId(123) + ->withBitrix24PartnerId(Uuid::v7()) + ->build(); + + + $this->handler->handle( + new Command( + $contactPerson->getFullName(), + $contactPerson->getEmail(), + $contactPerson->getMobilePhone(), + $contactPerson->getComment(), + $contactPerson->getExternalId(), + $contactPerson->getBitrix24UserId(), + $contactPerson->getBitrix24PartnerId(), + $contactPerson->getUserAgentInfo()->ip, + $contactPerson->getUserAgentInfo()->userAgent, + $contactPerson->getUserAgentInfo()->referrer, + '1.0' + ) + ); + + $contactPersonFromRepo = $this->repository->findByExternalId($contactPerson->getExternalId()); + $this->assertCount(1, $contactPersonFromRepo); + $this->assertInstanceOf(ContactPersonInterface::class, $contactPersonFromRepo[0]); + $this->assertEquals($contactPerson->getFullName()->name, $contactPersonFromRepo[0]->getFullName()->name); + $this->assertEquals($contactPerson->getEmail(), $contactPersonFromRepo[0]->getEmail()); + $this->assertEquals($contactPerson->getMobilePhone(), $contactPersonFromRepo[0]->getMobilePhone()); + $this->assertEquals(ContactPersonStatus::active, $contactPersonFromRepo[0]->getStatus()); + + $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); + $this->assertContains(ContactPersonCreatedEvent::class, $dispatchedEvents); + } + + /** + * @throws InvalidArgumentException + */ + #[Test] + public function testContactPersonWithDuplicateExternalId(): void + { + $contactPersonBuilder = new ContactPersonBuilder(); + $contactPerson = $contactPersonBuilder + ->withEmail('alice.cooper@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991112222')) + ->withExternalId('duplicate-ext') + ->withBitrix24UserId(789) + ->withBitrix24PartnerId(Uuid::v7()) + ->build(); + + $command = new Command( + $contactPerson->getFullName(), + $contactPerson->getEmail(), + $contactPerson->getMobilePhone(), + $contactPerson->getComment(), + $contactPerson->getExternalId(), + $contactPerson->getBitrix24UserId(), + $contactPerson->getBitrix24PartnerId(), + $contactPerson->getUserAgentInfo()->ip, + $contactPerson->getUserAgentInfo()->userAgent, + $contactPerson->getUserAgentInfo()->referrer, + '1.0' + ); + + $this->handler->handle($command); + + $this->expectException(InvalidArgumentException::class); + $this->handler->handle($command); + } + + private function createPhoneNumber(string $number): \libphonenumber\PhoneNumber + { + $phoneNumberUtil = \libphonenumber\PhoneNumberUtil::getInstance(); + return $phoneNumberUtil->parse($number, 'RU'); + } +} \ No newline at end of file From 961a68f5bf42f7e5f74704f5fd538c621e2ae502 Mon Sep 17 00:00:00 2001 From: kirill Date: Mon, 8 Dec 2025 23:30:27 +0300 Subject: [PATCH 02/31] . --- .../Builders/ContactPersonBuilder.php | 10 +++--- .../{InstallStart => Install}/HandlerTest.php | 32 ++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) rename tests/Functional/ContactPersons/UseCase/{InstallStart => Install}/HandlerTest.php (85%) diff --git a/tests/Functional/ContactPersons/Builders/ContactPersonBuilder.php b/tests/Functional/ContactPersons/Builders/ContactPersonBuilder.php index 460c409..a7964d6 100644 --- a/tests/Functional/ContactPersons/Builders/ContactPersonBuilder.php +++ b/tests/Functional/ContactPersons/Builders/ContactPersonBuilder.php @@ -12,6 +12,7 @@ use libphonenumber\PhoneNumberUtil; use Symfony\Component\Uid\Uuid; use Darsyn\IP\Version\Multi as IP; +use Bitrix24\SDK\Tests\Builders\DemoDataGenerator; class ContactPersonBuilder { @@ -38,7 +39,7 @@ class ContactPersonBuilder public function __construct() { $this->id = Uuid::v7(); - $this->fullName = new FullName('John', 'Doe', 'Smith'); + $this->fullName = DemoDataGenerator::getFullName(); $this->bitrix24UserId = random_int(1, 1_000_000); $this->bitrix24PartnerId = Uuid::v7(); } @@ -109,8 +110,8 @@ public function withUserAgentInfo(UserAgentInfo $userAgentInfo): self public function build(): ContactPerson { $userAgentInfo = $this->userAgentInfo ?? new UserAgentInfo( - IP::factory('192.168.1.1'), - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + DemoDataGenerator::getUserAgentIp(), + DemoDataGenerator::getUserAgent() ); return new ContactPerson( @@ -125,8 +126,7 @@ public function build(): ContactPerson $this->externalId, $this->bitrix24UserId, $this->bitrix24PartnerId, - $userAgentInfo, - true + $userAgentInfo ); } } \ No newline at end of file diff --git a/tests/Functional/ContactPersons/UseCase/InstallStart/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php similarity index 85% rename from tests/Functional/ContactPersons/UseCase/InstallStart/HandlerTest.php rename to tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php index 3b5d897..1887b4c 100644 --- a/tests/Functional/ContactPersons/UseCase/InstallStart/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php @@ -30,7 +30,8 @@ use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Uid\Uuid; - +use libphonenumber\PhoneNumberUtil; +use libphonenumber\PhoneNumber; use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; /** @@ -62,19 +63,21 @@ protected function setUp(): void } /** - * @throws InvalidArgumentException + * @throws InvalidArgumentException|\Random\RandomException */ #[Test] public function testNewContactPerson(): void { $contactPersonBuilder = new ContactPersonBuilder(); $externalId = Uuid::v7()->toRfc4122(); + $bitrix24UserId = random_int(1, 1_000_000); + $contactPerson = $contactPersonBuilder ->withEmail('john.doe@example.com') ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) ->withComment('Test comment') ->withExternalId($externalId) - ->withBitrix24UserId(123) + ->withBitrix24UserId($bitrix24UserId) ->withBitrix24PartnerId(Uuid::v7()) ->build(); @@ -92,19 +95,18 @@ public function testNewContactPerson(): void $contactPerson->getUserAgentInfo()->userAgent, $contactPerson->getUserAgentInfo()->referrer, '1.0' - ) - ); + ) + ); $contactPersonFromRepo = $this->repository->findByExternalId($contactPerson->getExternalId()); $this->assertCount(1, $contactPersonFromRepo); - $this->assertInstanceOf(ContactPersonInterface::class, $contactPersonFromRepo[0]); - $this->assertEquals($contactPerson->getFullName()->name, $contactPersonFromRepo[0]->getFullName()->name); - $this->assertEquals($contactPerson->getEmail(), $contactPersonFromRepo[0]->getEmail()); - $this->assertEquals($contactPerson->getMobilePhone(), $contactPersonFromRepo[0]->getMobilePhone()); - $this->assertEquals(ContactPersonStatus::active, $contactPersonFromRepo[0]->getStatus()); - - $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); - $this->assertContains(ContactPersonCreatedEvent::class, $dispatchedEvents); + $foundContactPerson = reset($contactPersonFromRepo); + + $this->assertInstanceOf(ContactPersonInterface::class, $foundContactPerson); + $this->assertEquals($contactPerson->getFullName()->name, $foundContactPerson->getFullName()->name); + $this->assertEquals($contactPerson->getEmail(), $foundContactPerson->getEmail()); + $this->assertEquals($contactPerson->getMobilePhone(), $foundContactPerson->getMobilePhone()); + $this->assertEquals(ContactPersonStatus::active, $foundContactPerson->getStatus()); } /** @@ -142,9 +144,9 @@ public function testContactPersonWithDuplicateExternalId(): void $this->handler->handle($command); } - private function createPhoneNumber(string $number): \libphonenumber\PhoneNumber + private function createPhoneNumber(string $number): PhoneNumber { - $phoneNumberUtil = \libphonenumber\PhoneNumberUtil::getInstance(); + $phoneNumberUtil = PhoneNumberUtil::getInstance(); return $phoneNumberUtil->parse($number, 'RU'); } } \ No newline at end of file From c0d5d9e05c57b7e47b540ed1edbd0d0203c0931a Mon Sep 17 00:00:00 2001 From: kirill Date: Fri, 12 Dec 2025 00:00:19 +0300 Subject: [PATCH 03/31] . --- .../UseCase/Install/Command.php | 25 +++++++++++++------ .../UseCase/Install/Handler.php | 17 +++++++++++++ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/ApplicationInstallations/UseCase/Install/Command.php b/src/ApplicationInstallations/UseCase/Install/Command.php index 224f9ba..204f3ca 100644 --- a/src/ApplicationInstallations/UseCase/Install/Command.php +++ b/src/ApplicationInstallations/UseCase/Install/Command.php @@ -11,6 +11,10 @@ use Bitrix24\SDK\Core\Credentials\Scope; use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; use Symfony\Component\Uid\Uuid; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\FullName; +use Faker\Provider\PhoneNumber; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\UserAgentInfo; /** * Installation can occur in 2 scenes. @@ -39,14 +43,19 @@ public function __construct( public int $bitrix24UserId, public bool $isBitrix24UserAdmin, public ApplicationStatus $applicationStatus, - public PortalLicenseFamily $portalLicenseFamily, - public ?string $applicationToken = null, - public ?int $portalUsersCount = null, - public ?Uuid $contactPersonId = null, - public ?Uuid $bitrix24PartnerContactPersonId = null, - public ?Uuid $bitrix24PartnerId = null, - public ?string $externalId = null, - public ?string $comment = null, + public PortalLicenseFamily $portalLicenseFamily, + public ?string $applicationToken = null, + public ?int $portalUsersCount = null, + public ?Uuid $contactPersonId = null, + public ?Uuid $bitrix24PartnerContactPersonId = null, + public ?Uuid $bitrix24PartnerId = null, + public ?string $externalId = null, + public ?string $comment = null, + public ?ContactPersonStatus $contactPersonStatus = null, + public ?FullName $fullName = null, + public ?PhoneNumber $mobilePhoneNumber = null, + public ?UserAgentInfo $userAgentInfo = null, + public ?string $email = null, ) { $this->validate(); } diff --git a/src/ApplicationInstallations/UseCase/Install/Handler.php b/src/ApplicationInstallations/UseCase/Install/Handler.php index 3c53d80..b852e31 100644 --- a/src/ApplicationInstallations/UseCase/Install/Handler.php +++ b/src/ApplicationInstallations/UseCase/Install/Handler.php @@ -6,6 +6,7 @@ use Bitrix24\Lib\ApplicationInstallations\Entity\ApplicationInstallation; use Bitrix24\Lib\Bitrix24Accounts\Entity\Bitrix24Account; +use Bitrix24\Lib\ContactPersons\Entity\ContactPerson; use Bitrix24\Lib\Services\Flusher; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationInterface; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Repository\ApplicationInstallationRepositoryInterface; @@ -76,6 +77,22 @@ public function handle(Command $command): void $uuidV7 = Uuid::v7(); $applicationInstallationId = Uuid::v7(); + $contactPersonId = Uuid::v7(); + + $contactPerson = new ContactPerson( + $contactPersonId, + $command->contactPersonStatus, + $command->fullName, + $command->email, + null, + $command->mobilePhoneNumber, + null, + $command->comment, + $command->externalId, + $command->bitrix24UserId, + $command->bitrix24PartnerId, + $command->userAgentInfo + ); $bitrix24Account = new Bitrix24Account( $uuidV7, From cdb31d419c4bbf0c4b69d7868f2aa614eac6e816 Mon Sep 17 00:00:00 2001 From: kirill Date: Sat, 13 Dec 2025 01:09:40 +0300 Subject: [PATCH 04/31] . --- .../UseCase/Install/Command.php | 5 - .../UseCase/Install/Handler.php | 16 --- src/ContactPersons/Enum/ContactPersonType.php | 20 +++ .../UseCase/Install/Command.php | 10 +- .../UseCase/Install/Handler.php | 39 ++++-- .../ApplicationInstallationBuilder.php | 18 ++- .../UseCase/Install/HandlerTest.php | 115 ++++++++++++++++-- 7 files changed, 182 insertions(+), 41 deletions(-) create mode 100644 src/ContactPersons/Enum/ContactPersonType.php diff --git a/src/ApplicationInstallations/UseCase/Install/Command.php b/src/ApplicationInstallations/UseCase/Install/Command.php index 204f3ca..4f65e15 100644 --- a/src/ApplicationInstallations/UseCase/Install/Command.php +++ b/src/ApplicationInstallations/UseCase/Install/Command.php @@ -51,11 +51,6 @@ public function __construct( public ?Uuid $bitrix24PartnerId = null, public ?string $externalId = null, public ?string $comment = null, - public ?ContactPersonStatus $contactPersonStatus = null, - public ?FullName $fullName = null, - public ?PhoneNumber $mobilePhoneNumber = null, - public ?UserAgentInfo $userAgentInfo = null, - public ?string $email = null, ) { $this->validate(); } diff --git a/src/ApplicationInstallations/UseCase/Install/Handler.php b/src/ApplicationInstallations/UseCase/Install/Handler.php index b852e31..44d76e7 100644 --- a/src/ApplicationInstallations/UseCase/Install/Handler.php +++ b/src/ApplicationInstallations/UseCase/Install/Handler.php @@ -77,22 +77,6 @@ public function handle(Command $command): void $uuidV7 = Uuid::v7(); $applicationInstallationId = Uuid::v7(); - $contactPersonId = Uuid::v7(); - - $contactPerson = new ContactPerson( - $contactPersonId, - $command->contactPersonStatus, - $command->fullName, - $command->email, - null, - $command->mobilePhoneNumber, - null, - $command->comment, - $command->externalId, - $command->bitrix24UserId, - $command->bitrix24PartnerId, - $command->userAgentInfo - ); $bitrix24Account = new Bitrix24Account( $uuidV7, diff --git a/src/ContactPersons/Enum/ContactPersonType.php b/src/ContactPersons/Enum/ContactPersonType.php new file mode 100644 index 0000000..f07ac55 --- /dev/null +++ b/src/ContactPersons/Enum/ContactPersonType.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\Lib\ContactPersons\Enum; + +enum ContactPersonType: string +{ + case personal = 'personal'; + case partner = 'partner'; +} \ No newline at end of file diff --git a/src/ContactPersons/UseCase/Install/Command.php b/src/ContactPersons/UseCase/Install/Command.php index 0243b3c..2600329 100644 --- a/src/ContactPersons/UseCase/Install/Command.php +++ b/src/ContactPersons/UseCase/Install/Command.php @@ -5,9 +5,11 @@ namespace Bitrix24\Lib\ContactPersons\UseCase\Install; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\FullName; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; use Darsyn\IP\Version\Multi as IP; use libphonenumber\PhoneNumber; use Symfony\Component\Uid\Uuid; +use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; readonly class Command { @@ -22,7 +24,9 @@ public function __construct( public ?IP $userAgentIp, public ?string $userAgent, public ?string $userAgentReferrer, - public string $userAgentVersion + public string $userAgentVersion, + public ?string $memberId, + public ?ContactPersonType $contactPersonType ) { $this->validate(); } @@ -40,5 +44,9 @@ private function validate(): void if (null !== $this->externalId && '' === trim($this->externalId)) { throw new \InvalidArgumentException('External ID cannot be empty if provided.'); } + + if ('' === $this->memberId) { + throw new InvalidArgumentException('Member ID must be a non-empty string.'); + } } } diff --git a/src/ContactPersons/UseCase/Install/Handler.php b/src/ContactPersons/UseCase/Install/Handler.php index 5853985..3f43696 100644 --- a/src/ContactPersons/UseCase/Install/Handler.php +++ b/src/ContactPersons/UseCase/Install/Handler.php @@ -5,17 +5,21 @@ namespace Bitrix24\Lib\ContactPersons\UseCase\Install; use Bitrix24\Lib\ContactPersons\Entity\ContactPerson; +use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; use Bitrix24\Lib\Services\Flusher; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationInterface; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Repository\ApplicationInstallationRepositoryInterface; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\UserAgentInfo; use Bitrix24\SDK\Application\Contracts\ContactPersons\Repository\ContactPersonRepositoryInterface; -use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\SDK\Application\Contracts\Events\AggregateRootEventsEmitterInterface; use Psr\Log\LoggerInterface; use Symfony\Component\Uid\Uuid; readonly class Handler { public function __construct( + private ApplicationInstallationRepositoryInterface $applicationInstallationRepository, private ContactPersonRepositoryInterface $contactPersonRepository, private Flusher $flusher, private LoggerInterface $logger @@ -25,20 +29,16 @@ public function handle(Command $command): ContactPerson { $this->logger->info('ContactPerson.Install.start', [ 'externalId' => $command->externalId, + 'memberId' => $command->memberId, + 'contactPersonType' => $command->contactPersonType, ]); - // Проверяем, существует ли контакт с таким externalId - if (null !== $command->externalId) { - $existing = $this->contactPersonRepository->findByExternalId($command->externalId); - if ([] !== $existing) { - throw new InvalidArgumentException('Contact with this external ID already exists.'); - } - } - $userAgentInfo = new UserAgentInfo($command->userAgentIp, $command->userAgent, $command->userAgentReferrer); $uuidV7 = Uuid::v7(); + $entitiesToFlush = []; + $contactPerson = new ContactPerson( $uuidV7, ContactPersonStatus::active, @@ -56,7 +56,26 @@ public function handle(Command $command): ContactPerson ); $this->contactPersonRepository->save($contactPerson); - $this->flusher->flush($contactPerson); + + $entitiesToFlush[] = $contactPerson; + + if (null !== $command->memberId) { + /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $activeInstallation */ + $activeInstallation = $this->applicationInstallationRepository->findByBitrix24AccountMemberId($command->memberId); + + if ($command->contactPersonType == ContactPersonType::personal) { + $activeInstallation->linkContactPerson($uuidV7); + } + + if ($command->contactPersonType == ContactPersonType::partner) { + $activeInstallation->linkBitrix24PartnerContactPerson($uuidV7); + } + + $this->applicationInstallationRepository->save($activeInstallation); + $entitiesToFlush[] = $activeInstallation; + } + + $this->flusher->flush(...array_filter($entitiesToFlush, fn ($entity): bool => $entity instanceof AggregateRootEventsEmitterInterface)); $this->logger->info('ContactPerson.Install.finish', [ 'contact_person_id' => $uuidV7, diff --git a/tests/Functional/ApplicationInstallations/Builders/ApplicationInstallationBuilder.php b/tests/Functional/ApplicationInstallations/Builders/ApplicationInstallationBuilder.php index 264a03a..5abde64 100644 --- a/tests/Functional/ApplicationInstallations/Builders/ApplicationInstallationBuilder.php +++ b/tests/Functional/ApplicationInstallations/Builders/ApplicationInstallationBuilder.php @@ -17,9 +17,9 @@ class ApplicationInstallationBuilder private Uuid $bitrix24AccountId; - private readonly ?Uuid $contactPersonId; + private ?Uuid $contactPersonId; - private readonly ?Uuid $bitrix24PartnerContactPersonId; + private ?Uuid $bitrix24PartnerContactPersonId; private readonly ?Uuid $bitrix24PartnerId; @@ -82,6 +82,20 @@ public function withBitrix24AccountId(Uuid $uuid): self return $this; } + public function withContactPersonId(?Uuid $uuid): self + { + $this->contactPersonId = $uuid; + + return $this; + } + + public function withBitrix24PartnerContactPersonId(?Uuid $uuid): self + { + $this->bitrix24PartnerContactPersonId = $uuid; + + return $this; + } + public function withPortalLicenseFamily(PortalLicenseFamily $portalLicenseFamily): self { $this->portalLicenseFamily = $portalLicenseFamily; diff --git a/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php index 1887b4c..f0e3304 100644 --- a/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php @@ -13,13 +13,21 @@ namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\Install; +use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository; +use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository; use Bitrix24\Lib\ContactPersons\UseCase\Install\Handler; use Bitrix24\Lib\ContactPersons\UseCase\Install\Command; use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; use Bitrix24\Lib\Services\Flusher; +use Bitrix24\Lib\Tests\Functional\ApplicationInstallations\Builders\ApplicationInstallationBuilder; +use Bitrix24\Lib\Tests\Functional\Bitrix24Accounts\Builders\Bitrix24AccountBuilder; +use Bitrix24\SDK\Application\ApplicationStatus; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationStatus; +use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonCreatedEvent; +use Bitrix24\SDK\Application\PortalLicenseFamily; +use Bitrix24\SDK\Core\Credentials\Scope; use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; use Bitrix24\Lib\Tests\EntityManagerFactory; use PHPUnit\Framework\Attributes\CoversClass; @@ -33,6 +41,7 @@ use libphonenumber\PhoneNumberUtil; use libphonenumber\PhoneNumber; use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; +use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; /** * @internal @@ -45,6 +54,10 @@ class HandlerTest extends TestCase private Flusher $flusher; private ContactPersonRepository $repository; + private ApplicationInstallationRepository $applicationInstallationRepository; + + private Bitrix24AccountRepository $bitrix24accountRepository; + private TraceableEventDispatcher $eventDispatcher; @@ -54,8 +67,11 @@ protected function setUp(): void $entityManager = EntityManagerFactory::get(); $this->eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); $this->repository = new ContactPersonRepository($entityManager); + $this->applicationInstallationRepository = new ApplicationInstallationRepository($entityManager); + $this->bitrix24accountRepository = new Bitrix24AccountRepository($entityManager); $this->flusher = new Flusher($entityManager, $this->eventDispatcher); $this->handler = new Handler( + $this->applicationInstallationRepository, $this->repository, $this->flusher, new NullLogger() @@ -94,7 +110,9 @@ public function testNewContactPerson(): void $contactPerson->getUserAgentInfo()->ip, $contactPerson->getUserAgentInfo()->userAgent, $contactPerson->getUserAgentInfo()->referrer, - '1.0' + '1.0', + null, + null ) ); @@ -109,8 +127,87 @@ public function testNewContactPerson(): void $this->assertEquals(ContactPersonStatus::active, $foundContactPerson->getStatus()); } - /** - * @throws InvalidArgumentException + #[Test] + public function testNewContactPersonAndLinkApp(): void + { + // Load account and application installation into database for uninstallation. + $applicationToken = Uuid::v7()->toRfc4122(); + $memberId = Uuid::v4()->toRfc4122(); + $externalId = Uuid::v7()->toRfc4122(); + + $bitrix24Account = (new Bitrix24AccountBuilder()) + ->withApplicationScope(new Scope(['crm'])) + ->withStatus(Bitrix24AccountStatus::new) + ->withApplicationToken($applicationToken) + ->withMemberId($memberId) + ->withMaster(true) + ->withSetToken() + ->withInstalled() + ->build(); + + $this->bitrix24accountRepository->save($bitrix24Account); + + $applicationInstallation = (new ApplicationInstallationBuilder()) + ->withApplicationStatus(new ApplicationStatus('F')) + ->withPortalLicenseFamily(PortalLicenseFamily::free) + ->withBitrix24AccountId($bitrix24Account->getId()) + ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) + ->withApplicationToken($applicationToken) + ->withContactPersonId(null) + ->withBitrix24PartnerContactPersonId(null) + ->withExternalId($externalId) + ->build(); + + $this->applicationInstallationRepository->save($applicationInstallation); + + $this->flusher->flush(); + + $contactPersonBuilder = new ContactPersonBuilder(); + + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId($applicationInstallation->getExternalId()) + ->withBitrix24UserId($bitrix24Account->getBitrix24UserId()) + ->withBitrix24PartnerId($applicationInstallation->getBitrix24PartnerId()) + ->build(); + + $this->handler->handle( + new Command( + $contactPerson->getFullName(), + $contactPerson->getEmail(), + $contactPerson->getMobilePhone(), + $contactPerson->getComment(), + $contactPerson->getExternalId(), + $contactPerson->getBitrix24UserId(), + $contactPerson->getBitrix24PartnerId(), + $contactPerson->getUserAgentInfo()->ip, + $contactPerson->getUserAgentInfo()->userAgent, + $contactPerson->getUserAgentInfo()->referrer, + '1.0', + $bitrix24Account->getMemberId(), + ContactPersonType::partner + ) + ); + + $applicationInstallationFromRepo = $this->applicationInstallationRepository->findByExternalId($applicationInstallation->getExternalId()); + $this->assertCount(1, $applicationInstallationFromRepo); + $foundInstallation = reset($applicationInstallationFromRepo); + + $contactPersonFromRepo = $this->repository->findByExternalId($foundInstallation->getExternalId()); + $this->assertCount(1, $contactPersonFromRepo); + $foundContactPerson = reset($contactPersonFromRepo); + + $this->assertNotNull($foundInstallation->getBitrix24PartnerContactPersonId()); + + // Можно дополнительно проверить, что именно нужное поле заполнено + $this->assertEquals($foundContactPerson->getId(), $foundInstallation->getBitrix24PartnerContactPersonId()); + } + + + /* + * Что такое externalId? Вроде бы это подпись. Тогда по сути у нас может на 1 подпись быть 2 контактных лица. */ #[Test] public function testContactPersonWithDuplicateExternalId(): void @@ -124,6 +221,10 @@ public function testContactPersonWithDuplicateExternalId(): void ->withBitrix24PartnerId(Uuid::v7()) ->build(); + $this->repository->save($contactPerson); + + $this->flusher->flush(); + $command = new Command( $contactPerson->getFullName(), $contactPerson->getEmail(), @@ -135,11 +236,11 @@ public function testContactPersonWithDuplicateExternalId(): void $contactPerson->getUserAgentInfo()->ip, $contactPerson->getUserAgentInfo()->userAgent, $contactPerson->getUserAgentInfo()->referrer, - '1.0' + '1.0', + null, + null ); - $this->handler->handle($command); - $this->expectException(InvalidArgumentException::class); $this->handler->handle($command); } From 09dafb9dbe75e51dfb5b69e2ce3ee93d6eaeb529 Mon Sep 17 00:00:00 2001 From: kirill Date: Sat, 13 Dec 2025 12:11:02 +0300 Subject: [PATCH 05/31] . --- .../UseCase/Install/Command.php | 20 ++- .../UseCase/Install/Handler.php | 1 - src/ContactPersons/Entity/ContactPerson.php | 6 + src/ContactPersons/Enum/ContactPersonType.php | 2 +- .../Doctrine/ContactPersonRepository.php | 6 + .../UseCase/Install/Command.php | 2 +- .../UseCase/Install/Handler.php | 8 +- .../UseCase/UpdateData/Command.php | 55 ++++++++ .../UseCase/UpdateData/Handler.php | 78 ++++++++++ .../UseCase/Install/HandlerTest.php | 28 ++-- .../UseCase/UpdateData/HandlerTest.php | 133 ++++++++++++++++++ 11 files changed, 309 insertions(+), 30 deletions(-) create mode 100644 src/ContactPersons/UseCase/UpdateData/Command.php create mode 100644 src/ContactPersons/UseCase/UpdateData/Handler.php create mode 100644 tests/Functional/ContactPersons/UseCase/UpdateData/HandlerTest.php diff --git a/src/ApplicationInstallations/UseCase/Install/Command.php b/src/ApplicationInstallations/UseCase/Install/Command.php index 4f65e15..224f9ba 100644 --- a/src/ApplicationInstallations/UseCase/Install/Command.php +++ b/src/ApplicationInstallations/UseCase/Install/Command.php @@ -11,10 +11,6 @@ use Bitrix24\SDK\Core\Credentials\Scope; use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; use Symfony\Component\Uid\Uuid; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\FullName; -use Faker\Provider\PhoneNumber; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\UserAgentInfo; /** * Installation can occur in 2 scenes. @@ -43,14 +39,14 @@ public function __construct( public int $bitrix24UserId, public bool $isBitrix24UserAdmin, public ApplicationStatus $applicationStatus, - public PortalLicenseFamily $portalLicenseFamily, - public ?string $applicationToken = null, - public ?int $portalUsersCount = null, - public ?Uuid $contactPersonId = null, - public ?Uuid $bitrix24PartnerContactPersonId = null, - public ?Uuid $bitrix24PartnerId = null, - public ?string $externalId = null, - public ?string $comment = null, + public PortalLicenseFamily $portalLicenseFamily, + public ?string $applicationToken = null, + public ?int $portalUsersCount = null, + public ?Uuid $contactPersonId = null, + public ?Uuid $bitrix24PartnerContactPersonId = null, + public ?Uuid $bitrix24PartnerId = null, + public ?string $externalId = null, + public ?string $comment = null, ) { $this->validate(); } diff --git a/src/ApplicationInstallations/UseCase/Install/Handler.php b/src/ApplicationInstallations/UseCase/Install/Handler.php index 44d76e7..3c53d80 100644 --- a/src/ApplicationInstallations/UseCase/Install/Handler.php +++ b/src/ApplicationInstallations/UseCase/Install/Handler.php @@ -6,7 +6,6 @@ use Bitrix24\Lib\ApplicationInstallations\Entity\ApplicationInstallation; use Bitrix24\Lib\Bitrix24Accounts\Entity\Bitrix24Account; -use Bitrix24\Lib\ContactPersons\Entity\ContactPerson; use Bitrix24\Lib\Services\Flusher; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationInterface; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Repository\ApplicationInstallationRepositoryInterface; diff --git a/src/ContactPersons/Entity/ContactPerson.php b/src/ContactPersons/Entity/ContactPerson.php index e9d70a1..dbb0746 100644 --- a/src/ContactPersons/Entity/ContactPerson.php +++ b/src/ContactPersons/Entity/ContactPerson.php @@ -15,6 +15,7 @@ use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonEmailChangedEvent; use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonEmailVerifiedEvent; use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonFullNameChangedEvent; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonMobilePhoneChangedEvent; use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonMobilePhoneVerifiedEvent; use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; use Bitrix24\SDK\Core\Exceptions\LogicException; @@ -206,6 +207,11 @@ public function changeMobilePhone(?PhoneNumber $phoneNumber): void } $this->updatedAt = new CarbonImmutable(); + + $this->events[] = new ContactPersonMobilePhoneChangedEvent( + $this->id, + $this->updatedAt, + ); } #[\Override] diff --git a/src/ContactPersons/Enum/ContactPersonType.php b/src/ContactPersons/Enum/ContactPersonType.php index f07ac55..edcf52a 100644 --- a/src/ContactPersons/Enum/ContactPersonType.php +++ b/src/ContactPersons/Enum/ContactPersonType.php @@ -17,4 +17,4 @@ enum ContactPersonType: string { case personal = 'personal'; case partner = 'partner'; -} \ No newline at end of file +} diff --git a/src/ContactPersons/Infrastructure/Doctrine/ContactPersonRepository.php b/src/ContactPersons/Infrastructure/Doctrine/ContactPersonRepository.php index 863b4d3..693b3f4 100644 --- a/src/ContactPersons/Infrastructure/Doctrine/ContactPersonRepository.php +++ b/src/ContactPersons/Infrastructure/Doctrine/ContactPersonRepository.php @@ -9,6 +9,7 @@ use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; use Bitrix24\SDK\Application\Contracts\ContactPersons\Repository\ContactPersonRepositoryInterface; +use Bitrix24\SDK\Application\Contracts\Events\AggregateRootEventsEmitterInterface; use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; @@ -54,6 +55,11 @@ public function delete(Uuid $uuid): void $this->save($contactPerson); } + /** + * @phpstan-return ContactPersonInterface&AggregateRootEventsEmitterInterface + * + * @throws ContactPersonNotFoundException + */ #[\Override] public function getById(Uuid $uuid): ContactPersonInterface { diff --git a/src/ContactPersons/UseCase/Install/Command.php b/src/ContactPersons/UseCase/Install/Command.php index 2600329..487436b 100644 --- a/src/ContactPersons/UseCase/Install/Command.php +++ b/src/ContactPersons/UseCase/Install/Command.php @@ -4,12 +4,12 @@ namespace Bitrix24\Lib\ContactPersons\UseCase\Install; +use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\FullName; use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; use Darsyn\IP\Version\Multi as IP; use libphonenumber\PhoneNumber; use Symfony\Component\Uid\Uuid; -use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; readonly class Command { diff --git a/src/ContactPersons/UseCase/Install/Handler.php b/src/ContactPersons/UseCase/Install/Handler.php index 3f43696..1cec3cf 100644 --- a/src/ContactPersons/UseCase/Install/Handler.php +++ b/src/ContactPersons/UseCase/Install/Handler.php @@ -25,7 +25,7 @@ public function __construct( private LoggerInterface $logger ) {} - public function handle(Command $command): ContactPerson + public function handle(Command $command): void { $this->logger->info('ContactPerson.Install.start', [ 'externalId' => $command->externalId, @@ -63,11 +63,11 @@ public function handle(Command $command): ContactPerson /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $activeInstallation */ $activeInstallation = $this->applicationInstallationRepository->findByBitrix24AccountMemberId($command->memberId); - if ($command->contactPersonType == ContactPersonType::personal) { + if (ContactPersonType::personal == $command->contactPersonType) { $activeInstallation->linkContactPerson($uuidV7); } - if ($command->contactPersonType == ContactPersonType::partner) { + if (ContactPersonType::partner == $command->contactPersonType) { $activeInstallation->linkBitrix24PartnerContactPerson($uuidV7); } @@ -81,7 +81,5 @@ public function handle(Command $command): ContactPerson 'contact_person_id' => $uuidV7, 'externalId' => $command->externalId, ]); - - return $contactPerson; } } diff --git a/src/ContactPersons/UseCase/UpdateData/Command.php b/src/ContactPersons/UseCase/UpdateData/Command.php new file mode 100644 index 0000000..75e216a --- /dev/null +++ b/src/ContactPersons/UseCase/UpdateData/Command.php @@ -0,0 +1,55 @@ +validate(); + } + + private function validate(): void + { + if ($this->fullName instanceof FullName && '' === trim($this->fullName->name)) { + throw new InvalidArgumentException('Full name cannot be empty.'); + } + + if (null !== $this->email && '' === trim($this->email)) { + throw new InvalidArgumentException('Email cannot be empty if provided.'); + } + + if (null !== $this->externalId && '' === trim($this->externalId)) { + throw new InvalidArgumentException('External ID cannot be empty if provided.'); + } + + if ($this->mobilePhoneNumber instanceof PhoneNumber) { + $phoneUtil = PhoneNumberUtil::getInstance(); + $isValidNumber = $phoneUtil->isValidNumber($this->mobilePhoneNumber); + $numberType = $phoneUtil->getNumberType($this->mobilePhoneNumber); + + if (!$isValidNumber) { + throw new InvalidArgumentException('Invalid phone number.'); + } + + if (PhoneNumberType::MOBILE !== $numberType) { + throw new InvalidArgumentException('Phone number must be mobile.'); + } + } + } +} diff --git a/src/ContactPersons/UseCase/UpdateData/Handler.php b/src/ContactPersons/UseCase/UpdateData/Handler.php new file mode 100644 index 0000000..908a1e0 --- /dev/null +++ b/src/ContactPersons/UseCase/UpdateData/Handler.php @@ -0,0 +1,78 @@ +logger->info('ContactPerson.UpdateData.start', [ + 'contactPersonId' => $command->contactPersonId, + 'fullName' => $command->fullName?->name ?? null, + 'email' => $command->email, + 'mobilePhoneNumber' => $command->mobilePhoneNumber?->__toString() ?? null, + 'externalId' => $command->externalId, + 'bitrix24PartnerId' => $command->bitrix24PartnerId?->toRfc4122() ?? null, + ]); + + + /** @var AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ + $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); + if (!$contactPerson) { + throw new InvalidArgumentException('Contact person not found.'); + } + + if ($command->fullName instanceof FullName) { + $contactPerson->changeFullName($command->fullName); + } + + if (null !== $command->email) { + $contactPerson->changeEmail($command->email); + } + + if ($command->mobilePhoneNumber instanceof PhoneNumber) { + $contactPerson->changeMobilePhone($command->mobilePhoneNumber); + } + + if (null !== $command->externalId) { + $contactPerson->setExternalId($command->externalId); + } + + if ($command->bitrix24PartnerId instanceof Uuid) { + $contactPerson->setBitrix24PartnerId($command->bitrix24PartnerId); + } + + $this->contactPersonRepository->save($contactPerson); + $this->flusher->flush($contactPerson); + + $this->logger->info('ContactPerson.UpdateData.finish', [ + 'contactPersonId' => $contactPerson->getId()->toRfc4122(), + 'updatedFields' => [ + 'fullName' => $command->fullName?->name ?? null, + 'email' => $command->email, + 'mobilePhoneNumber' => $command->mobilePhoneNumber?->__toString() ?? null, + 'externalId' => $command->externalId, + 'bitrix24PartnerId' => $command->bitrix24PartnerId?->toRfc4122() ?? null, + ], + ]); + } +} diff --git a/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php index f0e3304..750c9b7 100644 --- a/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php @@ -23,9 +23,13 @@ use Bitrix24\Lib\Tests\Functional\Bitrix24Accounts\Builders\Bitrix24AccountBuilder; use Bitrix24\SDK\Application\ApplicationStatus; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationStatus; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationBitrix24PartnerLinkedEvent; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationContactPersonLinkedEvent; use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonCreatedEvent; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonEmailChangedEvent; use Bitrix24\SDK\Application\PortalLicenseFamily; use Bitrix24\SDK\Core\Credentials\Scope; use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; @@ -54,11 +58,11 @@ class HandlerTest extends TestCase private Flusher $flusher; private ContactPersonRepository $repository; + private ApplicationInstallationRepository $applicationInstallationRepository; private Bitrix24AccountRepository $bitrix24accountRepository; - private TraceableEventDispatcher $eventDispatcher; #[\Override] @@ -117,7 +121,14 @@ public function testNewContactPerson(): void ); $contactPersonFromRepo = $this->repository->findByExternalId($contactPerson->getExternalId()); + + $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); + $this->assertContains(ContactPersonCreatedEvent::class, $dispatchedEvents); + $this->assertNotContains(ApplicationInstallationContactPersonLinkedEvent::class, $dispatchedEvents); + $this->assertNotContains(ApplicationInstallationBitrix24PartnerLinkedEvent::class, $dispatchedEvents); + $this->assertCount(1, $contactPersonFromRepo); + $foundContactPerson = reset($contactPersonFromRepo); $this->assertInstanceOf(ContactPersonInterface::class, $foundContactPerson); @@ -191,18 +202,15 @@ public function testNewContactPersonAndLinkApp(): void ) ); - $applicationInstallationFromRepo = $this->applicationInstallationRepository->findByExternalId($applicationInstallation->getExternalId()); - $this->assertCount(1, $applicationInstallationFromRepo); - $foundInstallation = reset($applicationInstallationFromRepo); + $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); - $contactPersonFromRepo = $this->repository->findByExternalId($foundInstallation->getExternalId()); - $this->assertCount(1, $contactPersonFromRepo); - $foundContactPerson = reset($contactPersonFromRepo); + $foundInstallation = $this->applicationInstallationRepository->findByBitrix24AccountMemberId($bitrix24Account->getMemberId()); + + $this->assertContains(ApplicationInstallationBitrix24PartnerLinkedEvent::class, $dispatchedEvents); - $this->assertNotNull($foundInstallation->getBitrix24PartnerContactPersonId()); + $foundContactPerson = $this->repository->getById($foundInstallation->getBitrix24PartnerContactPersonId()); + $this->assertNotNull($foundContactPerson); - // Можно дополнительно проверить, что именно нужное поле заполнено - $this->assertEquals($foundContactPerson->getId(), $foundInstallation->getBitrix24PartnerContactPersonId()); } diff --git a/tests/Functional/ContactPersons/UseCase/UpdateData/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/UpdateData/HandlerTest.php new file mode 100644 index 0000000..a509903 --- /dev/null +++ b/tests/Functional/ContactPersons/UseCase/UpdateData/HandlerTest.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\UpdateData; + +use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository; +use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository; +use Bitrix24\Lib\ContactPersons\UseCase\UpdateData\Handler; +use Bitrix24\Lib\ContactPersons\UseCase\UpdateData\Command; +use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; +use Bitrix24\Lib\Services\Flusher; +use Bitrix24\Lib\Tests\Functional\ApplicationInstallations\Builders\ApplicationInstallationBuilder; +use Bitrix24\Lib\Tests\Functional\Bitrix24Accounts\Builders\Bitrix24AccountBuilder; +use Bitrix24\SDK\Application\ApplicationStatus; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationStatus; +use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonEmailChangedEvent; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonFullNameChangedEvent; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonMobilePhoneChangedEvent; +use Bitrix24\SDK\Application\PortalLicenseFamily; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\Lib\Tests\EntityManagerFactory; +use libphonenumber\PhoneNumberFormat; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Uid\Uuid; +use libphonenumber\PhoneNumberUtil; +use libphonenumber\PhoneNumber; +use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; +use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\FullName; +/** + * @internal + */ +#[CoversClass(Handler::class)] +class HandlerTest extends TestCase +{ + private Handler $handler; + + private Flusher $flusher; + + private ContactPersonRepository $repository; + + private TraceableEventDispatcher $eventDispatcher; + + #[\Override] + protected function setUp(): void + { + $entityManager = EntityManagerFactory::get(); + $this->eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $this->repository = new ContactPersonRepository($entityManager); + $this->flusher = new Flusher($entityManager, $this->eventDispatcher); + $this->handler = new Handler( + $this->repository, + $this->flusher, + new NullLogger() + ); + } + + #[Test] + public function testUpdateExistingContactPerson(): void + { + // Создаем контактное лицо через билдера + $contactPersonBuilder = new ContactPersonBuilder(); + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Initial comment') + ->withExternalId(Uuid::v7()->toRfc4122()) + ->withBitrix24UserId(random_int(1, 1_000_000)) + ->withBitrix24PartnerId(Uuid::v7()) + ->build(); + + $this->repository->save($contactPerson); + $this->flusher->flush(); + + $externalId = Uuid::v7()->toRfc4122(); + $bitrix24PartnerId = Uuid::v7(); + + // Обновляем контактное лицо через команду + $this->handler->handle( + new Command( + $contactPerson->getId(), + new FullName('Jane Doe'), + 'jane.doe@example.com', + $this->createPhoneNumber('+79997654321'), + $externalId, + $bitrix24PartnerId, + ) + ); + + + // Проверяем, что изменения сохранились + $updatedContactPerson = $this->repository->getById($contactPerson->getId()); + $phoneUtil = PhoneNumberUtil::getInstance(); + $formattedPhone = $phoneUtil->format($updatedContactPerson->getMobilePhone(), PhoneNumberFormat::E164); + + $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); + $this->assertContains(ContactPersonEmailChangedEvent::class, $dispatchedEvents); + $this->assertContains(ContactPersonMobilePhoneChangedEvent::class, $dispatchedEvents); + $this->assertContains(ContactPersonFullNameChangedEvent::class, $dispatchedEvents); + $this->assertEquals('Jane Doe', $updatedContactPerson->getFullName()->name); + $this->assertEquals('jane.doe@example.com', $updatedContactPerson->getEmail()); + $this->assertEquals('+79997654321', $formattedPhone); + $this->assertEquals($contactPerson->getExternalId(), $updatedContactPerson->getExternalId()); + $this->assertEquals($contactPerson->getBitrix24PartnerId(), $updatedContactPerson->getBitrix24PartnerId()); + } + + private function createPhoneNumber(string $number): PhoneNumber + { + $phoneNumberUtil = PhoneNumberUtil::getInstance(); + return $phoneNumberUtil->parse($number, 'RU'); + } +} \ No newline at end of file From 6c330fc57089f114a166ee22c92683cc41f9f905 Mon Sep 17 00:00:00 2001 From: kirill Date: Sun, 14 Dec 2025 01:29:59 +0300 Subject: [PATCH 06/31] . --- .../UseCase/MarkEmailAsVerified/Command.php | 14 + .../UseCase/MarkEmailAsVerified/Handler.php | 38 +++ .../UseCase/MarkPhoneAsVerified/Command.php | 14 + .../UseCase/MarkPhoneAsVerified/Handler.php | 38 +++ .../UseCase/Uninstall/Command.php | 39 +++ .../UseCase/Uninstall/Handler.php | 104 +++++++ .../UseCase/Install/HandlerTest.php | 8 +- .../MarkEmailAsVerified/HandlerTest.php | 122 ++++++++ .../MarkPhoneAsVerified/HandlerTest.php | 122 ++++++++ .../UseCase/Uninstall/HandlerTest.php | 277 ++++++++++++++++++ 10 files changed, 771 insertions(+), 5 deletions(-) create mode 100644 src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php create mode 100644 src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php create mode 100644 src/ContactPersons/UseCase/MarkPhoneAsVerified/Command.php create mode 100644 src/ContactPersons/UseCase/MarkPhoneAsVerified/Handler.php create mode 100644 src/ContactPersons/UseCase/Uninstall/Command.php create mode 100644 src/ContactPersons/UseCase/Uninstall/Handler.php create mode 100644 tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php create mode 100644 tests/Functional/ContactPersons/UseCase/MarkPhoneAsVerified/HandlerTest.php create mode 100644 tests/Functional/ContactPersons/UseCase/Uninstall/HandlerTest.php diff --git a/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php b/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php new file mode 100644 index 0000000..b46cc4f --- /dev/null +++ b/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php @@ -0,0 +1,14 @@ +logger->info('ContactPerson.ConfirmEmailVerification.start', [ + 'contactPersonId' => $command->contactPersonId->toRfc4122(), + ]); + + /** @var null|AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ + $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); + $contactPerson->markEmailAsVerified(); + + $this->contactPersonRepository->save($contactPerson); + $this->flusher->flush($contactPerson); + + $this->logger->info('ContactPerson.ConfirmEmailVerification.finish', [ + 'contactPersonId' => $contactPerson->getId()->toRfc4122(), + ]); + } +} \ No newline at end of file diff --git a/src/ContactPersons/UseCase/MarkPhoneAsVerified/Command.php b/src/ContactPersons/UseCase/MarkPhoneAsVerified/Command.php new file mode 100644 index 0000000..87b2819 --- /dev/null +++ b/src/ContactPersons/UseCase/MarkPhoneAsVerified/Command.php @@ -0,0 +1,14 @@ +logger->info('ContactPerson.ConfirmEmailVerification.start', [ + 'contactPersonId' => $command->contactPersonId->toRfc4122(), + ]); + + /** @var null|AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ + $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); + $contactPerson->markMobilePhoneAsVerified(); + + $this->contactPersonRepository->save($contactPerson); + $this->flusher->flush($contactPerson); + + $this->logger->info('ContactPerson.ConfirmEmailVerification.finish', [ + 'contactPersonId' => $contactPerson->getId()->toRfc4122(), + ]); + } +} \ No newline at end of file diff --git a/src/ContactPersons/UseCase/Uninstall/Command.php b/src/ContactPersons/UseCase/Uninstall/Command.php new file mode 100644 index 0000000..de0b9da --- /dev/null +++ b/src/ContactPersons/UseCase/Uninstall/Command.php @@ -0,0 +1,39 @@ +validate(); + } + + private function validate(): void + { + if ($this->memberId === null && $this->contactPersonId === null) { + throw new InvalidArgumentException('Either memberId or contactPersonId must be provided.'); + } + + if ($this->memberId !== null && '' === $this->memberId) { + throw new InvalidArgumentException('Member ID must be a non-empty string if provided.'); + } + + if ($this->memberId !== null && $this->contactPersonType === null) { + throw new InvalidArgumentException('ContactPersonType must be provided if memberId is provided.'); + } + } +} diff --git a/src/ContactPersons/UseCase/Uninstall/Handler.php b/src/ContactPersons/UseCase/Uninstall/Handler.php new file mode 100644 index 0000000..ad359b4 --- /dev/null +++ b/src/ContactPersons/UseCase/Uninstall/Handler.php @@ -0,0 +1,104 @@ +logger->info('ContactPerson.Uninstall.start', [ + 'memberId' => $command->memberId, + 'contactPersonType' => $command->contactPersonType?->value, + 'contactPersonId' => $command->contactPersonId?->toRfc4122(), + ]); + + $entitiesToFlush = []; // Объявляем переменную + + // Если передан memberId, пытаемся найти установку и отвязать контактное лицо нужного типа + if ($command->memberId !== null && $command->contactPersonType !== null) { + /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $activeInstallation */ + $activeInstallation = $this->applicationInstallationRepository->findByBitrix24AccountMemberId($command->memberId); + + if ($activeInstallation !== null) { + $contactPersonId = null; + + if ($command->contactPersonType === ContactPersonType::personal) { + $contactPersonId = $activeInstallation->getContactPersonId(); + $activeInstallation->unlinkContactPerson(); + } + + if ($command->contactPersonType === ContactPersonType::partner) { + $contactPersonId = $activeInstallation->getBitrix24PartnerContactPersonId(); + $activeInstallation->unlinkBitrix24PartnerContactPerson(); + } + + $entitiesToFlush[] = $activeInstallation; + $this->applicationInstallationRepository->save($activeInstallation); + + + // Если у установки был контакт, помечаем его как удалённый + if ($contactPersonId !== null) { + /** @var null|AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ + $contactPerson = $this->contactPersonRepository->getById($contactPersonId); + if ($contactPerson !== null) { + $this->logger->info('ContactPerson.Uninstall.deletingContactPersonFromInstallation', [ + 'contactPersonId' => $contactPersonId->toRfc4122(), + ]); + $contactPerson->markAsDeleted($command->comment); + $this->contactPersonRepository->save($contactPerson); + $entitiesToFlush[] = $contactPerson; + } + } + $this->flusher->flush(...array_filter($entitiesToFlush, fn ($entity): bool => $entity instanceof AggregateRootEventsEmitterInterface)); + } + } + + // Если передан ID контактного лица, удаляем его + if ($command->contactPersonId !== null) { + $alreadyDeleted = false; + foreach ($entitiesToFlush as $entity) { + if ($entity instanceof ContactPersonInterface && $entity->getId()->equals($command->contactPersonId)) { + $alreadyDeleted = true; + break; + } + } + + if (!$alreadyDeleted) { + $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); + if ($contactPerson !== null) { + $contactPerson->markAsDeleted($command->comment); + $this->contactPersonRepository->save($contactPerson); + $this->flusher->flush($contactPerson); + } + } + } + + $this->logger->info('ContactPerson.Uninstall.finish', [ + 'memberId' => $command->memberId, + 'contactPersonType' => $command->contactPersonType?->value, + 'contactPersonId' => $command->contactPersonId?->toRfc4122(), + ]); + } +} diff --git a/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php index 750c9b7..8f8a79b 100644 --- a/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php @@ -23,6 +23,7 @@ use Bitrix24\Lib\Tests\Functional\Bitrix24Accounts\Builders\Bitrix24AccountBuilder; use Bitrix24\SDK\Application\ApplicationStatus; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationStatus; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationBitrix24PartnerContactPersonLinkedEvent; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationBitrix24PartnerLinkedEvent; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationContactPersonLinkedEvent; use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus; @@ -203,14 +204,11 @@ public function testNewContactPersonAndLinkApp(): void ); $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); - $foundInstallation = $this->applicationInstallationRepository->findByBitrix24AccountMemberId($bitrix24Account->getMemberId()); - - $this->assertContains(ApplicationInstallationBitrix24PartnerLinkedEvent::class, $dispatchedEvents); - $foundContactPerson = $this->repository->getById($foundInstallation->getBitrix24PartnerContactPersonId()); - $this->assertNotNull($foundContactPerson); + $this->assertContains(ApplicationInstallationBitrix24PartnerContactPersonLinkedEvent::class, $dispatchedEvents); + $this->assertNotNull($foundContactPerson); } diff --git a/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php new file mode 100644 index 0000000..9394860 --- /dev/null +++ b/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\MarkEmailAsVerified; + +use Bitrix24\Lib\ContactPersons\UseCase\MarkEmailAsVerified\Handler; +use Bitrix24\Lib\ContactPersons\UseCase\MarkEmailAsVerified\Command; +use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; +use Bitrix24\Lib\Services\Flusher; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; +use Bitrix24\Lib\Tests\EntityManagerFactory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Uid\Uuid; +use libphonenumber\PhoneNumberUtil; +use libphonenumber\PhoneNumber; +use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; + + +/** + * @internal + */ +#[CoversClass(Handler::class)] +class HandlerTest extends TestCase +{ + private Handler $handler; + + private Flusher $flusher; + + private ContactPersonRepository $repository; + + private TraceableEventDispatcher $eventDispatcher; + + #[\Override] + protected function setUp(): void + { + $entityManager = EntityManagerFactory::get(); + $this->eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $this->repository = new ContactPersonRepository($entityManager); + $this->flusher = new Flusher($entityManager, $this->eventDispatcher); + $this->handler = new Handler( + $this->repository, + $this->flusher, + new NullLogger() + ); + } + + #[Test] + public function testConfirmEmailVerification(): void + { + $contactPersonBuilder = new ContactPersonBuilder(); + $externalId = Uuid::v7()->toRfc4122(); + $bitrix24UserId = random_int(1, 1_000_000); + + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId($externalId) + ->withBitrix24UserId($bitrix24UserId) + ->withBitrix24PartnerId(Uuid::v7()) + ->build(); + + $this->repository->save($contactPerson); + $this->flusher->flush(); + + $this->assertFalse($contactPerson->isEmailVerified()); + + $this->handler->handle( + new Command($contactPerson->getId()) + ); + + $updatedContactPerson = $this->repository->getById($contactPerson->getId()); + $this->assertTrue($updatedContactPerson->isEmailVerified()); + } + + #[Test] + public function testConfirmEmailVerificationFailsIfContactPersonNotFound(): void + { + $contactPersonBuilder = new ContactPersonBuilder(); + $externalId = Uuid::v7()->toRfc4122(); + $bitrix24UserId = random_int(1, 1_000_000); + + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId($externalId) + ->withBitrix24UserId($bitrix24UserId) + ->withBitrix24PartnerId(Uuid::v7()) + ->build(); + + $this->repository->save($contactPerson); + $this->flusher->flush(); + + $this->assertFalse($contactPerson->isEmailVerified()); + + $this->expectException(ContactPersonNotFoundException::class); + $this->handler->handle(new Command(Uuid::v7())); + } + + private function createPhoneNumber(string $number): PhoneNumber + { + $phoneNumberUtil = PhoneNumberUtil::getInstance(); + return $phoneNumberUtil->parse($number, 'RU'); + } +} \ No newline at end of file diff --git a/tests/Functional/ContactPersons/UseCase/MarkPhoneAsVerified/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/MarkPhoneAsVerified/HandlerTest.php new file mode 100644 index 0000000..5964d5d --- /dev/null +++ b/tests/Functional/ContactPersons/UseCase/MarkPhoneAsVerified/HandlerTest.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\MarkPhoneAsVerified; + +use Bitrix24\Lib\ContactPersons\UseCase\MarkPhoneAsVerified\Handler; +use Bitrix24\Lib\ContactPersons\UseCase\MarkPhoneAsVerified\Command; +use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; +use Bitrix24\Lib\Services\Flusher; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; +use Bitrix24\Lib\Tests\EntityManagerFactory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Uid\Uuid; +use libphonenumber\PhoneNumberUtil; +use libphonenumber\PhoneNumber; +use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; + + +/** + * @internal + */ +#[CoversClass(Handler::class)] +class HandlerTest extends TestCase +{ + private Handler $handler; + + private Flusher $flusher; + + private ContactPersonRepository $repository; + + private TraceableEventDispatcher $eventDispatcher; + + #[\Override] + protected function setUp(): void + { + $entityManager = EntityManagerFactory::get(); + $this->eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $this->repository = new ContactPersonRepository($entityManager); + $this->flusher = new Flusher($entityManager, $this->eventDispatcher); + $this->handler = new Handler( + $this->repository, + $this->flusher, + new NullLogger() + ); + } + + #[Test] + public function testConfirmPhoneVerification(): void + { + $contactPersonBuilder = new ContactPersonBuilder(); + $externalId = Uuid::v7()->toRfc4122(); + $bitrix24UserId = random_int(1, 1_000_000); + + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId($externalId) + ->withBitrix24UserId($bitrix24UserId) + ->withBitrix24PartnerId(Uuid::v7()) + ->build(); + + $this->repository->save($contactPerson); + $this->flusher->flush(); + + $this->assertFalse($contactPerson->isMobilePhoneVerified()); + + $this->handler->handle( + new Command($contactPerson->getId()) + ); + + $updatedContactPerson = $this->repository->getById($contactPerson->getId()); + $this->assertTrue($updatedContactPerson->isMobilePhoneVerified()); + } + + #[Test] + public function testConfirmPhoneVerificationFailsIfContactPersonNotFound(): void + { + $contactPersonBuilder = new ContactPersonBuilder(); + $externalId = Uuid::v7()->toRfc4122(); + $bitrix24UserId = random_int(1, 1_000_000); + + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId($externalId) + ->withBitrix24UserId($bitrix24UserId) + ->withBitrix24PartnerId(Uuid::v7()) + ->build(); + + $this->repository->save($contactPerson); + $this->flusher->flush(); + + $this->assertFalse($contactPerson->isMobilePhoneVerified()); + + $this->expectException(ContactPersonNotFoundException::class); + $this->handler->handle(new Command(Uuid::v7())); + } + + private function createPhoneNumber(string $number): PhoneNumber + { + $phoneNumberUtil = PhoneNumberUtil::getInstance(); + return $phoneNumberUtil->parse($number, 'RU'); + } +} \ No newline at end of file diff --git a/tests/Functional/ContactPersons/UseCase/Uninstall/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/Uninstall/HandlerTest.php new file mode 100644 index 0000000..790935f --- /dev/null +++ b/tests/Functional/ContactPersons/UseCase/Uninstall/HandlerTest.php @@ -0,0 +1,277 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\Uninstall; + +use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository; +use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository; +use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; +use Bitrix24\Lib\ContactPersons\UseCase\Uninstall\Handler; +use Bitrix24\Lib\ContactPersons\UseCase\Uninstall\Command; +use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; +use Bitrix24\Lib\Services\Flusher; +use Bitrix24\Lib\Tests\Functional\ApplicationInstallations\Builders\ApplicationInstallationBuilder; +use Bitrix24\Lib\Tests\Functional\Bitrix24Accounts\Builders\Bitrix24AccountBuilder; +use Bitrix24\SDK\Application\ApplicationStatus; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationStatus; +use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; +use Bitrix24\Lib\Tests\EntityManagerFactory; +use Bitrix24\SDK\Application\PortalLicenseFamily; +use Bitrix24\SDK\Core\Credentials\Scope; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Uid\Uuid; +use libphonenumber\PhoneNumberUtil; +use libphonenumber\PhoneNumber; +use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; + + +/** + * @internal + */ +#[CoversClass(Handler::class)] +class HandlerTest extends TestCase +{ + private Handler $handler; + + private Flusher $flusher; + + private ContactPersonRepository $repository; + private ApplicationInstallationRepository $applicationInstallationRepository; + private Bitrix24AccountRepository $bitrix24accountRepository; + + private TraceableEventDispatcher $eventDispatcher; + + #[\Override] + protected function setUp(): void + { + $entityManager = EntityManagerFactory::get(); + $this->eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $this->repository = new ContactPersonRepository($entityManager); + $this->applicationInstallationRepository = new ApplicationInstallationRepository($entityManager); + $this->bitrix24accountRepository = new Bitrix24AccountRepository($entityManager); + $this->flusher = new Flusher($entityManager, $this->eventDispatcher); + $this->handler = new Handler( + $this->applicationInstallationRepository, + $this->repository, + $this->flusher, + new NullLogger() + ); + } + #[Test] + public function testUninstallContactPersonByMemberIdPersonal(): void + { + + $contactPersonBuilder = new ContactPersonBuilder(); + + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->build(); + + $this->repository->save($contactPerson); + + // Load account and application installation into database for uninstallation. + $applicationToken = Uuid::v7()->toRfc4122(); + $memberId = Uuid::v4()->toRfc4122(); + $externalId = Uuid::v7()->toRfc4122(); + $contactPersonId = $contactPerson->getId(); + + $bitrix24Account = (new Bitrix24AccountBuilder()) + ->withApplicationScope(new Scope(['crm'])) + ->withStatus(Bitrix24AccountStatus::new) + ->withApplicationToken($applicationToken) + ->withMemberId($memberId) + ->withMaster(true) + ->withSetToken() + ->withInstalled() + ->build(); + + $this->bitrix24accountRepository->save($bitrix24Account); + + $applicationInstallation = (new ApplicationInstallationBuilder()) + ->withApplicationStatus(new ApplicationStatus('F')) + ->withPortalLicenseFamily(PortalLicenseFamily::free) + ->withBitrix24AccountId($bitrix24Account->getId()) + ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) + ->withApplicationToken($applicationToken) + ->withContactPersonId($contactPersonId) + ->withBitrix24PartnerContactPersonId(null) + ->withExternalId($externalId) + ->build(); + + $this->applicationInstallationRepository->save($applicationInstallation); + + $this->flusher->flush(); + + $this->handler->handle( + new Command($memberId, ContactPersonType::personal) + ); + + $updatedInstallation = $this->applicationInstallationRepository->findByBitrix24AccountMemberId($memberId); + $this->assertNull($updatedInstallation->getContactPersonId()); + + $this->expectException(ContactPersonNotFoundException::class); + $this->repository->getById($contactPersonId); + } + + #[Test] + public function testUninstallContactPersonByMemberIdPartner(): void + { + $contactPersonBuilder = new ContactPersonBuilder(); + + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->build(); + + $this->repository->save($contactPerson); + + // Load account and application installation into database for uninstallation. + $applicationToken = Uuid::v7()->toRfc4122(); + $memberId = Uuid::v4()->toRfc4122(); + $externalId = Uuid::v7()->toRfc4122(); + $contactPersonId = $contactPerson->getId(); + + $bitrix24Account = (new Bitrix24AccountBuilder()) + ->withApplicationScope(new Scope(['crm'])) + ->withStatus(Bitrix24AccountStatus::new) + ->withApplicationToken($applicationToken) + ->withMemberId($memberId) + ->withMaster(true) + ->withSetToken() + ->withInstalled() + ->build(); + + $this->bitrix24accountRepository->save($bitrix24Account); + + $applicationInstallation = (new ApplicationInstallationBuilder()) + ->withApplicationStatus(new ApplicationStatus('F')) + ->withPortalLicenseFamily(PortalLicenseFamily::free) + ->withBitrix24AccountId($bitrix24Account->getId()) + ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) + ->withApplicationToken($applicationToken) + ->withContactPersonId(null) + ->withBitrix24PartnerContactPersonId($contactPersonId) + ->withExternalId($externalId) + ->build(); + + $this->applicationInstallationRepository->save($applicationInstallation); + + $this->flusher->flush(); + + $this->handler->handle( + new Command($memberId, ContactPersonType::partner) + ); + + $updatedInstallation = $this->applicationInstallationRepository->findByBitrix24AccountMemberId($memberId); + $this->assertNull($updatedInstallation->getBitrix24PartnerContactPersonId()); + + $this->expectException(ContactPersonNotFoundException::class); + $this->repository->getById($contactPersonId); + } + + #[Test] + public function testUninstallContactPersonById(): void + { + $contactPersonBuilder = new ContactPersonBuilder(); + + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->build(); + + $this->repository->save($contactPerson); + $this->flusher->flush(); + + $this->handler->handle( + new Command(null, null, $contactPerson->getId()) + ); + + $this->expectException(ContactPersonNotFoundException::class); + $this->repository->getById($contactPerson->getId()); + } + + #[Test] + public function testUninstallContactPersonByMemberIdAndId(): void + { + $contactPersonBuilder = new ContactPersonBuilder(); + + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->build(); + + $this->repository->save($contactPerson); + + // Load account and application installation into database for uninstallation. + $applicationToken = Uuid::v7()->toRfc4122(); + $memberId = Uuid::v4()->toRfc4122(); + $externalId = Uuid::v7()->toRfc4122(); + $contactPersonId = $contactPerson->getId(); + + $bitrix24Account = (new Bitrix24AccountBuilder()) + ->withApplicationScope(new Scope(['crm'])) + ->withStatus(Bitrix24AccountStatus::new) + ->withApplicationToken($applicationToken) + ->withMemberId($memberId) + ->withMaster(true) + ->withSetToken() + ->withInstalled() + ->build(); + + $this->bitrix24accountRepository->save($bitrix24Account); + + $applicationInstallation = (new ApplicationInstallationBuilder()) + ->withApplicationStatus(new ApplicationStatus('F')) + ->withPortalLicenseFamily(PortalLicenseFamily::free) + ->withBitrix24AccountId($bitrix24Account->getId()) + ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) + ->withApplicationToken($applicationToken) + ->withContactPersonId($contactPersonId) + ->withBitrix24PartnerContactPersonId(null) + ->withExternalId($externalId) + ->build(); + + $this->applicationInstallationRepository->save($applicationInstallation); + + $this->flusher->flush(); + + $this->handler->handle( + new Command($memberId, ContactPersonType::personal, $contactPersonId) + ); + + $updatedInstallation = $this->applicationInstallationRepository->findByBitrix24AccountMemberId($memberId); + $this->assertNull($updatedInstallation->getContactPersonId()); + + $this->expectException(ContactPersonNotFoundException::class); + $this->repository->getById($contactPersonId); + } + + private function createPhoneNumber(string $number): PhoneNumber + { + $phoneNumberUtil = PhoneNumberUtil::getInstance(); + return $phoneNumberUtil->parse($number, 'RU'); + } +} \ No newline at end of file From d083cf11bc0ac6028ca381297a8ac03546f2bd97 Mon Sep 17 00:00:00 2001 From: kirill Date: Wed, 17 Dec 2025 00:01:06 +0300 Subject: [PATCH 07/31] . --- src/ContactPersons/Entity/ContactPerson.php | 6 +++--- src/ContactPersons/UseCase/Install/Handler.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ContactPersons/Entity/ContactPerson.php b/src/ContactPersons/Entity/ContactPerson.php index dbb0746..caaa027 100644 --- a/src/ContactPersons/Entity/ContactPerson.php +++ b/src/ContactPersons/Entity/ContactPerson.php @@ -31,9 +31,9 @@ class ContactPerson extends AggregateRoot implements ContactPersonInterface private CarbonImmutable $updatedAt; - private ?bool $isEmailVerified = false; + private bool $isEmailVerified = false; - private ?bool $isMobilePhoneVerified = false; + private bool $isMobilePhoneVerified = false; public function __construct( private readonly Uuid $id, @@ -47,7 +47,7 @@ public function __construct( private ?string $externalId, private readonly ?int $bitrix24UserId, private ?Uuid $bitrix24PartnerId, - private readonly ?UserAgentInfo $userAgentInfo, + private readonly UserAgentInfo $userAgentInfo, private readonly bool $isEmitContactPersonCreatedEvent = false, ) { $this->createdAt = new CarbonImmutable(); diff --git a/src/ContactPersons/UseCase/Install/Handler.php b/src/ContactPersons/UseCase/Install/Handler.php index 1cec3cf..dc0009d 100644 --- a/src/ContactPersons/UseCase/Install/Handler.php +++ b/src/ContactPersons/UseCase/Install/Handler.php @@ -75,7 +75,7 @@ public function handle(Command $command): void $entitiesToFlush[] = $activeInstallation; } - $this->flusher->flush(...array_filter($entitiesToFlush, fn ($entity): bool => $entity instanceof AggregateRootEventsEmitterInterface)); + $this->flusher->flush($activeInstallation,$contactPerson); $this->logger->info('ContactPerson.Install.finish', [ 'contact_person_id' => $uuidV7, From fe89ec452e10f9d88427fcccf99936d15d7b1fbb Mon Sep 17 00:00:00 2001 From: kirill Date: Wed, 17 Dec 2025 23:00:46 +0300 Subject: [PATCH 08/31] . --- ...trix24.Lib.ContactPersons.Entity.ContactPerson.dcm.xml | 4 ++-- ....Contracts.ContactPersons.Entity.UserAgentInfo.dcm.xml | 2 +- src/ContactPersons/Entity/ContactPerson.php | 5 ++--- .../Infrastructure/Doctrine/ContactPersonRepository.php | 4 ++-- src/ContactPersons/UseCase/Install/Command.php | 8 +++----- src/ContactPersons/UseCase/Install/Handler.php | 4 +--- 6 files changed, 11 insertions(+), 16 deletions(-) diff --git a/config/xml/Bitrix24.Lib.ContactPersons.Entity.ContactPerson.dcm.xml b/config/xml/Bitrix24.Lib.ContactPersons.Entity.ContactPerson.dcm.xml index cfd0668..e641458 100644 --- a/config/xml/Bitrix24.Lib.ContactPersons.Entity.ContactPerson.dcm.xml +++ b/config/xml/Bitrix24.Lib.ContactPersons.Entity.ContactPerson.dcm.xml @@ -10,14 +10,14 @@ - + - + diff --git a/config/xml/Bitrix24.SDK.Application.Contracts.ContactPersons.Entity.UserAgentInfo.dcm.xml b/config/xml/Bitrix24.SDK.Application.Contracts.ContactPersons.Entity.UserAgentInfo.dcm.xml index 7de53bc..779b038 100644 --- a/config/xml/Bitrix24.SDK.Application.Contracts.ContactPersons.Entity.UserAgentInfo.dcm.xml +++ b/config/xml/Bitrix24.SDK.Application.Contracts.ContactPersons.Entity.UserAgentInfo.dcm.xml @@ -5,6 +5,6 @@ - + \ No newline at end of file diff --git a/src/ContactPersons/Entity/ContactPerson.php b/src/ContactPersons/Entity/ContactPerson.php index caaa027..85af3b9 100644 --- a/src/ContactPersons/Entity/ContactPerson.php +++ b/src/ContactPersons/Entity/ContactPerson.php @@ -45,7 +45,7 @@ public function __construct( private ?CarbonImmutable $mobilePhoneVerifiedAt, private ?string $comment, private ?string $externalId, - private readonly ?int $bitrix24UserId, + private readonly int $bitrix24UserId, private ?Uuid $bitrix24PartnerId, private readonly UserAgentInfo $userAgentInfo, private readonly bool $isEmitContactPersonCreatedEvent = false, @@ -202,10 +202,9 @@ public function changeMobilePhone(?PhoneNumber $phoneNumber): void if (PhoneNumberType::MOBILE !== $numberType) { throw new InvalidArgumentException('Phone number must be mobile.'); } - - $this->mobilePhoneNumber = $phoneNumber; } + $this->mobilePhoneNumber = $phoneNumber; $this->updatedAt = new CarbonImmutable(); $this->events[] = new ContactPersonMobilePhoneChangedEvent( diff --git a/src/ContactPersons/Infrastructure/Doctrine/ContactPersonRepository.php b/src/ContactPersons/Infrastructure/Doctrine/ContactPersonRepository.php index 693b3f4..4fb9b76 100644 --- a/src/ContactPersons/Infrastructure/Doctrine/ContactPersonRepository.php +++ b/src/ContactPersons/Infrastructure/Doctrine/ContactPersonRepository.php @@ -18,7 +18,7 @@ class ContactPersonRepository implements ContactPersonRepositoryInterface { - private readonly EntityRepository $repository; // Внутренний репозиторий для базовых операций + private readonly EntityRepository $repository; public function __construct(private readonly EntityManagerInterface $entityManager) { @@ -128,7 +128,7 @@ public function findByExternalId(string $externalId, ?ContactPersonStatus $conta $criteria = ['externalId' => $externalId]; if ($contactPersonStatus instanceof ContactPersonStatus) { - $criteria['contactPersonStatus'] = $contactPersonStatus->name; + $criteria['status'] = $contactPersonStatus->name; } return $this->repository->findBy($criteria); diff --git a/src/ContactPersons/UseCase/Install/Command.php b/src/ContactPersons/UseCase/Install/Command.php index 487436b..3628948 100644 --- a/src/ContactPersons/UseCase/Install/Command.php +++ b/src/ContactPersons/UseCase/Install/Command.php @@ -6,6 +6,7 @@ use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\FullName; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\UserAgentInfo; use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; use Darsyn\IP\Version\Multi as IP; use libphonenumber\PhoneNumber; @@ -19,12 +20,9 @@ public function __construct( public ?PhoneNumber $mobilePhoneNumber, public ?string $comment, public ?string $externalId, - public ?int $bitrix24UserId, + public int $bitrix24UserId, public ?Uuid $bitrix24PartnerId, - public ?IP $userAgentIp, - public ?string $userAgent, - public ?string $userAgentReferrer, - public string $userAgentVersion, + public UserAgentInfo $userAgentInfo, public ?string $memberId, public ?ContactPersonType $contactPersonType ) { diff --git a/src/ContactPersons/UseCase/Install/Handler.php b/src/ContactPersons/UseCase/Install/Handler.php index dc0009d..de497bf 100644 --- a/src/ContactPersons/UseCase/Install/Handler.php +++ b/src/ContactPersons/UseCase/Install/Handler.php @@ -33,8 +33,6 @@ public function handle(Command $command): void 'contactPersonType' => $command->contactPersonType, ]); - $userAgentInfo = new UserAgentInfo($command->userAgentIp, $command->userAgent, $command->userAgentReferrer); - $uuidV7 = Uuid::v7(); $entitiesToFlush = []; @@ -51,7 +49,7 @@ public function handle(Command $command): void $command->externalId, $command->bitrix24UserId, $command->bitrix24PartnerId, - $userAgentInfo, + $command->userAgentInfo, true ); From 289aba951091abc5d943d9822b5c4a1c11625e5c Mon Sep 17 00:00:00 2001 From: kirill Date: Sat, 20 Dec 2025 00:43:11 +0300 Subject: [PATCH 09/31] . --- .../UseCase/Install/Command.php | 50 ------------------- .../UseCase/InstallContactPerson/Command.php | 45 +++++++++++++++++ .../Handler.php | 39 +++++---------- .../UseCase/Install/HandlerTest.php | 44 +--------------- 4 files changed, 59 insertions(+), 119 deletions(-) delete mode 100644 src/ContactPersons/UseCase/Install/Command.php create mode 100644 src/ContactPersons/UseCase/InstallContactPerson/Command.php rename src/ContactPersons/UseCase/{Install => InstallContactPerson}/Handler.php (56%) diff --git a/src/ContactPersons/UseCase/Install/Command.php b/src/ContactPersons/UseCase/Install/Command.php deleted file mode 100644 index 3628948..0000000 --- a/src/ContactPersons/UseCase/Install/Command.php +++ /dev/null @@ -1,50 +0,0 @@ -validate(); - } - - private function validate(): void - { - if ('' === $this->fullName->name) { - throw new \InvalidArgumentException('Full name cannot be empty.'); - } - - if (null !== $this->email && '' === trim($this->email)) { - throw new \InvalidArgumentException('Email cannot be empty if provided.'); - } - - if (null !== $this->externalId && '' === trim($this->externalId)) { - throw new \InvalidArgumentException('External ID cannot be empty if provided.'); - } - - if ('' === $this->memberId) { - throw new InvalidArgumentException('Member ID must be a non-empty string.'); - } - } -} diff --git a/src/ContactPersons/UseCase/InstallContactPerson/Command.php b/src/ContactPersons/UseCase/InstallContactPerson/Command.php new file mode 100644 index 0000000..1b88982 --- /dev/null +++ b/src/ContactPersons/UseCase/InstallContactPerson/Command.php @@ -0,0 +1,45 @@ +validate(); + } + + private function validate(): void + { + if (null !== $this->email && !filter_var($this->email, FILTER_VALIDATE_EMAIL)) { + throw new \InvalidArgumentException('Invalid email format.'); + } + + if (null !== $this->externalId && '' === trim($this->externalId)) { + throw new \InvalidArgumentException('External ID cannot be empty if provided.'); + } + + if ($this->bitrix24UserId <= 0) { + throw new \InvalidArgumentException('Bitrix24 User ID must be a positive integer.'); + } + } +} diff --git a/src/ContactPersons/UseCase/Install/Handler.php b/src/ContactPersons/UseCase/InstallContactPerson/Handler.php similarity index 56% rename from src/ContactPersons/UseCase/Install/Handler.php rename to src/ContactPersons/UseCase/InstallContactPerson/Handler.php index de497bf..6d9c56f 100644 --- a/src/ContactPersons/UseCase/Install/Handler.php +++ b/src/ContactPersons/UseCase/InstallContactPerson/Handler.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Bitrix24\Lib\ContactPersons\UseCase\Install; +namespace Bitrix24\Lib\ContactPersons\UseCase\InstallContactPerson; use Bitrix24\Lib\ContactPersons\Entity\ContactPerson; use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; @@ -27,16 +27,13 @@ public function __construct( public function handle(Command $command): void { - $this->logger->info('ContactPerson.Install.start', [ - 'externalId' => $command->externalId, - 'memberId' => $command->memberId, - 'contactPersonType' => $command->contactPersonType, + $this->logger->info('ContactPerson.InstallContactPerson.start', [ + 'applicationInstallationId' => $command->applicationInstallationId, + 'bitrix24UserId' => $command->bitrix24UserId, ]); $uuidV7 = Uuid::v7(); - $entitiesToFlush = []; - $contactPerson = new ContactPerson( $uuidV7, ContactPersonStatus::active, @@ -55,29 +52,17 @@ public function handle(Command $command): void $this->contactPersonRepository->save($contactPerson); - $entitiesToFlush[] = $contactPerson; - - if (null !== $command->memberId) { - /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $activeInstallation */ - $activeInstallation = $this->applicationInstallationRepository->findByBitrix24AccountMemberId($command->memberId); - - if (ContactPersonType::personal == $command->contactPersonType) { - $activeInstallation->linkContactPerson($uuidV7); - } - - if (ContactPersonType::partner == $command->contactPersonType) { - $activeInstallation->linkBitrix24PartnerContactPerson($uuidV7); - } + /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $activeInstallation */ + $activeInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); - $this->applicationInstallationRepository->save($activeInstallation); - $entitiesToFlush[] = $activeInstallation; - } + $activeInstallation->linkContactPerson($uuidV7); + $this->applicationInstallationRepository->save($activeInstallation); - $this->flusher->flush($activeInstallation,$contactPerson); + $this->flusher->flush(); - $this->logger->info('ContactPerson.Install.finish', [ - 'contact_person_id' => $uuidV7, - 'externalId' => $command->externalId, + $this->logger->info('ContactPerson.InstallContactPerson.finish', [ + 'contact_person_id' => $uuidV7->toRfc4122(), + 'applicationInstallationId' => $command->applicationInstallationId, ]); } } diff --git a/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php index 8f8a79b..3b0f0d1 100644 --- a/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php @@ -15,8 +15,8 @@ use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository; use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository; -use Bitrix24\Lib\ContactPersons\UseCase\Install\Handler; -use Bitrix24\Lib\ContactPersons\UseCase\Install\Command; +use Bitrix24\Lib\ContactPersons\UseCase\InstallContactPerson\Handler; +use Bitrix24\Lib\ContactPersons\UseCase\InstallContactPerson\Command; use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; use Bitrix24\Lib\Services\Flusher; use Bitrix24\Lib\Tests\Functional\ApplicationInstallations\Builders\ApplicationInstallationBuilder; @@ -211,46 +211,6 @@ public function testNewContactPersonAndLinkApp(): void $this->assertNotNull($foundContactPerson); } - - /* - * Что такое externalId? Вроде бы это подпись. Тогда по сути у нас может на 1 подпись быть 2 контактных лица. - */ - #[Test] - public function testContactPersonWithDuplicateExternalId(): void - { - $contactPersonBuilder = new ContactPersonBuilder(); - $contactPerson = $contactPersonBuilder - ->withEmail('alice.cooper@example.com') - ->withMobilePhoneNumber($this->createPhoneNumber('+79991112222')) - ->withExternalId('duplicate-ext') - ->withBitrix24UserId(789) - ->withBitrix24PartnerId(Uuid::v7()) - ->build(); - - $this->repository->save($contactPerson); - - $this->flusher->flush(); - - $command = new Command( - $contactPerson->getFullName(), - $contactPerson->getEmail(), - $contactPerson->getMobilePhone(), - $contactPerson->getComment(), - $contactPerson->getExternalId(), - $contactPerson->getBitrix24UserId(), - $contactPerson->getBitrix24PartnerId(), - $contactPerson->getUserAgentInfo()->ip, - $contactPerson->getUserAgentInfo()->userAgent, - $contactPerson->getUserAgentInfo()->referrer, - '1.0', - null, - null - ); - - $this->expectException(InvalidArgumentException::class); - $this->handler->handle($command); - } - private function createPhoneNumber(string $number): PhoneNumber { $phoneNumberUtil = PhoneNumberUtil::getInstance(); From 601fbd40c9a4a409fb12d8fcb89d45c1031770a0 Mon Sep 17 00:00:00 2001 From: kirill Date: Sat, 20 Dec 2025 11:03:32 +0300 Subject: [PATCH 10/31] . --- .../UseCase/InstallContactPerson/Handler.php | 7 +- .../InstallPartnerContactPerson/Command.php | 45 +++++++ .../InstallPartnerContactPerson/Handler.php | 67 ++++++++++ .../HandlerTest.php | 126 ++++++++---------- 4 files changed, 170 insertions(+), 75 deletions(-) create mode 100644 src/ContactPersons/UseCase/InstallPartnerContactPerson/Command.php create mode 100644 src/ContactPersons/UseCase/InstallPartnerContactPerson/Handler.php rename tests/Functional/ContactPersons/UseCase/{Install => InstallContactPerson}/HandlerTest.php (74%) diff --git a/src/ContactPersons/UseCase/InstallContactPerson/Handler.php b/src/ContactPersons/UseCase/InstallContactPerson/Handler.php index 6d9c56f..36a4ca6 100644 --- a/src/ContactPersons/UseCase/InstallContactPerson/Handler.php +++ b/src/ContactPersons/UseCase/InstallContactPerson/Handler.php @@ -32,6 +32,9 @@ public function handle(Command $command): void 'bitrix24UserId' => $command->bitrix24UserId, ]); + /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $activeInstallation */ + $activeInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); + $uuidV7 = Uuid::v7(); $contactPerson = new ContactPerson( @@ -52,9 +55,6 @@ public function handle(Command $command): void $this->contactPersonRepository->save($contactPerson); - /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $activeInstallation */ - $activeInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); - $activeInstallation->linkContactPerson($uuidV7); $this->applicationInstallationRepository->save($activeInstallation); @@ -62,7 +62,6 @@ public function handle(Command $command): void $this->logger->info('ContactPerson.InstallContactPerson.finish', [ 'contact_person_id' => $uuidV7->toRfc4122(), - 'applicationInstallationId' => $command->applicationInstallationId, ]); } } diff --git a/src/ContactPersons/UseCase/InstallPartnerContactPerson/Command.php b/src/ContactPersons/UseCase/InstallPartnerContactPerson/Command.php new file mode 100644 index 0000000..c229bf3 --- /dev/null +++ b/src/ContactPersons/UseCase/InstallPartnerContactPerson/Command.php @@ -0,0 +1,45 @@ +validate(); + } + + private function validate(): void + { + if (null !== $this->email && !filter_var($this->email, FILTER_VALIDATE_EMAIL)) { + throw new \InvalidArgumentException('Invalid email format.'); + } + + if (null !== $this->externalId && '' === trim($this->externalId)) { + throw new \InvalidArgumentException('External ID cannot be empty if provided.'); + } + + if ($this->bitrix24UserId <= 0) { + throw new \InvalidArgumentException('Bitrix24 User ID must be a positive integer.'); + } + } +} diff --git a/src/ContactPersons/UseCase/InstallPartnerContactPerson/Handler.php b/src/ContactPersons/UseCase/InstallPartnerContactPerson/Handler.php new file mode 100644 index 0000000..2e8be6d --- /dev/null +++ b/src/ContactPersons/UseCase/InstallPartnerContactPerson/Handler.php @@ -0,0 +1,67 @@ +logger->info('ContactPerson.InstallPartnerContactPerson.start', [ + 'applicationInstallationId' => $command->applicationInstallationId, + 'bitrix24UserId' => $command->bitrix24UserId, + ]); + + /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $activeInstallation */ + $activeInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); + + $uuidV7 = Uuid::v7(); + + $contactPerson = new ContactPerson( + $uuidV7, + ContactPersonStatus::active, + $command->fullName, + $command->email, + null, + $command->mobilePhoneNumber, + null, + $command->comment, + $command->externalId, + $command->bitrix24UserId, + $command->bitrix24PartnerId, + $command->userAgentInfo, + true + ); + + $this->contactPersonRepository->save($contactPerson); + + $activeInstallation->linkBitrix24PartnerContactPerson($uuidV7); + $this->applicationInstallationRepository->save($activeInstallation); + + $this->flusher->flush(); + + $this->logger->info('ContactPerson.InstallPartnerContactPerson.finish', [ + 'contact_person_id' => $uuidV7->toRfc4122(), + ]); + } +} diff --git a/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/InstallContactPerson/HandlerTest.php similarity index 74% rename from tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php rename to tests/Functional/ContactPersons/UseCase/InstallContactPerson/HandlerTest.php index 3b0f0d1..9d4d920 100644 --- a/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/InstallContactPerson/HandlerTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\Install; +namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\InstallContactPerson; use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository; use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository; @@ -35,6 +35,7 @@ use Bitrix24\SDK\Core\Credentials\Scope; use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; use Bitrix24\Lib\Tests\EntityManagerFactory; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Exceptions\ApplicationInstallationNotFoundException; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; @@ -87,62 +88,9 @@ protected function setUp(): void * @throws InvalidArgumentException|\Random\RandomException */ #[Test] - public function testNewContactPerson(): void + public function testInstallContactPersonSuccess(): void { - $contactPersonBuilder = new ContactPersonBuilder(); - $externalId = Uuid::v7()->toRfc4122(); - $bitrix24UserId = random_int(1, 1_000_000); - - $contactPerson = $contactPersonBuilder - ->withEmail('john.doe@example.com') - ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) - ->withComment('Test comment') - ->withExternalId($externalId) - ->withBitrix24UserId($bitrix24UserId) - ->withBitrix24PartnerId(Uuid::v7()) - ->build(); - - - $this->handler->handle( - new Command( - $contactPerson->getFullName(), - $contactPerson->getEmail(), - $contactPerson->getMobilePhone(), - $contactPerson->getComment(), - $contactPerson->getExternalId(), - $contactPerson->getBitrix24UserId(), - $contactPerson->getBitrix24PartnerId(), - $contactPerson->getUserAgentInfo()->ip, - $contactPerson->getUserAgentInfo()->userAgent, - $contactPerson->getUserAgentInfo()->referrer, - '1.0', - null, - null - ) - ); - - $contactPersonFromRepo = $this->repository->findByExternalId($contactPerson->getExternalId()); - - $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); - $this->assertContains(ContactPersonCreatedEvent::class, $dispatchedEvents); - $this->assertNotContains(ApplicationInstallationContactPersonLinkedEvent::class, $dispatchedEvents); - $this->assertNotContains(ApplicationInstallationBitrix24PartnerLinkedEvent::class, $dispatchedEvents); - - $this->assertCount(1, $contactPersonFromRepo); - - $foundContactPerson = reset($contactPersonFromRepo); - - $this->assertInstanceOf(ContactPersonInterface::class, $foundContactPerson); - $this->assertEquals($contactPerson->getFullName()->name, $foundContactPerson->getFullName()->name); - $this->assertEquals($contactPerson->getEmail(), $foundContactPerson->getEmail()); - $this->assertEquals($contactPerson->getMobilePhone(), $foundContactPerson->getMobilePhone()); - $this->assertEquals(ContactPersonStatus::active, $foundContactPerson->getStatus()); - } - - #[Test] - public function testNewContactPersonAndLinkApp(): void - { - // Load account and application installation into database for uninstallation. + // Подготовка Bitrix24 аккаунта и установки приложения $applicationToken = Uuid::v7()->toRfc4122(); $memberId = Uuid::v4()->toRfc4122(); $externalId = Uuid::v7()->toRfc4122(); @@ -171,44 +119,80 @@ public function testNewContactPersonAndLinkApp(): void ->build(); $this->applicationInstallationRepository->save($applicationInstallation); - $this->flusher->flush(); + // Данные контакта $contactPersonBuilder = new ContactPersonBuilder(); - $contactPerson = $contactPersonBuilder ->withEmail('john.doe@example.com') ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) ->withComment('Test comment') - ->withExternalId($applicationInstallation->getExternalId()) + ->withExternalId($externalId) ->withBitrix24UserId($bitrix24Account->getBitrix24UserId()) - ->withBitrix24PartnerId($applicationInstallation->getBitrix24PartnerId()) + ->withBitrix24PartnerId(null === $applicationInstallation->getBitrix24PartnerId() ? Uuid::v7() : $applicationInstallation->getBitrix24PartnerId()) ->build(); + // Запуск use-case $this->handler->handle( new Command( + $applicationInstallation->getId(), $contactPerson->getFullName(), + $bitrix24Account->getBitrix24UserId(), + $contactPerson->getUserAgentInfo(), $contactPerson->getEmail(), $contactPerson->getMobilePhone(), $contactPerson->getComment(), $contactPerson->getExternalId(), - $contactPerson->getBitrix24UserId(), $contactPerson->getBitrix24PartnerId(), - $contactPerson->getUserAgentInfo()->ip, - $contactPerson->getUserAgentInfo()->userAgent, - $contactPerson->getUserAgentInfo()->referrer, - '1.0', - $bitrix24Account->getMemberId(), - ContactPersonType::partner ) ); + // Проверки: событие, связь и наличие контакта $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); - $foundInstallation = $this->applicationInstallationRepository->findByBitrix24AccountMemberId($bitrix24Account->getMemberId()); - $foundContactPerson = $this->repository->getById($foundInstallation->getBitrix24PartnerContactPersonId()); + $this->assertContains(ContactPersonCreatedEvent::class, $dispatchedEvents); + $this->assertContains(ApplicationInstallationContactPersonLinkedEvent::class, $dispatchedEvents); + + // Найдём контакт по externalId + $contactPersonFromRepo = $this->repository->findByExternalId($contactPerson->getExternalId()); + $this->assertCount(1, $contactPersonFromRepo); + $foundContactPerson = reset($contactPersonFromRepo); + $this->assertInstanceOf(ContactPersonInterface::class, $foundContactPerson); - $this->assertContains(ApplicationInstallationBitrix24PartnerContactPersonLinkedEvent::class, $dispatchedEvents); - $this->assertNotNull($foundContactPerson); + // Перечитаем установку и проверим привязку контактного лица + $foundInstallation = $this->applicationInstallationRepository->getById($applicationInstallation->getId()); + $this->assertNotNull($foundInstallation->getContactPersonId()); + $this->assertEquals($foundContactPerson->getId(), $foundInstallation->getContactPersonId()); + } + + #[Test] + public function testInstallContactPersonWithWrongApplicationInstallationId(): void + { + // Подготовим входные данные контакта (без реальной установки) + $contactPersonBuilder = new ContactPersonBuilder(); + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId(Uuid::v7()->toRfc4122()) + ->build(); + + $wrongInstallationId = Uuid::v7(); + + $this->expectException(ApplicationInstallationNotFoundException::class); + + $this->handler->handle( + new Command( + $wrongInstallationId, + $contactPerson->getFullName(), + random_int(1, 1_000_000), + $contactPerson->getUserAgentInfo(), + $contactPerson->getEmail(), + $contactPerson->getMobilePhone(), + $contactPerson->getComment(), + $contactPerson->getExternalId(), + $contactPerson->getBitrix24PartnerId(), + ) + ); } private function createPhoneNumber(string $number): PhoneNumber From c4f727a69f828a7236f4724c34eaee73e9460e14 Mon Sep 17 00:00:00 2001 From: kirill Date: Sat, 20 Dec 2025 13:25:03 +0300 Subject: [PATCH 11/31] . --- .../UseCase/InstallContactPerson/Handler.php | 2 +- .../InstallPartnerContactPerson/Handler.php | 2 +- .../InstallContactPerson/HandlerTest.php | 14 +- .../HandlerTest.php | 201 ++++++++++++++++++ 4 files changed, 209 insertions(+), 10 deletions(-) create mode 100644 tests/Functional/ContactPersons/UseCase/InstallPartnerContactPerson/HandlerTest.php diff --git a/src/ContactPersons/UseCase/InstallContactPerson/Handler.php b/src/ContactPersons/UseCase/InstallContactPerson/Handler.php index 36a4ca6..57fd3c2 100644 --- a/src/ContactPersons/UseCase/InstallContactPerson/Handler.php +++ b/src/ContactPersons/UseCase/InstallContactPerson/Handler.php @@ -58,7 +58,7 @@ public function handle(Command $command): void $activeInstallation->linkContactPerson($uuidV7); $this->applicationInstallationRepository->save($activeInstallation); - $this->flusher->flush(); + $this->flusher->flush($contactPerson,$activeInstallation); $this->logger->info('ContactPerson.InstallContactPerson.finish', [ 'contact_person_id' => $uuidV7->toRfc4122(), diff --git a/src/ContactPersons/UseCase/InstallPartnerContactPerson/Handler.php b/src/ContactPersons/UseCase/InstallPartnerContactPerson/Handler.php index 2e8be6d..a01461c 100644 --- a/src/ContactPersons/UseCase/InstallPartnerContactPerson/Handler.php +++ b/src/ContactPersons/UseCase/InstallPartnerContactPerson/Handler.php @@ -58,7 +58,7 @@ public function handle(Command $command): void $activeInstallation->linkBitrix24PartnerContactPerson($uuidV7); $this->applicationInstallationRepository->save($activeInstallation); - $this->flusher->flush(); + $this->flusher->flush($contactPerson,$activeInstallation); $this->logger->info('ContactPerson.InstallPartnerContactPerson.finish', [ 'contact_person_id' => $uuidV7->toRfc4122(), diff --git a/tests/Functional/ContactPersons/UseCase/InstallContactPerson/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/InstallContactPerson/HandlerTest.php index 9d4d920..89cd17c 100644 --- a/tests/Functional/ContactPersons/UseCase/InstallContactPerson/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/InstallContactPerson/HandlerTest.php @@ -152,16 +152,14 @@ public function testInstallContactPersonSuccess(): void $this->assertContains(ContactPersonCreatedEvent::class, $dispatchedEvents); $this->assertContains(ApplicationInstallationContactPersonLinkedEvent::class, $dispatchedEvents); - // Найдём контакт по externalId - $contactPersonFromRepo = $this->repository->findByExternalId($contactPerson->getExternalId()); - $this->assertCount(1, $contactPersonFromRepo); - $foundContactPerson = reset($contactPersonFromRepo); - $this->assertInstanceOf(ContactPersonInterface::class, $foundContactPerson); - - // Перечитаем установку и проверим привязку контактного лица + // Перечитаем установку и проверим привязку контактного лица (без поиска по externalId) $foundInstallation = $this->applicationInstallationRepository->getById($applicationInstallation->getId()); $this->assertNotNull($foundInstallation->getContactPersonId()); - $this->assertEquals($foundContactPerson->getId(), $foundInstallation->getContactPersonId()); + + $contactPersonId = $foundInstallation->getContactPersonId(); + $foundContactPerson = $this->repository->getById($contactPersonId); + $this->assertInstanceOf(ContactPersonInterface::class, $foundContactPerson); + $this->assertEquals($foundContactPerson->getId(), $contactPersonId); } #[Test] diff --git a/tests/Functional/ContactPersons/UseCase/InstallPartnerContactPerson/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/InstallPartnerContactPerson/HandlerTest.php new file mode 100644 index 0000000..c19ca9f --- /dev/null +++ b/tests/Functional/ContactPersons/UseCase/InstallPartnerContactPerson/HandlerTest.php @@ -0,0 +1,201 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\InstallPartnerContactPerson; + +use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository; +use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository; +use Bitrix24\Lib\ContactPersons\UseCase\InstallPartnerContactPerson\Handler; +use Bitrix24\Lib\ContactPersons\UseCase\InstallPartnerContactPerson\Command; +use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; +use Bitrix24\Lib\Services\Flusher; +use Bitrix24\Lib\Tests\Functional\ApplicationInstallations\Builders\ApplicationInstallationBuilder; +use Bitrix24\Lib\Tests\Functional\Bitrix24Accounts\Builders\Bitrix24AccountBuilder; +use Bitrix24\SDK\Application\ApplicationStatus; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationStatus; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationBitrix24PartnerContactPersonLinkedEvent; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationBitrix24PartnerLinkedEvent; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationContactPersonLinkedEvent; +use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonCreatedEvent; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonEmailChangedEvent; +use Bitrix24\SDK\Application\PortalLicenseFamily; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\Lib\Tests\EntityManagerFactory; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Exceptions\ApplicationInstallationNotFoundException; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Uid\Uuid; +use libphonenumber\PhoneNumberUtil; +use libphonenumber\PhoneNumber; +use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; +use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; + +/** + * @internal + */ +#[CoversClass(Handler::class)] +class HandlerTest extends TestCase +{ + private Handler $handler; + + private Flusher $flusher; + + private ContactPersonRepository $repository; + + private ApplicationInstallationRepository $applicationInstallationRepository; + + private Bitrix24AccountRepository $bitrix24accountRepository; + + private TraceableEventDispatcher $eventDispatcher; + + #[\Override] + protected function setUp(): void + { + $entityManager = EntityManagerFactory::get(); + $this->eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $this->repository = new ContactPersonRepository($entityManager); + $this->applicationInstallationRepository = new ApplicationInstallationRepository($entityManager); + $this->bitrix24accountRepository = new Bitrix24AccountRepository($entityManager); + $this->flusher = new Flusher($entityManager, $this->eventDispatcher); + $this->handler = new Handler( + $this->applicationInstallationRepository, + $this->repository, + $this->flusher, + new NullLogger() + ); + } + + /** + * @throws InvalidArgumentException|\Random\RandomException + */ + #[Test] + public function testInstallPartnerContactPersonSuccess(): void + { + // Подготовка Bitrix24 аккаунта и установки приложения + $applicationToken = Uuid::v7()->toRfc4122(); + $memberId = Uuid::v4()->toRfc4122(); + $externalId = Uuid::v7()->toRfc4122(); + + $bitrix24Account = (new Bitrix24AccountBuilder()) + ->withApplicationScope(new Scope(['crm'])) + ->withStatus(Bitrix24AccountStatus::new) + ->withApplicationToken($applicationToken) + ->withMemberId($memberId) + ->withMaster(true) + ->withSetToken() + ->withInstalled() + ->build(); + + $this->bitrix24accountRepository->save($bitrix24Account); + + $applicationInstallation = (new ApplicationInstallationBuilder()) + ->withApplicationStatus(new ApplicationStatus('F')) + ->withPortalLicenseFamily(PortalLicenseFamily::free) + ->withBitrix24AccountId($bitrix24Account->getId()) + ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) + ->withApplicationToken($applicationToken) + ->withContactPersonId(null) + ->withBitrix24PartnerContactPersonId(null) + ->withExternalId($externalId) + ->build(); + + $this->applicationInstallationRepository->save($applicationInstallation); + $this->flusher->flush(); + + // Данные контакта + $contactPersonBuilder = new ContactPersonBuilder(); + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId($externalId) + ->withBitrix24UserId($bitrix24Account->getBitrix24UserId()) + ->withBitrix24PartnerId(null === $applicationInstallation->getBitrix24PartnerId() ? Uuid::v7() : $applicationInstallation->getBitrix24PartnerId()) + ->build(); + + // Запуск use-case + $this->handler->handle( + new Command( + $applicationInstallation->getId(), + $contactPerson->getFullName(), + $bitrix24Account->getBitrix24UserId(), + $contactPerson->getUserAgentInfo(), + $contactPerson->getEmail(), + $contactPerson->getMobilePhone(), + $contactPerson->getComment(), + $contactPerson->getExternalId(), + $contactPerson->getBitrix24PartnerId(), + ) + ); + + // Проверки: событие, связь и наличие контакта + $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); + $this->assertContains(ContactPersonCreatedEvent::class, $dispatchedEvents); + $this->assertContains(ApplicationInstallationBitrix24PartnerContactPersonLinkedEvent::class, $dispatchedEvents); + + // Перечитаем установку и проверим привязку контактного лица (без поиска по externalId) + $foundInstallation = $this->applicationInstallationRepository->getById($applicationInstallation->getId()); + $this->assertNotNull($foundInstallation->getBitrix24PartnerContactPersonId()); + + $bitrix24PartnerContactPersonId = $foundInstallation->getBitrix24PartnerContactPersonId(); + $foundContactPerson = $this->repository->getById($bitrix24PartnerContactPersonId); + $this->assertInstanceOf(ContactPersonInterface::class, $foundContactPerson); + $this->assertEquals($foundContactPerson->getId(), $bitrix24PartnerContactPersonId); + } + + #[Test] + public function testInstallPartnerContactPersonWithWrongApplicationInstallationId(): void + { + // Подготовим входные данные контакта (без реальной установки) + $contactPersonBuilder = new ContactPersonBuilder(); + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId(Uuid::v7()->toRfc4122()) + ->build(); + + $wrongInstallationId = Uuid::v7(); + + $this->expectException(ApplicationInstallationNotFoundException::class); + + $this->handler->handle( + new Command( + $wrongInstallationId, + $contactPerson->getFullName(), + random_int(1, 1_000_000), + $contactPerson->getUserAgentInfo(), + $contactPerson->getEmail(), + $contactPerson->getMobilePhone(), + $contactPerson->getComment(), + $contactPerson->getExternalId(), + $contactPerson->getBitrix24PartnerId(), + ) + ); + } + + private function createPhoneNumber(string $number): PhoneNumber + { + $phoneNumberUtil = PhoneNumberUtil::getInstance(); + return $phoneNumberUtil->parse($number, 'RU'); + } +} \ No newline at end of file From 896cdf91e14735e148c76a2e48e7c20f410cc466 Mon Sep 17 00:00:00 2001 From: kirill Date: Sun, 21 Dec 2025 10:43:10 +0300 Subject: [PATCH 12/31] . --- .../UseCase/InstallContactPerson/Command.php | 21 +- .../UseCase/InstallContactPerson/Handler.php | 12 +- .../InstallPartnerContactPerson/Command.php | 21 +- .../InstallPartnerContactPerson/Handler.php | 12 +- .../UseCase/MarkEmailAsVerified/Command.php | 4 +- .../UseCase/MarkEmailAsVerified/Handler.php | 2 +- .../UseCase/MarkPhoneAsVerified/Command.php | 4 +- .../UseCase/MarkPhoneAsVerified/Handler.php | 2 +- .../UseCase/Uninstall/Command.php | 39 --- .../UseCase/Uninstall/Handler.php | 104 ------- .../UseCase/UnlinkContactPerson/Command.php | 22 ++ .../UseCase/UnlinkContactPerson/Handler.php | 68 +++++ .../UnlinkPartnerContactPerson/Command.php | 22 ++ .../UnlinkPartnerContactPerson/Handler.php | 68 +++++ .../UseCase/UpdateData/Handler.php | 2 - .../InstallContactPerson/HandlerTest.php | 12 +- .../HandlerTest.php | 12 +- .../UseCase/Uninstall/HandlerTest.php | 277 ------------------ .../UnlinkContactPerson/HandlerTest.php | 176 +++++++++++ .../HandlerTest.php | 177 +++++++++++ .../UseCase/UpdateData/HandlerTest.php | 8 +- 21 files changed, 583 insertions(+), 482 deletions(-) delete mode 100644 src/ContactPersons/UseCase/Uninstall/Command.php delete mode 100644 src/ContactPersons/UseCase/Uninstall/Handler.php create mode 100644 src/ContactPersons/UseCase/UnlinkContactPerson/Command.php create mode 100644 src/ContactPersons/UseCase/UnlinkContactPerson/Handler.php create mode 100644 src/ContactPersons/UseCase/UnlinkPartnerContactPerson/Command.php create mode 100644 src/ContactPersons/UseCase/UnlinkPartnerContactPerson/Handler.php delete mode 100644 tests/Functional/ContactPersons/UseCase/Uninstall/HandlerTest.php create mode 100644 tests/Functional/ContactPersons/UseCase/UnlinkContactPerson/HandlerTest.php create mode 100644 tests/Functional/ContactPersons/UseCase/UnlinkPartnerContactPerson/HandlerTest.php diff --git a/src/ContactPersons/UseCase/InstallContactPerson/Command.php b/src/ContactPersons/UseCase/InstallContactPerson/Command.php index 1b88982..e4f0233 100644 --- a/src/ContactPersons/UseCase/InstallContactPerson/Command.php +++ b/src/ContactPersons/UseCase/InstallContactPerson/Command.php @@ -4,26 +4,23 @@ namespace Bitrix24\Lib\ContactPersons\UseCase\InstallContactPerson; -use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\FullName; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\UserAgentInfo; -use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; -use Darsyn\IP\Version\Multi as IP; use libphonenumber\PhoneNumber; use Symfony\Component\Uid\Uuid; readonly class Command { public function __construct( - public Uuid $applicationInstallationId, - public FullName $fullName, - public int $bitrix24UserId, - public UserAgentInfo $userAgentInfo, - public ?string $email, - public ?PhoneNumber $mobilePhoneNumber, - public ?string $comment, - public ?string $externalId, - public ?Uuid $bitrix24PartnerId, + public Uuid $applicationInstallationId, + public FullName $fullName, + public int $bitrix24UserId, + public UserAgentInfo $userAgentInfo, + public ?string $email, + public ?PhoneNumber $mobilePhoneNumber, + public ?string $comment, + public ?string $externalId, + public ?Uuid $bitrix24PartnerId, ) { $this->validate(); } diff --git a/src/ContactPersons/UseCase/InstallContactPerson/Handler.php b/src/ContactPersons/UseCase/InstallContactPerson/Handler.php index 57fd3c2..15a724c 100644 --- a/src/ContactPersons/UseCase/InstallContactPerson/Handler.php +++ b/src/ContactPersons/UseCase/InstallContactPerson/Handler.php @@ -5,12 +5,10 @@ namespace Bitrix24\Lib\ContactPersons\UseCase\InstallContactPerson; use Bitrix24\Lib\ContactPersons\Entity\ContactPerson; -use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; use Bitrix24\Lib\Services\Flusher; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationInterface; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Repository\ApplicationInstallationRepositoryInterface; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\UserAgentInfo; use Bitrix24\SDK\Application\Contracts\ContactPersons\Repository\ContactPersonRepositoryInterface; use Bitrix24\SDK\Application\Contracts\Events\AggregateRootEventsEmitterInterface; use Psr\Log\LoggerInterface; @@ -32,8 +30,8 @@ public function handle(Command $command): void 'bitrix24UserId' => $command->bitrix24UserId, ]); - /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $activeInstallation */ - $activeInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); + /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $applicationInstallation */ + $applicationInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); $uuidV7 = Uuid::v7(); @@ -55,10 +53,10 @@ public function handle(Command $command): void $this->contactPersonRepository->save($contactPerson); - $activeInstallation->linkContactPerson($uuidV7); - $this->applicationInstallationRepository->save($activeInstallation); + $applicationInstallation->linkContactPerson($uuidV7); + $this->applicationInstallationRepository->save($applicationInstallation); - $this->flusher->flush($contactPerson,$activeInstallation); + $this->flusher->flush($contactPerson, $applicationInstallation); $this->logger->info('ContactPerson.InstallContactPerson.finish', [ 'contact_person_id' => $uuidV7->toRfc4122(), diff --git a/src/ContactPersons/UseCase/InstallPartnerContactPerson/Command.php b/src/ContactPersons/UseCase/InstallPartnerContactPerson/Command.php index c229bf3..96d6fb7 100644 --- a/src/ContactPersons/UseCase/InstallPartnerContactPerson/Command.php +++ b/src/ContactPersons/UseCase/InstallPartnerContactPerson/Command.php @@ -4,26 +4,23 @@ namespace Bitrix24\Lib\ContactPersons\UseCase\InstallPartnerContactPerson; -use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\FullName; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\UserAgentInfo; -use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; -use Darsyn\IP\Version\Multi as IP; use libphonenumber\PhoneNumber; use Symfony\Component\Uid\Uuid; readonly class Command { public function __construct( - public Uuid $applicationInstallationId, - public FullName $fullName, - public int $bitrix24UserId, - public UserAgentInfo $userAgentInfo, - public ?string $email, - public ?PhoneNumber $mobilePhoneNumber, - public ?string $comment, - public ?string $externalId, - public ?Uuid $bitrix24PartnerId, + public Uuid $applicationInstallationId, + public FullName $fullName, + public int $bitrix24UserId, + public UserAgentInfo $userAgentInfo, + public ?string $email, + public ?PhoneNumber $mobilePhoneNumber, + public ?string $comment, + public ?string $externalId, + public ?Uuid $bitrix24PartnerId, ) { $this->validate(); } diff --git a/src/ContactPersons/UseCase/InstallPartnerContactPerson/Handler.php b/src/ContactPersons/UseCase/InstallPartnerContactPerson/Handler.php index a01461c..3a18c50 100644 --- a/src/ContactPersons/UseCase/InstallPartnerContactPerson/Handler.php +++ b/src/ContactPersons/UseCase/InstallPartnerContactPerson/Handler.php @@ -5,12 +5,10 @@ namespace Bitrix24\Lib\ContactPersons\UseCase\InstallPartnerContactPerson; use Bitrix24\Lib\ContactPersons\Entity\ContactPerson; -use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; use Bitrix24\Lib\Services\Flusher; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationInterface; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Repository\ApplicationInstallationRepositoryInterface; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\UserAgentInfo; use Bitrix24\SDK\Application\Contracts\ContactPersons\Repository\ContactPersonRepositoryInterface; use Bitrix24\SDK\Application\Contracts\Events\AggregateRootEventsEmitterInterface; use Psr\Log\LoggerInterface; @@ -32,8 +30,8 @@ public function handle(Command $command): void 'bitrix24UserId' => $command->bitrix24UserId, ]); - /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $activeInstallation */ - $activeInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); + /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $applicationInstallation */ + $applicationInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); $uuidV7 = Uuid::v7(); @@ -55,10 +53,10 @@ public function handle(Command $command): void $this->contactPersonRepository->save($contactPerson); - $activeInstallation->linkBitrix24PartnerContactPerson($uuidV7); - $this->applicationInstallationRepository->save($activeInstallation); + $applicationInstallation->linkBitrix24PartnerContactPerson($uuidV7); + $this->applicationInstallationRepository->save($applicationInstallation); - $this->flusher->flush($contactPerson,$activeInstallation); + $this->flusher->flush($contactPerson, $applicationInstallation); $this->logger->info('ContactPerson.InstallPartnerContactPerson.finish', [ 'contact_person_id' => $uuidV7->toRfc4122(), diff --git a/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php b/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php index b46cc4f..2fe6861 100644 --- a/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php +++ b/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php @@ -10,5 +10,5 @@ { public function __construct( public Uuid $contactPersonId, - ){} -} \ No newline at end of file + ) {} +} diff --git a/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php index 7d691ad..5ef1195 100644 --- a/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php +++ b/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php @@ -35,4 +35,4 @@ public function handle(Command $command): void 'contactPersonId' => $contactPerson->getId()->toRfc4122(), ]); } -} \ No newline at end of file +} diff --git a/src/ContactPersons/UseCase/MarkPhoneAsVerified/Command.php b/src/ContactPersons/UseCase/MarkPhoneAsVerified/Command.php index 87b2819..3df1c9f 100644 --- a/src/ContactPersons/UseCase/MarkPhoneAsVerified/Command.php +++ b/src/ContactPersons/UseCase/MarkPhoneAsVerified/Command.php @@ -10,5 +10,5 @@ { public function __construct( public Uuid $contactPersonId, - ){} -} \ No newline at end of file + ) {} +} diff --git a/src/ContactPersons/UseCase/MarkPhoneAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkPhoneAsVerified/Handler.php index a8a9630..e555f7e 100644 --- a/src/ContactPersons/UseCase/MarkPhoneAsVerified/Handler.php +++ b/src/ContactPersons/UseCase/MarkPhoneAsVerified/Handler.php @@ -35,4 +35,4 @@ public function handle(Command $command): void 'contactPersonId' => $contactPerson->getId()->toRfc4122(), ]); } -} \ No newline at end of file +} diff --git a/src/ContactPersons/UseCase/Uninstall/Command.php b/src/ContactPersons/UseCase/Uninstall/Command.php deleted file mode 100644 index de0b9da..0000000 --- a/src/ContactPersons/UseCase/Uninstall/Command.php +++ /dev/null @@ -1,39 +0,0 @@ -validate(); - } - - private function validate(): void - { - if ($this->memberId === null && $this->contactPersonId === null) { - throw new InvalidArgumentException('Either memberId or contactPersonId must be provided.'); - } - - if ($this->memberId !== null && '' === $this->memberId) { - throw new InvalidArgumentException('Member ID must be a non-empty string if provided.'); - } - - if ($this->memberId !== null && $this->contactPersonType === null) { - throw new InvalidArgumentException('ContactPersonType must be provided if memberId is provided.'); - } - } -} diff --git a/src/ContactPersons/UseCase/Uninstall/Handler.php b/src/ContactPersons/UseCase/Uninstall/Handler.php deleted file mode 100644 index ad359b4..0000000 --- a/src/ContactPersons/UseCase/Uninstall/Handler.php +++ /dev/null @@ -1,104 +0,0 @@ -logger->info('ContactPerson.Uninstall.start', [ - 'memberId' => $command->memberId, - 'contactPersonType' => $command->contactPersonType?->value, - 'contactPersonId' => $command->contactPersonId?->toRfc4122(), - ]); - - $entitiesToFlush = []; // Объявляем переменную - - // Если передан memberId, пытаемся найти установку и отвязать контактное лицо нужного типа - if ($command->memberId !== null && $command->contactPersonType !== null) { - /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $activeInstallation */ - $activeInstallation = $this->applicationInstallationRepository->findByBitrix24AccountMemberId($command->memberId); - - if ($activeInstallation !== null) { - $contactPersonId = null; - - if ($command->contactPersonType === ContactPersonType::personal) { - $contactPersonId = $activeInstallation->getContactPersonId(); - $activeInstallation->unlinkContactPerson(); - } - - if ($command->contactPersonType === ContactPersonType::partner) { - $contactPersonId = $activeInstallation->getBitrix24PartnerContactPersonId(); - $activeInstallation->unlinkBitrix24PartnerContactPerson(); - } - - $entitiesToFlush[] = $activeInstallation; - $this->applicationInstallationRepository->save($activeInstallation); - - - // Если у установки был контакт, помечаем его как удалённый - if ($contactPersonId !== null) { - /** @var null|AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ - $contactPerson = $this->contactPersonRepository->getById($contactPersonId); - if ($contactPerson !== null) { - $this->logger->info('ContactPerson.Uninstall.deletingContactPersonFromInstallation', [ - 'contactPersonId' => $contactPersonId->toRfc4122(), - ]); - $contactPerson->markAsDeleted($command->comment); - $this->contactPersonRepository->save($contactPerson); - $entitiesToFlush[] = $contactPerson; - } - } - $this->flusher->flush(...array_filter($entitiesToFlush, fn ($entity): bool => $entity instanceof AggregateRootEventsEmitterInterface)); - } - } - - // Если передан ID контактного лица, удаляем его - if ($command->contactPersonId !== null) { - $alreadyDeleted = false; - foreach ($entitiesToFlush as $entity) { - if ($entity instanceof ContactPersonInterface && $entity->getId()->equals($command->contactPersonId)) { - $alreadyDeleted = true; - break; - } - } - - if (!$alreadyDeleted) { - $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); - if ($contactPerson !== null) { - $contactPerson->markAsDeleted($command->comment); - $this->contactPersonRepository->save($contactPerson); - $this->flusher->flush($contactPerson); - } - } - } - - $this->logger->info('ContactPerson.Uninstall.finish', [ - 'memberId' => $command->memberId, - 'contactPersonType' => $command->contactPersonType?->value, - 'contactPersonId' => $command->contactPersonId?->toRfc4122(), - ]); - } -} diff --git a/src/ContactPersons/UseCase/UnlinkContactPerson/Command.php b/src/ContactPersons/UseCase/UnlinkContactPerson/Command.php new file mode 100644 index 0000000..7d116a0 --- /dev/null +++ b/src/ContactPersons/UseCase/UnlinkContactPerson/Command.php @@ -0,0 +1,22 @@ +validate(); + } + + private function validate(): void + { + // no-op for now, but keep a place for future checks + } +} diff --git a/src/ContactPersons/UseCase/UnlinkContactPerson/Handler.php b/src/ContactPersons/UseCase/UnlinkContactPerson/Handler.php new file mode 100644 index 0000000..2e1db96 --- /dev/null +++ b/src/ContactPersons/UseCase/UnlinkContactPerson/Handler.php @@ -0,0 +1,68 @@ +logger->info('ContactPerson.UninstallContactPerson.start', [ + 'applicationInstallationId' => $command->applicationInstallationId, + ]); + + /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $applicationInstallation */ + $applicationInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); + + $contactPersonId = $applicationInstallation->getContactPersonId(); + + // unlink from installation first + $applicationInstallation->unlinkContactPerson(); + $this->applicationInstallationRepository->save($applicationInstallation); + + // если контакта не было привязано — просто логируем и флашим установку + if (null === $contactPersonId) { + $this->logger->info('ContactPerson.UninstallContactPerson.noLinkedContact', [ + 'applicationInstallationId' => $command->applicationInstallationId, + ]); + $this->flusher->flush($applicationInstallation); + } else { + /** @var null|AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ + $contactPerson = $this->contactPersonRepository->getById($contactPersonId); + + // если ID есть, но сущность не нашли в репозитории — логируем warning и флашим только установку + if (null === $contactPerson) { + $this->logger->warning('ContactPerson.UninstallContactPerson.linkedContactNotFoundInRepo', [ + 'contact_person_id' => $contactPersonId->toRfc4122(), + 'applicationInstallationId' => $command->applicationInstallationId, + ]); + $this->flusher->flush($applicationInstallation); + } else { + // нормальный сценарий: помечаем контакт удалённым, сохраняем и флашим обе сущности + $contactPerson->markAsDeleted($command->comment); + $this->contactPersonRepository->save($contactPerson); + $this->flusher->flush($applicationInstallation, $contactPerson); + } + } + + $this->logger->info('ContactPerson.UninstallContactPerson.finish', [ + 'contact_person_id' => $contactPersonId?->toRfc4122(), + ]); + } +} diff --git a/src/ContactPersons/UseCase/UnlinkPartnerContactPerson/Command.php b/src/ContactPersons/UseCase/UnlinkPartnerContactPerson/Command.php new file mode 100644 index 0000000..205c8a9 --- /dev/null +++ b/src/ContactPersons/UseCase/UnlinkPartnerContactPerson/Command.php @@ -0,0 +1,22 @@ +validate(); + } + + private function validate(): void + { + // no-op for now, but keep a place for future checks + } +} diff --git a/src/ContactPersons/UseCase/UnlinkPartnerContactPerson/Handler.php b/src/ContactPersons/UseCase/UnlinkPartnerContactPerson/Handler.php new file mode 100644 index 0000000..2640cbb --- /dev/null +++ b/src/ContactPersons/UseCase/UnlinkPartnerContactPerson/Handler.php @@ -0,0 +1,68 @@ +logger->info('ContactPerson.UninstallPartnerContactPerson.start', [ + 'applicationInstallationId' => $command->applicationInstallationId, + ]); + + /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $applicationInstallation */ + $applicationInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); + + $contactPersonId = $applicationInstallation->getBitrix24PartnerContactPersonId(); + + // unlink from installation first + $applicationInstallation->unlinkBitrix24PartnerContactPerson(); + $this->applicationInstallationRepository->save($applicationInstallation); + + // если партнёрский контакт не был привязан — просто логируем и флашим установку + if (null === $contactPersonId) { + $this->logger->info('ContactPerson.UninstallPartnerContactPerson.noLinkedContact', [ + 'applicationInstallationId' => $command->applicationInstallationId, + ]); + $this->flusher->flush($applicationInstallation); + } else { + /** @var null|AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ + $contactPerson = $this->contactPersonRepository->getById($contactPersonId); + + // если ID есть, но сущность не нашли в репозитории — логируем warning и флашим только установку + if (null === $contactPerson) { + $this->logger->warning('ContactPerson.UninstallPartnerContactPerson.linkedContactNotFoundInRepo', [ + 'contact_person_id' => $contactPersonId->toRfc4122(), + 'applicationInstallationId' => $command->applicationInstallationId, + ]); + $this->flusher->flush($applicationInstallation); + } else { + // нормальный сценарий: помечаем контакт удалённым, сохраняем и флашим обе сущности + $contactPerson->markAsDeleted($command->comment); + $this->contactPersonRepository->save($contactPerson); + $this->flusher->flush($applicationInstallation, $contactPerson); + } + } + + $this->logger->info('ContactPerson.UninstallPartnerContactPerson.finish', [ + 'contact_person_id' => $contactPersonId?->toRfc4122(), + ]); + } +} diff --git a/src/ContactPersons/UseCase/UpdateData/Handler.php b/src/ContactPersons/UseCase/UpdateData/Handler.php index 908a1e0..27e7057 100644 --- a/src/ContactPersons/UseCase/UpdateData/Handler.php +++ b/src/ContactPersons/UseCase/UpdateData/Handler.php @@ -5,7 +5,6 @@ namespace Bitrix24\Lib\ContactPersons\UseCase\UpdateData; use Bitrix24\Lib\Services\Flusher; -use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationInterface; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\FullName; use Bitrix24\SDK\Application\Contracts\ContactPersons\Repository\ContactPersonRepositoryInterface; @@ -34,7 +33,6 @@ public function handle(Command $command): void 'bitrix24PartnerId' => $command->bitrix24PartnerId?->toRfc4122() ?? null, ]); - /** @var AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); if (!$contactPerson) { diff --git a/tests/Functional/ContactPersons/UseCase/InstallContactPerson/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/InstallContactPerson/HandlerTest.php index 89cd17c..b33ac3a 100644 --- a/tests/Functional/ContactPersons/UseCase/InstallContactPerson/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/InstallContactPerson/HandlerTest.php @@ -129,7 +129,7 @@ public function testInstallContactPersonSuccess(): void ->withComment('Test comment') ->withExternalId($externalId) ->withBitrix24UserId($bitrix24Account->getBitrix24UserId()) - ->withBitrix24PartnerId(null === $applicationInstallation->getBitrix24PartnerId() ? Uuid::v7() : $applicationInstallation->getBitrix24PartnerId()) + ->withBitrix24PartnerId($applicationInstallation->getBitrix24PartnerId() ?? Uuid::v7()) ->build(); // Запуск use-case @@ -156,10 +156,10 @@ public function testInstallContactPersonSuccess(): void $foundInstallation = $this->applicationInstallationRepository->getById($applicationInstallation->getId()); $this->assertNotNull($foundInstallation->getContactPersonId()); - $contactPersonId = $foundInstallation->getContactPersonId(); - $foundContactPerson = $this->repository->getById($contactPersonId); + $uuid = $foundInstallation->getContactPersonId(); + $foundContactPerson = $this->repository->getById($uuid); $this->assertInstanceOf(ContactPersonInterface::class, $foundContactPerson); - $this->assertEquals($foundContactPerson->getId(), $contactPersonId); + $this->assertEquals($foundContactPerson->getId(), $uuid); } #[Test] @@ -174,13 +174,13 @@ public function testInstallContactPersonWithWrongApplicationInstallationId(): vo ->withExternalId(Uuid::v7()->toRfc4122()) ->build(); - $wrongInstallationId = Uuid::v7(); + $uuidV7 = Uuid::v7(); $this->expectException(ApplicationInstallationNotFoundException::class); $this->handler->handle( new Command( - $wrongInstallationId, + $uuidV7, $contactPerson->getFullName(), random_int(1, 1_000_000), $contactPerson->getUserAgentInfo(), diff --git a/tests/Functional/ContactPersons/UseCase/InstallPartnerContactPerson/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/InstallPartnerContactPerson/HandlerTest.php index c19ca9f..d3192fd 100644 --- a/tests/Functional/ContactPersons/UseCase/InstallPartnerContactPerson/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/InstallPartnerContactPerson/HandlerTest.php @@ -129,7 +129,7 @@ public function testInstallPartnerContactPersonSuccess(): void ->withComment('Test comment') ->withExternalId($externalId) ->withBitrix24UserId($bitrix24Account->getBitrix24UserId()) - ->withBitrix24PartnerId(null === $applicationInstallation->getBitrix24PartnerId() ? Uuid::v7() : $applicationInstallation->getBitrix24PartnerId()) + ->withBitrix24PartnerId($applicationInstallation->getBitrix24PartnerId() ?? Uuid::v7()) ->build(); // Запуск use-case @@ -156,10 +156,10 @@ public function testInstallPartnerContactPersonSuccess(): void $foundInstallation = $this->applicationInstallationRepository->getById($applicationInstallation->getId()); $this->assertNotNull($foundInstallation->getBitrix24PartnerContactPersonId()); - $bitrix24PartnerContactPersonId = $foundInstallation->getBitrix24PartnerContactPersonId(); - $foundContactPerson = $this->repository->getById($bitrix24PartnerContactPersonId); + $uuid = $foundInstallation->getBitrix24PartnerContactPersonId(); + $foundContactPerson = $this->repository->getById($uuid); $this->assertInstanceOf(ContactPersonInterface::class, $foundContactPerson); - $this->assertEquals($foundContactPerson->getId(), $bitrix24PartnerContactPersonId); + $this->assertEquals($foundContactPerson->getId(), $uuid); } #[Test] @@ -174,13 +174,13 @@ public function testInstallPartnerContactPersonWithWrongApplicationInstallationI ->withExternalId(Uuid::v7()->toRfc4122()) ->build(); - $wrongInstallationId = Uuid::v7(); + $uuidV7 = Uuid::v7(); $this->expectException(ApplicationInstallationNotFoundException::class); $this->handler->handle( new Command( - $wrongInstallationId, + $uuidV7, $contactPerson->getFullName(), random_int(1, 1_000_000), $contactPerson->getUserAgentInfo(), diff --git a/tests/Functional/ContactPersons/UseCase/Uninstall/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/Uninstall/HandlerTest.php deleted file mode 100644 index 790935f..0000000 --- a/tests/Functional/ContactPersons/UseCase/Uninstall/HandlerTest.php +++ /dev/null @@ -1,277 +0,0 @@ - - * - * For the full copyright and license information, please view the MIT-LICENSE.txt - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\Uninstall; - -use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository; -use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository; -use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; -use Bitrix24\Lib\ContactPersons\UseCase\Uninstall\Handler; -use Bitrix24\Lib\ContactPersons\UseCase\Uninstall\Command; -use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; -use Bitrix24\Lib\Services\Flusher; -use Bitrix24\Lib\Tests\Functional\ApplicationInstallations\Builders\ApplicationInstallationBuilder; -use Bitrix24\Lib\Tests\Functional\Bitrix24Accounts\Builders\Bitrix24AccountBuilder; -use Bitrix24\SDK\Application\ApplicationStatus; -use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationStatus; -use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; -use Bitrix24\Lib\Tests\EntityManagerFactory; -use Bitrix24\SDK\Application\PortalLicenseFamily; -use Bitrix24\SDK\Core\Credentials\Scope; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\Test; -use PHPUnit\Framework\TestCase; -use Psr\Log\NullLogger; -use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; -use Symfony\Component\EventDispatcher\EventDispatcher; -use Symfony\Component\Stopwatch\Stopwatch; -use Symfony\Component\Uid\Uuid; -use libphonenumber\PhoneNumberUtil; -use libphonenumber\PhoneNumber; -use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; - - -/** - * @internal - */ -#[CoversClass(Handler::class)] -class HandlerTest extends TestCase -{ - private Handler $handler; - - private Flusher $flusher; - - private ContactPersonRepository $repository; - private ApplicationInstallationRepository $applicationInstallationRepository; - private Bitrix24AccountRepository $bitrix24accountRepository; - - private TraceableEventDispatcher $eventDispatcher; - - #[\Override] - protected function setUp(): void - { - $entityManager = EntityManagerFactory::get(); - $this->eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); - $this->repository = new ContactPersonRepository($entityManager); - $this->applicationInstallationRepository = new ApplicationInstallationRepository($entityManager); - $this->bitrix24accountRepository = new Bitrix24AccountRepository($entityManager); - $this->flusher = new Flusher($entityManager, $this->eventDispatcher); - $this->handler = new Handler( - $this->applicationInstallationRepository, - $this->repository, - $this->flusher, - new NullLogger() - ); - } - #[Test] - public function testUninstallContactPersonByMemberIdPersonal(): void - { - - $contactPersonBuilder = new ContactPersonBuilder(); - - $contactPerson = $contactPersonBuilder - ->withEmail('john.doe@example.com') - ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) - ->withComment('Test comment') - ->build(); - - $this->repository->save($contactPerson); - - // Load account and application installation into database for uninstallation. - $applicationToken = Uuid::v7()->toRfc4122(); - $memberId = Uuid::v4()->toRfc4122(); - $externalId = Uuid::v7()->toRfc4122(); - $contactPersonId = $contactPerson->getId(); - - $bitrix24Account = (new Bitrix24AccountBuilder()) - ->withApplicationScope(new Scope(['crm'])) - ->withStatus(Bitrix24AccountStatus::new) - ->withApplicationToken($applicationToken) - ->withMemberId($memberId) - ->withMaster(true) - ->withSetToken() - ->withInstalled() - ->build(); - - $this->bitrix24accountRepository->save($bitrix24Account); - - $applicationInstallation = (new ApplicationInstallationBuilder()) - ->withApplicationStatus(new ApplicationStatus('F')) - ->withPortalLicenseFamily(PortalLicenseFamily::free) - ->withBitrix24AccountId($bitrix24Account->getId()) - ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) - ->withApplicationToken($applicationToken) - ->withContactPersonId($contactPersonId) - ->withBitrix24PartnerContactPersonId(null) - ->withExternalId($externalId) - ->build(); - - $this->applicationInstallationRepository->save($applicationInstallation); - - $this->flusher->flush(); - - $this->handler->handle( - new Command($memberId, ContactPersonType::personal) - ); - - $updatedInstallation = $this->applicationInstallationRepository->findByBitrix24AccountMemberId($memberId); - $this->assertNull($updatedInstallation->getContactPersonId()); - - $this->expectException(ContactPersonNotFoundException::class); - $this->repository->getById($contactPersonId); - } - - #[Test] - public function testUninstallContactPersonByMemberIdPartner(): void - { - $contactPersonBuilder = new ContactPersonBuilder(); - - $contactPerson = $contactPersonBuilder - ->withEmail('john.doe@example.com') - ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) - ->withComment('Test comment') - ->build(); - - $this->repository->save($contactPerson); - - // Load account and application installation into database for uninstallation. - $applicationToken = Uuid::v7()->toRfc4122(); - $memberId = Uuid::v4()->toRfc4122(); - $externalId = Uuid::v7()->toRfc4122(); - $contactPersonId = $contactPerson->getId(); - - $bitrix24Account = (new Bitrix24AccountBuilder()) - ->withApplicationScope(new Scope(['crm'])) - ->withStatus(Bitrix24AccountStatus::new) - ->withApplicationToken($applicationToken) - ->withMemberId($memberId) - ->withMaster(true) - ->withSetToken() - ->withInstalled() - ->build(); - - $this->bitrix24accountRepository->save($bitrix24Account); - - $applicationInstallation = (new ApplicationInstallationBuilder()) - ->withApplicationStatus(new ApplicationStatus('F')) - ->withPortalLicenseFamily(PortalLicenseFamily::free) - ->withBitrix24AccountId($bitrix24Account->getId()) - ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) - ->withApplicationToken($applicationToken) - ->withContactPersonId(null) - ->withBitrix24PartnerContactPersonId($contactPersonId) - ->withExternalId($externalId) - ->build(); - - $this->applicationInstallationRepository->save($applicationInstallation); - - $this->flusher->flush(); - - $this->handler->handle( - new Command($memberId, ContactPersonType::partner) - ); - - $updatedInstallation = $this->applicationInstallationRepository->findByBitrix24AccountMemberId($memberId); - $this->assertNull($updatedInstallation->getBitrix24PartnerContactPersonId()); - - $this->expectException(ContactPersonNotFoundException::class); - $this->repository->getById($contactPersonId); - } - - #[Test] - public function testUninstallContactPersonById(): void - { - $contactPersonBuilder = new ContactPersonBuilder(); - - $contactPerson = $contactPersonBuilder - ->withEmail('john.doe@example.com') - ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) - ->withComment('Test comment') - ->build(); - - $this->repository->save($contactPerson); - $this->flusher->flush(); - - $this->handler->handle( - new Command(null, null, $contactPerson->getId()) - ); - - $this->expectException(ContactPersonNotFoundException::class); - $this->repository->getById($contactPerson->getId()); - } - - #[Test] - public function testUninstallContactPersonByMemberIdAndId(): void - { - $contactPersonBuilder = new ContactPersonBuilder(); - - $contactPerson = $contactPersonBuilder - ->withEmail('john.doe@example.com') - ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) - ->withComment('Test comment') - ->build(); - - $this->repository->save($contactPerson); - - // Load account and application installation into database for uninstallation. - $applicationToken = Uuid::v7()->toRfc4122(); - $memberId = Uuid::v4()->toRfc4122(); - $externalId = Uuid::v7()->toRfc4122(); - $contactPersonId = $contactPerson->getId(); - - $bitrix24Account = (new Bitrix24AccountBuilder()) - ->withApplicationScope(new Scope(['crm'])) - ->withStatus(Bitrix24AccountStatus::new) - ->withApplicationToken($applicationToken) - ->withMemberId($memberId) - ->withMaster(true) - ->withSetToken() - ->withInstalled() - ->build(); - - $this->bitrix24accountRepository->save($bitrix24Account); - - $applicationInstallation = (new ApplicationInstallationBuilder()) - ->withApplicationStatus(new ApplicationStatus('F')) - ->withPortalLicenseFamily(PortalLicenseFamily::free) - ->withBitrix24AccountId($bitrix24Account->getId()) - ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) - ->withApplicationToken($applicationToken) - ->withContactPersonId($contactPersonId) - ->withBitrix24PartnerContactPersonId(null) - ->withExternalId($externalId) - ->build(); - - $this->applicationInstallationRepository->save($applicationInstallation); - - $this->flusher->flush(); - - $this->handler->handle( - new Command($memberId, ContactPersonType::personal, $contactPersonId) - ); - - $updatedInstallation = $this->applicationInstallationRepository->findByBitrix24AccountMemberId($memberId); - $this->assertNull($updatedInstallation->getContactPersonId()); - - $this->expectException(ContactPersonNotFoundException::class); - $this->repository->getById($contactPersonId); - } - - private function createPhoneNumber(string $number): PhoneNumber - { - $phoneNumberUtil = PhoneNumberUtil::getInstance(); - return $phoneNumberUtil->parse($number, 'RU'); - } -} \ No newline at end of file diff --git a/tests/Functional/ContactPersons/UseCase/UnlinkContactPerson/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/UnlinkContactPerson/HandlerTest.php new file mode 100644 index 0000000..7ce0c41 --- /dev/null +++ b/tests/Functional/ContactPersons/UseCase/UnlinkContactPerson/HandlerTest.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\UnlinkContactPerson; + +use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository; +use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository; +use Bitrix24\Lib\ContactPersons\UseCase\UnlinkContactPerson\Handler; +use Bitrix24\Lib\ContactPersons\UseCase\UnlinkContactPerson\Command; +use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; +use Bitrix24\Lib\Services\Flusher; +use Bitrix24\Lib\Tests\Functional\ApplicationInstallations\Builders\ApplicationInstallationBuilder; +use Bitrix24\Lib\Tests\Functional\Bitrix24Accounts\Builders\Bitrix24AccountBuilder; +use Bitrix24\SDK\Application\ApplicationStatus; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationStatus; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationContactPersonUnlinkedEvent; +use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonDeletedEvent; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; +use Bitrix24\SDK\Application\PortalLicenseFamily; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\Lib\Tests\EntityManagerFactory; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Exceptions\ApplicationInstallationNotFoundException; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Uid\Uuid; +use libphonenumber\PhoneNumberUtil; +use libphonenumber\PhoneNumber; +use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; + +/** + * @internal + */ +#[CoversClass(Handler::class)] +class HandlerTest extends TestCase +{ + private Handler $handler; + + private Flusher $flusher; + + private ContactPersonRepository $repository; + + private ApplicationInstallationRepository $applicationInstallationRepository; + + private Bitrix24AccountRepository $bitrix24accountRepository; + + private TraceableEventDispatcher $eventDispatcher; + + #[\Override] + protected function setUp(): void + { + $entityManager = EntityManagerFactory::get(); + $this->eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $this->repository = new ContactPersonRepository($entityManager); + $this->applicationInstallationRepository = new ApplicationInstallationRepository($entityManager); + $this->bitrix24accountRepository = new Bitrix24AccountRepository($entityManager); + $this->flusher = new Flusher($entityManager, $this->eventDispatcher); + $this->handler = new Handler( + $this->applicationInstallationRepository, + $this->repository, + $this->flusher, + new NullLogger() + ); + } + + /** + * @throws InvalidArgumentException|\Random\RandomException + */ + #[Test] + public function testUninstallContactPersonSuccess(): void + { + // Подготовка Bitrix24 аккаунта и установки приложения + $applicationToken = Uuid::v7()->toRfc4122(); + $memberId = Uuid::v4()->toRfc4122(); + $externalId = Uuid::v7()->toRfc4122(); + + $bitrix24Account = (new Bitrix24AccountBuilder()) + ->withApplicationScope(new Scope(['crm'])) + ->withStatus(Bitrix24AccountStatus::new) + ->withApplicationToken($applicationToken) + ->withMemberId($memberId) + ->withMaster(true) + ->withSetToken() + ->withInstalled() + ->build(); + + $this->bitrix24accountRepository->save($bitrix24Account); + + $applicationInstallation = (new ApplicationInstallationBuilder()) + ->withApplicationStatus(new ApplicationStatus('F')) + ->withPortalLicenseFamily(PortalLicenseFamily::free) + ->withBitrix24AccountId($bitrix24Account->getId()) + ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) + ->withApplicationToken($applicationToken) + ->withContactPersonId(null) + ->withBitrix24PartnerContactPersonId(null) + ->withExternalId($externalId) + ->build(); + + $this->applicationInstallationRepository->save($applicationInstallation); + + // Создаём контакт и привязываем к установке + $contactPersonBuilder = new ContactPersonBuilder(); + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId($externalId) + ->withBitrix24UserId($bitrix24Account->getBitrix24UserId()) + ->build(); + + $this->repository->save($contactPerson); + $applicationInstallation->linkContactPerson($contactPerson->getId()); + $this->applicationInstallationRepository->save($applicationInstallation); + $this->flusher->flush(); + + // Запуск use-case + $this->handler->handle( + new Command( + $applicationInstallation->getId(), + 'Deleted by test' + ) + ); + + // Проверки: события отвязки и удаления контакта + $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); + $this->assertContains(ContactPersonDeletedEvent::class, $dispatchedEvents); + $this->assertContains(ApplicationInstallationContactPersonUnlinkedEvent::class, $dispatchedEvents); + + // Перечитаем установку и проверим, что контакт отвязан + $foundInstallation = $this->applicationInstallationRepository->getById($applicationInstallation->getId()); + $this->assertNull($foundInstallation->getContactPersonId()); + + // Контакт всё ещё доступен в репозитории (с пометкой deleted), сам факт наличия достаточен для данного теста + $this->expectException(ContactPersonNotFoundException::class); + $this->repository->getById($contactPerson->getId()); + } + + #[Test] + public function testUninstallContactPersonWithWrongApplicationInstallationId(): void + { + $uuidV7 = Uuid::v7(); + + $this->expectException(ApplicationInstallationNotFoundException::class); + + $this->handler->handle( + new Command( + $uuidV7, + 'Deleted by test' + ) + ); + } + + private function createPhoneNumber(string $number): PhoneNumber + { + $phoneNumberUtil = PhoneNumberUtil::getInstance(); + return $phoneNumberUtil->parse($number, 'RU'); + } +} diff --git a/tests/Functional/ContactPersons/UseCase/UnlinkPartnerContactPerson/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/UnlinkPartnerContactPerson/HandlerTest.php new file mode 100644 index 0000000..cd63b06 --- /dev/null +++ b/tests/Functional/ContactPersons/UseCase/UnlinkPartnerContactPerson/HandlerTest.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\UnlinkPartnerContactPerson; + +use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository; +use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository; +use Bitrix24\Lib\ContactPersons\UseCase\UnlinkPartnerContactPerson\Handler; +use Bitrix24\Lib\ContactPersons\UseCase\UnlinkPartnerContactPerson\Command; +use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; +use Bitrix24\Lib\Services\Flusher; +use Bitrix24\Lib\Tests\Functional\ApplicationInstallations\Builders\ApplicationInstallationBuilder; +use Bitrix24\Lib\Tests\Functional\Bitrix24Accounts\Builders\Bitrix24AccountBuilder; +use Bitrix24\SDK\Application\ApplicationStatus; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationStatus; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationBitrix24PartnerContactPersonUnlinkedEvent; +use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonDeletedEvent; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; +use Bitrix24\SDK\Application\PortalLicenseFamily; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\Lib\Tests\EntityManagerFactory; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Exceptions\ApplicationInstallationNotFoundException; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Uid\Uuid; +use libphonenumber\PhoneNumberUtil; +use libphonenumber\PhoneNumber; +use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; + +/** + * @internal + */ +#[CoversClass(Handler::class)] +class HandlerTest extends TestCase +{ + private Handler $handler; + + private Flusher $flusher; + + private ContactPersonRepository $repository; + + private ApplicationInstallationRepository $applicationInstallationRepository; + + private Bitrix24AccountRepository $bitrix24accountRepository; + + private TraceableEventDispatcher $eventDispatcher; + + #[\Override] + protected function setUp(): void + { + $entityManager = EntityManagerFactory::get(); + $this->eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $this->repository = new ContactPersonRepository($entityManager); + $this->applicationInstallationRepository = new ApplicationInstallationRepository($entityManager); + $this->bitrix24accountRepository = new Bitrix24AccountRepository($entityManager); + $this->flusher = new Flusher($entityManager, $this->eventDispatcher); + $this->handler = new Handler( + $this->applicationInstallationRepository, + $this->repository, + $this->flusher, + new NullLogger() + ); + } + + /** + * @throws InvalidArgumentException|\Random\RandomException + */ + #[Test] + public function testUninstallPartnerContactPersonSuccess(): void + { + // Подготовка Bitrix24 аккаунта и установки приложения + $applicationToken = Uuid::v7()->toRfc4122(); + $memberId = Uuid::v4()->toRfc4122(); + $externalId = Uuid::v7()->toRfc4122(); + + $bitrix24Account = (new Bitrix24AccountBuilder()) + ->withApplicationScope(new Scope(['crm'])) + ->withStatus(Bitrix24AccountStatus::new) + ->withApplicationToken($applicationToken) + ->withMemberId($memberId) + ->withMaster(true) + ->withSetToken() + ->withInstalled() + ->build(); + + $this->bitrix24accountRepository->save($bitrix24Account); + + $applicationInstallation = (new ApplicationInstallationBuilder()) + ->withApplicationStatus(new ApplicationStatus('F')) + ->withPortalLicenseFamily(PortalLicenseFamily::free) + ->withBitrix24AccountId($bitrix24Account->getId()) + ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) + ->withApplicationToken($applicationToken) + ->withContactPersonId(null) + ->withBitrix24PartnerContactPersonId(null) + ->withExternalId($externalId) + ->build(); + + $this->applicationInstallationRepository->save($applicationInstallation); + + // Создаём контакт и привязываем как партнёрский к установке + $contactPersonBuilder = new ContactPersonBuilder(); + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId($externalId) + ->withBitrix24UserId($bitrix24Account->getBitrix24UserId()) + ->withBitrix24PartnerId(Uuid::v7()) + ->build(); + + $this->repository->save($contactPerson); + $applicationInstallation->linkBitrix24PartnerContactPerson($contactPerson->getId()); + $this->applicationInstallationRepository->save($applicationInstallation); + $this->flusher->flush(); + + // Запуск use-case + $this->handler->handle( + new Command( + $applicationInstallation->getId(), + 'Deleted by test' + ) + ); + + // Проверки: события отвязки и удаления контакта + $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); + $this->assertContains(ContactPersonDeletedEvent::class, $dispatchedEvents); + $this->assertContains(ApplicationInstallationBitrix24PartnerContactPersonUnlinkedEvent::class, $dispatchedEvents); + + // Перечитаем установку и проверим, что партнёрский контакт отвязан + $foundInstallation = $this->applicationInstallationRepository->getById($applicationInstallation->getId()); + $this->assertNull($foundInstallation->getBitrix24PartnerContactPersonId()); + + // Контакт доступен в репозитории (с пометкой deleted) + $this->expectException(ContactPersonNotFoundException::class); + $this->repository->getById($contactPerson->getId()); + } + + #[Test] + public function testUninstallPartnerContactPersonWithWrongApplicationInstallationId(): void + { + $uuidV7 = Uuid::v7(); + + $this->expectException(ApplicationInstallationNotFoundException::class); + + $this->handler->handle( + new Command( + $uuidV7, + 'Deleted by test' + ) + ); + } + + private function createPhoneNumber(string $number): PhoneNumber + { + $phoneNumberUtil = PhoneNumberUtil::getInstance(); + return $phoneNumberUtil->parse($number, 'RU'); + } +} diff --git a/tests/Functional/ContactPersons/UseCase/UpdateData/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/UpdateData/HandlerTest.php index a509903..516e515 100644 --- a/tests/Functional/ContactPersons/UseCase/UpdateData/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/UpdateData/HandlerTest.php @@ -94,7 +94,7 @@ public function testUpdateExistingContactPerson(): void $this->flusher->flush(); $externalId = Uuid::v7()->toRfc4122(); - $bitrix24PartnerId = Uuid::v7(); + $uuidV7 = Uuid::v7(); // Обновляем контактное лицо через команду $this->handler->handle( @@ -104,15 +104,15 @@ public function testUpdateExistingContactPerson(): void 'jane.doe@example.com', $this->createPhoneNumber('+79997654321'), $externalId, - $bitrix24PartnerId, + $uuidV7, ) ); // Проверяем, что изменения сохранились $updatedContactPerson = $this->repository->getById($contactPerson->getId()); - $phoneUtil = PhoneNumberUtil::getInstance(); - $formattedPhone = $phoneUtil->format($updatedContactPerson->getMobilePhone(), PhoneNumberFormat::E164); + $phoneNumberUtil = PhoneNumberUtil::getInstance(); + $formattedPhone = $phoneNumberUtil->format($updatedContactPerson->getMobilePhone(), PhoneNumberFormat::E164); $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); $this->assertContains(ContactPersonEmailChangedEvent::class, $dispatchedEvents); From 8e43b5499eca646b6e7ccf186e4edbed38ae7c7b Mon Sep 17 00:00:00 2001 From: kirill Date: Tue, 23 Dec 2025 00:28:14 +0300 Subject: [PATCH 13/31] Remove Install/Unlink PartnerContactPerson use cases and related tests --- .../Entity/ApplicationInstallation.php | 8 + .../ApplicationInstallationRepository.php | 26 +++ src/ContactPersons/Entity/ContactPerson.php | 9 + .../Command.php | 2 +- .../Handler.php | 10 +- .../InstallPartnerContactPerson/Command.php | 42 ---- .../InstallPartnerContactPerson/Handler.php | 65 ------ .../Command.php | 4 +- src/ContactPersons/UseCase/Unlink/Handler.php | 61 ++++++ .../UseCase/UnlinkContactPerson/Handler.php | 68 ------ .../UnlinkPartnerContactPerson/Command.php | 22 -- .../UnlinkPartnerContactPerson/Handler.php | 68 ------ .../ApplicationInstallationBuilder.php | 10 +- .../Builders/ContactPersonBuilder.php | 3 +- .../HandlerTest.php | 87 +++++++- .../HandlerTest.php | 201 ------------------ .../HandlerTest.php | 168 ++++++++++++++- .../UnlinkContactPerson/HandlerTest.php | 176 --------------- 18 files changed, 366 insertions(+), 664 deletions(-) rename src/ContactPersons/UseCase/{InstallContactPerson => Install}/Command.php (94%) rename src/ContactPersons/UseCase/{InstallContactPerson => Install}/Handler.php (86%) delete mode 100644 src/ContactPersons/UseCase/InstallPartnerContactPerson/Command.php delete mode 100644 src/ContactPersons/UseCase/InstallPartnerContactPerson/Handler.php rename src/ContactPersons/UseCase/{UnlinkContactPerson => Unlink}/Command.php (73%) create mode 100644 src/ContactPersons/UseCase/Unlink/Handler.php delete mode 100644 src/ContactPersons/UseCase/UnlinkContactPerson/Handler.php delete mode 100644 src/ContactPersons/UseCase/UnlinkPartnerContactPerson/Command.php delete mode 100644 src/ContactPersons/UseCase/UnlinkPartnerContactPerson/Handler.php rename tests/Functional/ContactPersons/UseCase/{InstallContactPerson => Install}/HandlerTest.php (69%) delete mode 100644 tests/Functional/ContactPersons/UseCase/InstallPartnerContactPerson/HandlerTest.php rename tests/Functional/ContactPersons/UseCase/{UnlinkPartnerContactPerson => Unlink}/HandlerTest.php (50%) delete mode 100644 tests/Functional/ContactPersons/UseCase/UnlinkContactPerson/HandlerTest.php diff --git a/src/ApplicationInstallations/Entity/ApplicationInstallation.php b/src/ApplicationInstallations/Entity/ApplicationInstallation.php index 9956862..e577a69 100644 --- a/src/ApplicationInstallations/Entity/ApplicationInstallation.php +++ b/src/ApplicationInstallations/Entity/ApplicationInstallation.php @@ -347,6 +347,10 @@ public function linkContactPerson(Uuid $uuid): void #[\Override] public function unlinkContactPerson(): void { + if (null === $this->contactPersonId) { + return; + } + $this->updatedAt = new CarbonImmutable(); $this->events[] = new Events\ApplicationInstallationContactPersonUnlinkedEvent( @@ -374,6 +378,10 @@ public function linkBitrix24PartnerContactPerson(Uuid $uuid): void #[\Override] public function unlinkBitrix24PartnerContactPerson(): void { + if (null === $this->bitrix24PartnerContactPersonId) { + return; + } + $this->updatedAt = new CarbonImmutable(); $this->events[] = new Events\ApplicationInstallationBitrix24PartnerContactPersonUnlinkedEvent( diff --git a/src/ApplicationInstallations/Infrastructure/Doctrine/ApplicationInstallationRepository.php b/src/ApplicationInstallations/Infrastructure/Doctrine/ApplicationInstallationRepository.php index d6644c6..28717da 100644 --- a/src/ApplicationInstallations/Infrastructure/Doctrine/ApplicationInstallationRepository.php +++ b/src/ApplicationInstallations/Infrastructure/Doctrine/ApplicationInstallationRepository.php @@ -102,6 +102,32 @@ public function findByExternalId(string $externalId): array ; } + /** + * Get the current installation on the portal without input parameters. + * The system allows only one active installation per portal, + * therefore, the current one is interpreted as the installation with status active. + * If, for any reason, there are multiple, select the most recent by createdAt. + * + * @throws ApplicationInstallationNotFoundException + */ + public function getCurrent(): ApplicationInstallationInterface + { + $applicationInstallation = $this->getEntityManager()->getRepository(ApplicationInstallation::class) + ->createQueryBuilder('appInstallation') + ->where('appInstallation.status = :status') + ->orderBy('appInstallation.createdAt', 'DESC') + ->setParameter('status', ApplicationInstallationStatus::active) + ->getQuery() + ->getOneOrNullResult() + ; + + if (null === $applicationInstallation) { + throw new ApplicationInstallationNotFoundException('current active application installation not found'); + } + + return $applicationInstallation; + } + /** * Find application installation by application token. * diff --git a/src/ContactPersons/Entity/ContactPerson.php b/src/ContactPersons/Entity/ContactPerson.php index 85af3b9..2c9533b 100644 --- a/src/ContactPersons/Entity/ContactPerson.php +++ b/src/ContactPersons/Entity/ContactPerson.php @@ -181,6 +181,15 @@ public function markEmailAsVerified(): void ); } + public function isPartner(): bool + { + if ($this->getBitrix24PartnerId() !== null) { + return true; + } + + return false; + } + #[\Override] public function getEmailVerifiedAt(): ?CarbonImmutable { diff --git a/src/ContactPersons/UseCase/InstallContactPerson/Command.php b/src/ContactPersons/UseCase/Install/Command.php similarity index 94% rename from src/ContactPersons/UseCase/InstallContactPerson/Command.php rename to src/ContactPersons/UseCase/Install/Command.php index e4f0233..21e8db0 100644 --- a/src/ContactPersons/UseCase/InstallContactPerson/Command.php +++ b/src/ContactPersons/UseCase/Install/Command.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Bitrix24\Lib\ContactPersons\UseCase\InstallContactPerson; +namespace Bitrix24\Lib\ContactPersons\UseCase\Install; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\FullName; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\UserAgentInfo; diff --git a/src/ContactPersons/UseCase/InstallContactPerson/Handler.php b/src/ContactPersons/UseCase/Install/Handler.php similarity index 86% rename from src/ContactPersons/UseCase/InstallContactPerson/Handler.php rename to src/ContactPersons/UseCase/Install/Handler.php index 15a724c..e6481b3 100644 --- a/src/ContactPersons/UseCase/InstallContactPerson/Handler.php +++ b/src/ContactPersons/UseCase/Install/Handler.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Bitrix24\Lib\ContactPersons\UseCase\InstallContactPerson; +namespace Bitrix24\Lib\ContactPersons\UseCase\Install; use Bitrix24\Lib\ContactPersons\Entity\ContactPerson; use Bitrix24\Lib\Services\Flusher; @@ -28,6 +28,7 @@ public function handle(Command $command): void $this->logger->info('ContactPerson.InstallContactPerson.start', [ 'applicationInstallationId' => $command->applicationInstallationId, 'bitrix24UserId' => $command->bitrix24UserId, + 'bitrix24PartnerId' => $command->bitrix24PartnerId?->toRfc4122() ?? '' ]); /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $applicationInstallation */ @@ -53,7 +54,12 @@ public function handle(Command $command): void $this->contactPersonRepository->save($contactPerson); - $applicationInstallation->linkContactPerson($uuidV7); + if ($contactPerson->isPartner()) { + $applicationInstallation->linkBitrix24PartnerContactPerson($uuidV7); + }else{ + $applicationInstallation->linkContactPerson($uuidV7); + } + $this->applicationInstallationRepository->save($applicationInstallation); $this->flusher->flush($contactPerson, $applicationInstallation); diff --git a/src/ContactPersons/UseCase/InstallPartnerContactPerson/Command.php b/src/ContactPersons/UseCase/InstallPartnerContactPerson/Command.php deleted file mode 100644 index 96d6fb7..0000000 --- a/src/ContactPersons/UseCase/InstallPartnerContactPerson/Command.php +++ /dev/null @@ -1,42 +0,0 @@ -validate(); - } - - private function validate(): void - { - if (null !== $this->email && !filter_var($this->email, FILTER_VALIDATE_EMAIL)) { - throw new \InvalidArgumentException('Invalid email format.'); - } - - if (null !== $this->externalId && '' === trim($this->externalId)) { - throw new \InvalidArgumentException('External ID cannot be empty if provided.'); - } - - if ($this->bitrix24UserId <= 0) { - throw new \InvalidArgumentException('Bitrix24 User ID must be a positive integer.'); - } - } -} diff --git a/src/ContactPersons/UseCase/InstallPartnerContactPerson/Handler.php b/src/ContactPersons/UseCase/InstallPartnerContactPerson/Handler.php deleted file mode 100644 index 3a18c50..0000000 --- a/src/ContactPersons/UseCase/InstallPartnerContactPerson/Handler.php +++ /dev/null @@ -1,65 +0,0 @@ -logger->info('ContactPerson.InstallPartnerContactPerson.start', [ - 'applicationInstallationId' => $command->applicationInstallationId, - 'bitrix24UserId' => $command->bitrix24UserId, - ]); - - /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $applicationInstallation */ - $applicationInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); - - $uuidV7 = Uuid::v7(); - - $contactPerson = new ContactPerson( - $uuidV7, - ContactPersonStatus::active, - $command->fullName, - $command->email, - null, - $command->mobilePhoneNumber, - null, - $command->comment, - $command->externalId, - $command->bitrix24UserId, - $command->bitrix24PartnerId, - $command->userAgentInfo, - true - ); - - $this->contactPersonRepository->save($contactPerson); - - $applicationInstallation->linkBitrix24PartnerContactPerson($uuidV7); - $this->applicationInstallationRepository->save($applicationInstallation); - - $this->flusher->flush($contactPerson, $applicationInstallation); - - $this->logger->info('ContactPerson.InstallPartnerContactPerson.finish', [ - 'contact_person_id' => $uuidV7->toRfc4122(), - ]); - } -} diff --git a/src/ContactPersons/UseCase/UnlinkContactPerson/Command.php b/src/ContactPersons/UseCase/Unlink/Command.php similarity index 73% rename from src/ContactPersons/UseCase/UnlinkContactPerson/Command.php rename to src/ContactPersons/UseCase/Unlink/Command.php index 7d116a0..c1fa64a 100644 --- a/src/ContactPersons/UseCase/UnlinkContactPerson/Command.php +++ b/src/ContactPersons/UseCase/Unlink/Command.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace Bitrix24\Lib\ContactPersons\UseCase\UnlinkContactPerson; +namespace Bitrix24\Lib\ContactPersons\UseCase\Unlink; use Symfony\Component\Uid\Uuid; readonly class Command { public function __construct( - public Uuid $applicationInstallationId, + public Uuid $contactPersonId, public ?string $comment = null, ) { $this->validate(); diff --git a/src/ContactPersons/UseCase/Unlink/Handler.php b/src/ContactPersons/UseCase/Unlink/Handler.php new file mode 100644 index 0000000..1d05848 --- /dev/null +++ b/src/ContactPersons/UseCase/Unlink/Handler.php @@ -0,0 +1,61 @@ +logger->info('ContactPerson.UninstallContactPerson.start', [ + 'contactPersonId' => $command->contactPersonId, + ]); + + /** @var AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ + $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); + + /** @var AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $applicationInstallation */ + $applicationInstallation = $this->applicationInstallationRepository->getCurrent(); + + $entitiesToFlush = []; + if ($contactPerson->isPartner()) { + if ($applicationInstallation->getBitrix24PartnerContactPersonId() !== null){ + $applicationInstallation->unlinkBitrix24PartnerContactPerson(); + $this->applicationInstallationRepository->save($applicationInstallation); + $entitiesToFlush[] = $applicationInstallation; + } + }else{ + if ($applicationInstallation->getContactPersonId() !== null){ + $applicationInstallation->unlinkContactPerson(); + $this->applicationInstallationRepository->save($applicationInstallation); + $entitiesToFlush[] = $applicationInstallation; + } + } + + $contactPerson->markAsDeleted($command->comment); + $this->contactPersonRepository->save($contactPerson); + $entitiesToFlush[] = $contactPerson; + + $this->flusher->flush(...array_filter($entitiesToFlush, fn ($entity): bool => $entity instanceof AggregateRootEventsEmitterInterface)); + + $this->logger->info('ContactPerson.UninstallContactPerson.finish', [ + 'contact_person_id' => $command->contactPersonId, + ]); + } +} diff --git a/src/ContactPersons/UseCase/UnlinkContactPerson/Handler.php b/src/ContactPersons/UseCase/UnlinkContactPerson/Handler.php deleted file mode 100644 index 2e1db96..0000000 --- a/src/ContactPersons/UseCase/UnlinkContactPerson/Handler.php +++ /dev/null @@ -1,68 +0,0 @@ -logger->info('ContactPerson.UninstallContactPerson.start', [ - 'applicationInstallationId' => $command->applicationInstallationId, - ]); - - /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $applicationInstallation */ - $applicationInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); - - $contactPersonId = $applicationInstallation->getContactPersonId(); - - // unlink from installation first - $applicationInstallation->unlinkContactPerson(); - $this->applicationInstallationRepository->save($applicationInstallation); - - // если контакта не было привязано — просто логируем и флашим установку - if (null === $contactPersonId) { - $this->logger->info('ContactPerson.UninstallContactPerson.noLinkedContact', [ - 'applicationInstallationId' => $command->applicationInstallationId, - ]); - $this->flusher->flush($applicationInstallation); - } else { - /** @var null|AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ - $contactPerson = $this->contactPersonRepository->getById($contactPersonId); - - // если ID есть, но сущность не нашли в репозитории — логируем warning и флашим только установку - if (null === $contactPerson) { - $this->logger->warning('ContactPerson.UninstallContactPerson.linkedContactNotFoundInRepo', [ - 'contact_person_id' => $contactPersonId->toRfc4122(), - 'applicationInstallationId' => $command->applicationInstallationId, - ]); - $this->flusher->flush($applicationInstallation); - } else { - // нормальный сценарий: помечаем контакт удалённым, сохраняем и флашим обе сущности - $contactPerson->markAsDeleted($command->comment); - $this->contactPersonRepository->save($contactPerson); - $this->flusher->flush($applicationInstallation, $contactPerson); - } - } - - $this->logger->info('ContactPerson.UninstallContactPerson.finish', [ - 'contact_person_id' => $contactPersonId?->toRfc4122(), - ]); - } -} diff --git a/src/ContactPersons/UseCase/UnlinkPartnerContactPerson/Command.php b/src/ContactPersons/UseCase/UnlinkPartnerContactPerson/Command.php deleted file mode 100644 index 205c8a9..0000000 --- a/src/ContactPersons/UseCase/UnlinkPartnerContactPerson/Command.php +++ /dev/null @@ -1,22 +0,0 @@ -validate(); - } - - private function validate(): void - { - // no-op for now, but keep a place for future checks - } -} diff --git a/src/ContactPersons/UseCase/UnlinkPartnerContactPerson/Handler.php b/src/ContactPersons/UseCase/UnlinkPartnerContactPerson/Handler.php deleted file mode 100644 index 2640cbb..0000000 --- a/src/ContactPersons/UseCase/UnlinkPartnerContactPerson/Handler.php +++ /dev/null @@ -1,68 +0,0 @@ -logger->info('ContactPerson.UninstallPartnerContactPerson.start', [ - 'applicationInstallationId' => $command->applicationInstallationId, - ]); - - /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $applicationInstallation */ - $applicationInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); - - $contactPersonId = $applicationInstallation->getBitrix24PartnerContactPersonId(); - - // unlink from installation first - $applicationInstallation->unlinkBitrix24PartnerContactPerson(); - $this->applicationInstallationRepository->save($applicationInstallation); - - // если партнёрский контакт не был привязан — просто логируем и флашим установку - if (null === $contactPersonId) { - $this->logger->info('ContactPerson.UninstallPartnerContactPerson.noLinkedContact', [ - 'applicationInstallationId' => $command->applicationInstallationId, - ]); - $this->flusher->flush($applicationInstallation); - } else { - /** @var null|AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ - $contactPerson = $this->contactPersonRepository->getById($contactPersonId); - - // если ID есть, но сущность не нашли в репозитории — логируем warning и флашим только установку - if (null === $contactPerson) { - $this->logger->warning('ContactPerson.UninstallPartnerContactPerson.linkedContactNotFoundInRepo', [ - 'contact_person_id' => $contactPersonId->toRfc4122(), - 'applicationInstallationId' => $command->applicationInstallationId, - ]); - $this->flusher->flush($applicationInstallation); - } else { - // нормальный сценарий: помечаем контакт удалённым, сохраняем и флашим обе сущности - $contactPerson->markAsDeleted($command->comment); - $this->contactPersonRepository->save($contactPerson); - $this->flusher->flush($applicationInstallation, $contactPerson); - } - } - - $this->logger->info('ContactPerson.UninstallPartnerContactPerson.finish', [ - 'contact_person_id' => $contactPersonId?->toRfc4122(), - ]); - } -} diff --git a/tests/Functional/ApplicationInstallations/Builders/ApplicationInstallationBuilder.php b/tests/Functional/ApplicationInstallations/Builders/ApplicationInstallationBuilder.php index 5abde64..05e087a 100644 --- a/tests/Functional/ApplicationInstallations/Builders/ApplicationInstallationBuilder.php +++ b/tests/Functional/ApplicationInstallations/Builders/ApplicationInstallationBuilder.php @@ -21,7 +21,7 @@ class ApplicationInstallationBuilder private ?Uuid $bitrix24PartnerContactPersonId; - private readonly ?Uuid $bitrix24PartnerId; + private ?Uuid $bitrix24PartnerId = null; private ?string $externalId = null; @@ -43,7 +43,6 @@ public function __construct() $this->bitrix24AccountId = Uuid::v7(); $this->bitrix24PartnerContactPersonId = Uuid::v7(); $this->contactPersonId = Uuid::v7(); - $this->bitrix24PartnerId = Uuid::v7(); $this->portalUsersCount = random_int(1, 1_000_000); } @@ -61,6 +60,13 @@ public function withApplicationToken(string $applicationToken): self return $this; } + public function withBitrix24PartnerId(?Uuid $bitrix24PartnerId): self + { + $this->bitrix24PartnerId = $bitrix24PartnerId; + + return $this; + } + public function withApplicationStatusInstallation(ApplicationInstallationStatus $applicationInstallationStatus): self { $this->status = $applicationInstallationStatus; diff --git a/tests/Functional/ContactPersons/Builders/ContactPersonBuilder.php b/tests/Functional/ContactPersons/Builders/ContactPersonBuilder.php index a7964d6..71cf7ac 100644 --- a/tests/Functional/ContactPersons/Builders/ContactPersonBuilder.php +++ b/tests/Functional/ContactPersons/Builders/ContactPersonBuilder.php @@ -41,7 +41,6 @@ public function __construct() $this->id = Uuid::v7(); $this->fullName = DemoDataGenerator::getFullName(); $this->bitrix24UserId = random_int(1, 1_000_000); - $this->bitrix24PartnerId = Uuid::v7(); } public function withStatus(ContactPersonStatus $contactPersonStatus): self @@ -93,7 +92,7 @@ public function withBitrix24UserId(int $bitrix24UserId): self return $this; } - public function withBitrix24PartnerId(Uuid $uuid): self + public function withBitrix24PartnerId(?Uuid $uuid): self { $this->bitrix24PartnerId = $uuid; diff --git a/tests/Functional/ContactPersons/UseCase/InstallContactPerson/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php similarity index 69% rename from tests/Functional/ContactPersons/UseCase/InstallContactPerson/HandlerTest.php rename to tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php index b33ac3a..3c2105e 100644 --- a/tests/Functional/ContactPersons/UseCase/InstallContactPerson/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\InstallContactPerson; +namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\Install; use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository; use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository; -use Bitrix24\Lib\ContactPersons\UseCase\InstallContactPerson\Handler; -use Bitrix24\Lib\ContactPersons\UseCase\InstallContactPerson\Command; +use Bitrix24\Lib\ContactPersons\UseCase\Install\Handler; +use Bitrix24\Lib\ContactPersons\UseCase\Install\Command; use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; use Bitrix24\Lib\Services\Flusher; use Bitrix24\Lib\Tests\Functional\ApplicationInstallations\Builders\ApplicationInstallationBuilder; @@ -84,9 +84,6 @@ protected function setUp(): void ); } - /** - * @throws InvalidArgumentException|\Random\RandomException - */ #[Test] public function testInstallContactPersonSuccess(): void { @@ -129,7 +126,7 @@ public function testInstallContactPersonSuccess(): void ->withComment('Test comment') ->withExternalId($externalId) ->withBitrix24UserId($bitrix24Account->getBitrix24UserId()) - ->withBitrix24PartnerId($applicationInstallation->getBitrix24PartnerId() ?? Uuid::v7()) + ->withBitrix24PartnerId($applicationInstallation->getBitrix24PartnerId()) ->build(); // Запуск use-case @@ -162,6 +159,82 @@ public function testInstallContactPersonSuccess(): void $this->assertEquals($foundContactPerson->getId(), $uuid); } + #[Test] + public function testInstallPartnerContactPersonSuccess(): void + { + // Подготовка Bitrix24 аккаунта и установки приложения + $applicationToken = Uuid::v7()->toRfc4122(); + $memberId = Uuid::v4()->toRfc4122(); + $externalId = Uuid::v7()->toRfc4122(); + $bitrix24PartnerId = Uuid::v7(); + + $bitrix24Account = (new Bitrix24AccountBuilder()) + ->withApplicationScope(new Scope(['crm'])) + ->withStatus(Bitrix24AccountStatus::new) + ->withApplicationToken($applicationToken) + ->withMemberId($memberId) + ->withMaster(true) + ->withSetToken() + ->withInstalled() + ->build(); + + $this->bitrix24accountRepository->save($bitrix24Account); + + $applicationInstallation = (new ApplicationInstallationBuilder()) + ->withApplicationStatus(new ApplicationStatus('F')) + ->withPortalLicenseFamily(PortalLicenseFamily::free) + ->withBitrix24AccountId($bitrix24Account->getId()) + ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) + ->withApplicationToken($applicationToken) + ->withContactPersonId(null) + ->withBitrix24PartnerId($bitrix24PartnerId) + ->withExternalId($externalId) + ->build(); + + $this->applicationInstallationRepository->save($applicationInstallation); + $this->flusher->flush(); + + // Данные контакта + $contactPersonBuilder = new ContactPersonBuilder(); + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId($externalId) + ->withBitrix24UserId($bitrix24Account->getBitrix24UserId()) + ->withBitrix24PartnerId($applicationInstallation->getBitrix24PartnerId()) + ->build(); + + // Запуск use-case + $this->handler->handle( + new Command( + $applicationInstallation->getId(), + $contactPerson->getFullName(), + $bitrix24Account->getBitrix24UserId(), + $contactPerson->getUserAgentInfo(), + $contactPerson->getEmail(), + $contactPerson->getMobilePhone(), + $contactPerson->getComment(), + $contactPerson->getExternalId(), + $contactPerson->getBitrix24PartnerId(), + ) + ); + + // Проверки: событие, связь и наличие контакта + $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); + $this->assertContains(ContactPersonCreatedEvent::class, $dispatchedEvents); + $this->assertContains(ApplicationInstallationBitrix24PartnerContactPersonLinkedEvent::class, $dispatchedEvents); + + // Перечитаем установку и проверим привязку контактного лица (без поиска по externalId) + $foundInstallation = $this->applicationInstallationRepository->getById($applicationInstallation->getId()); + $this->assertNotNull($foundInstallation->getBitrix24PartnerContactPersonId()); + + $uuid = $foundInstallation->getBitrix24PartnerContactPersonId(); + $foundContactPerson = $this->repository->getById($uuid); + $this->assertInstanceOf(ContactPersonInterface::class, $foundContactPerson); + $this->assertEquals($foundContactPerson->getId(), $uuid); + } + #[Test] public function testInstallContactPersonWithWrongApplicationInstallationId(): void { diff --git a/tests/Functional/ContactPersons/UseCase/InstallPartnerContactPerson/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/InstallPartnerContactPerson/HandlerTest.php deleted file mode 100644 index d3192fd..0000000 --- a/tests/Functional/ContactPersons/UseCase/InstallPartnerContactPerson/HandlerTest.php +++ /dev/null @@ -1,201 +0,0 @@ - - * - * For the full copyright and license information, please view the MIT-LICENSE.txt - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\InstallPartnerContactPerson; - -use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository; -use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository; -use Bitrix24\Lib\ContactPersons\UseCase\InstallPartnerContactPerson\Handler; -use Bitrix24\Lib\ContactPersons\UseCase\InstallPartnerContactPerson\Command; -use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; -use Bitrix24\Lib\Services\Flusher; -use Bitrix24\Lib\Tests\Functional\ApplicationInstallations\Builders\ApplicationInstallationBuilder; -use Bitrix24\Lib\Tests\Functional\Bitrix24Accounts\Builders\Bitrix24AccountBuilder; -use Bitrix24\SDK\Application\ApplicationStatus; -use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationStatus; -use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationBitrix24PartnerContactPersonLinkedEvent; -use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationBitrix24PartnerLinkedEvent; -use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationContactPersonLinkedEvent; -use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonCreatedEvent; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonEmailChangedEvent; -use Bitrix24\SDK\Application\PortalLicenseFamily; -use Bitrix24\SDK\Core\Credentials\Scope; -use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; -use Bitrix24\Lib\Tests\EntityManagerFactory; -use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Exceptions\ApplicationInstallationNotFoundException; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\Test; -use PHPUnit\Framework\TestCase; -use Psr\Log\NullLogger; -use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; -use Symfony\Component\EventDispatcher\EventDispatcher; -use Symfony\Component\Stopwatch\Stopwatch; -use Symfony\Component\Uid\Uuid; -use libphonenumber\PhoneNumberUtil; -use libphonenumber\PhoneNumber; -use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; -use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; - -/** - * @internal - */ -#[CoversClass(Handler::class)] -class HandlerTest extends TestCase -{ - private Handler $handler; - - private Flusher $flusher; - - private ContactPersonRepository $repository; - - private ApplicationInstallationRepository $applicationInstallationRepository; - - private Bitrix24AccountRepository $bitrix24accountRepository; - - private TraceableEventDispatcher $eventDispatcher; - - #[\Override] - protected function setUp(): void - { - $entityManager = EntityManagerFactory::get(); - $this->eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); - $this->repository = new ContactPersonRepository($entityManager); - $this->applicationInstallationRepository = new ApplicationInstallationRepository($entityManager); - $this->bitrix24accountRepository = new Bitrix24AccountRepository($entityManager); - $this->flusher = new Flusher($entityManager, $this->eventDispatcher); - $this->handler = new Handler( - $this->applicationInstallationRepository, - $this->repository, - $this->flusher, - new NullLogger() - ); - } - - /** - * @throws InvalidArgumentException|\Random\RandomException - */ - #[Test] - public function testInstallPartnerContactPersonSuccess(): void - { - // Подготовка Bitrix24 аккаунта и установки приложения - $applicationToken = Uuid::v7()->toRfc4122(); - $memberId = Uuid::v4()->toRfc4122(); - $externalId = Uuid::v7()->toRfc4122(); - - $bitrix24Account = (new Bitrix24AccountBuilder()) - ->withApplicationScope(new Scope(['crm'])) - ->withStatus(Bitrix24AccountStatus::new) - ->withApplicationToken($applicationToken) - ->withMemberId($memberId) - ->withMaster(true) - ->withSetToken() - ->withInstalled() - ->build(); - - $this->bitrix24accountRepository->save($bitrix24Account); - - $applicationInstallation = (new ApplicationInstallationBuilder()) - ->withApplicationStatus(new ApplicationStatus('F')) - ->withPortalLicenseFamily(PortalLicenseFamily::free) - ->withBitrix24AccountId($bitrix24Account->getId()) - ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) - ->withApplicationToken($applicationToken) - ->withContactPersonId(null) - ->withBitrix24PartnerContactPersonId(null) - ->withExternalId($externalId) - ->build(); - - $this->applicationInstallationRepository->save($applicationInstallation); - $this->flusher->flush(); - - // Данные контакта - $contactPersonBuilder = new ContactPersonBuilder(); - $contactPerson = $contactPersonBuilder - ->withEmail('john.doe@example.com') - ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) - ->withComment('Test comment') - ->withExternalId($externalId) - ->withBitrix24UserId($bitrix24Account->getBitrix24UserId()) - ->withBitrix24PartnerId($applicationInstallation->getBitrix24PartnerId() ?? Uuid::v7()) - ->build(); - - // Запуск use-case - $this->handler->handle( - new Command( - $applicationInstallation->getId(), - $contactPerson->getFullName(), - $bitrix24Account->getBitrix24UserId(), - $contactPerson->getUserAgentInfo(), - $contactPerson->getEmail(), - $contactPerson->getMobilePhone(), - $contactPerson->getComment(), - $contactPerson->getExternalId(), - $contactPerson->getBitrix24PartnerId(), - ) - ); - - // Проверки: событие, связь и наличие контакта - $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); - $this->assertContains(ContactPersonCreatedEvent::class, $dispatchedEvents); - $this->assertContains(ApplicationInstallationBitrix24PartnerContactPersonLinkedEvent::class, $dispatchedEvents); - - // Перечитаем установку и проверим привязку контактного лица (без поиска по externalId) - $foundInstallation = $this->applicationInstallationRepository->getById($applicationInstallation->getId()); - $this->assertNotNull($foundInstallation->getBitrix24PartnerContactPersonId()); - - $uuid = $foundInstallation->getBitrix24PartnerContactPersonId(); - $foundContactPerson = $this->repository->getById($uuid); - $this->assertInstanceOf(ContactPersonInterface::class, $foundContactPerson); - $this->assertEquals($foundContactPerson->getId(), $uuid); - } - - #[Test] - public function testInstallPartnerContactPersonWithWrongApplicationInstallationId(): void - { - // Подготовим входные данные контакта (без реальной установки) - $contactPersonBuilder = new ContactPersonBuilder(); - $contactPerson = $contactPersonBuilder - ->withEmail('john.doe@example.com') - ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) - ->withComment('Test comment') - ->withExternalId(Uuid::v7()->toRfc4122()) - ->build(); - - $uuidV7 = Uuid::v7(); - - $this->expectException(ApplicationInstallationNotFoundException::class); - - $this->handler->handle( - new Command( - $uuidV7, - $contactPerson->getFullName(), - random_int(1, 1_000_000), - $contactPerson->getUserAgentInfo(), - $contactPerson->getEmail(), - $contactPerson->getMobilePhone(), - $contactPerson->getComment(), - $contactPerson->getExternalId(), - $contactPerson->getBitrix24PartnerId(), - ) - ); - } - - private function createPhoneNumber(string $number): PhoneNumber - { - $phoneNumberUtil = PhoneNumberUtil::getInstance(); - return $phoneNumberUtil->parse($number, 'RU'); - } -} \ No newline at end of file diff --git a/tests/Functional/ContactPersons/UseCase/UnlinkPartnerContactPerson/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/Unlink/HandlerTest.php similarity index 50% rename from tests/Functional/ContactPersons/UseCase/UnlinkPartnerContactPerson/HandlerTest.php rename to tests/Functional/ContactPersons/UseCase/Unlink/HandlerTest.php index cd63b06..44eee1e 100644 --- a/tests/Functional/ContactPersons/UseCase/UnlinkPartnerContactPerson/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/Unlink/HandlerTest.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\UnlinkPartnerContactPerson; +namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\Unlink; use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository; use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository; -use Bitrix24\Lib\ContactPersons\UseCase\UnlinkPartnerContactPerson\Handler; -use Bitrix24\Lib\ContactPersons\UseCase\UnlinkPartnerContactPerson\Command; +use Bitrix24\Lib\ContactPersons\UseCase\Unlink\Handler; +use Bitrix24\Lib\ContactPersons\UseCase\Unlink\Command; use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; use Bitrix24\Lib\Services\Flusher; use Bitrix24\Lib\Tests\Functional\ApplicationInstallations\Builders\ApplicationInstallationBuilder; @@ -24,6 +24,7 @@ use Bitrix24\SDK\Application\ApplicationStatus; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationStatus; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationBitrix24PartnerContactPersonUnlinkedEvent; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationContactPersonUnlinkedEvent; use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonDeletedEvent; @@ -80,6 +81,150 @@ protected function setUp(): void ); } + /** + * @throws InvalidArgumentException|\Random\RandomException + */ + #[Test] + public function testUninstallContactPersonSuccess(): void + { + // Подготовка Bitrix24 аккаунта и установки приложения + $applicationToken = Uuid::v7()->toRfc4122(); + $memberId = Uuid::v4()->toRfc4122(); + $externalId = Uuid::v7()->toRfc4122(); + + $bitrix24Account = (new Bitrix24AccountBuilder()) + ->withApplicationScope(new Scope(['crm'])) + ->withStatus(Bitrix24AccountStatus::new) + ->withApplicationToken($applicationToken) + ->withMemberId($memberId) + ->withMaster(true) + ->withSetToken() + ->withInstalled() + ->build(); + + $this->bitrix24accountRepository->save($bitrix24Account); + + $applicationInstallation = (new ApplicationInstallationBuilder()) + ->withApplicationStatus(new ApplicationStatus('F')) + ->withPortalLicenseFamily(PortalLicenseFamily::free) + ->withBitrix24AccountId($bitrix24Account->getId()) + ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) + ->withApplicationToken($applicationToken) + ->withContactPersonId(null) + ->withBitrix24PartnerContactPersonId(null) + ->withExternalId($externalId) + ->build(); + + $this->applicationInstallationRepository->save($applicationInstallation); + + // Создаём контакт и привязываем к установке + $contactPersonBuilder = new ContactPersonBuilder(); + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId($externalId) + ->withBitrix24UserId($bitrix24Account->getBitrix24UserId()) + ->build(); + + $this->repository->save($contactPerson); + $applicationInstallation->linkContactPerson($contactPerson->getId()); + $this->applicationInstallationRepository->save($applicationInstallation); + $this->flusher->flush(); + + // Запуск use-case + $this->handler->handle( + new Command( + $contactPerson->getId(), + 'Deleted by test' + ) + ); + + // Проверки: события отвязки и удаления контакта + $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); + $this->assertContains(ContactPersonDeletedEvent::class, $dispatchedEvents); + $this->assertContains(ApplicationInstallationContactPersonUnlinkedEvent::class, $dispatchedEvents); + + // Перечитаем установку и проверим, что контакт отвязан + $foundInstallation = $this->applicationInstallationRepository->getById($applicationInstallation->getId()); + $this->assertNull($foundInstallation->getContactPersonId()); + + // Контакт помечен как удалённый и недоступен через getById + $this->expectException(ContactPersonNotFoundException::class); + $this->repository->getById($contactPerson->getId()); + } + + #[Test] + public function testUninstallContactPersonNotFound(): void + { + // Подготовка Bitrix24 аккаунта и установки приложения (чтобы getCurrent() вернул установку) + $applicationToken = Uuid::v7()->toRfc4122(); + $memberId = Uuid::v4()->toRfc4122(); + $externalId = Uuid::v7()->toRfc4122(); + + $bitrix24Account = (new Bitrix24AccountBuilder()) + ->withApplicationScope(new Scope(['crm'])) + ->withStatus(Bitrix24AccountStatus::new) + ->withApplicationToken($applicationToken) + ->withMemberId($memberId) + ->withMaster(true) + ->withSetToken() + ->withInstalled() + ->build(); + + $this->bitrix24accountRepository->save($bitrix24Account); + + $applicationInstallation = (new ApplicationInstallationBuilder()) + ->withApplicationStatus(new ApplicationStatus('F')) + ->withPortalLicenseFamily(PortalLicenseFamily::free) + ->withBitrix24AccountId($bitrix24Account->getId()) + ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) + ->withApplicationToken($applicationToken) + ->withContactPersonId(null) + ->withBitrix24PartnerContactPersonId(null) + ->withExternalId($externalId) + ->build(); + + $this->applicationInstallationRepository->save($applicationInstallation); + $this->flusher->flush(); + + // Ожидаем исключение, т.к. контактного лица с таким ID нет + $this->expectException(ContactPersonNotFoundException::class); + + $this->handler->handle( + new Command( + Uuid::v7(), + 'Deleted by test' + ) + ); + } + + #[Test] + public function testUninstallContactPersonWithWrongApplicationInstallationId(): void + { + // Создадим контактное лицо, но не будем создавать установку приложения, + // чтобы репозиторий вернул ApplicationInstallationNotFoundException при getCurrent() + $externalId = Uuid::v7()->toRfc4122(); + $contactPerson = (new ContactPersonBuilder()) + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId($externalId) + ->build(); + + $this->repository->save($contactPerson); + $this->flusher->flush(); + + $this->expectException(ApplicationInstallationNotFoundException::class); + + $this->handler->handle( + new Command( + $contactPerson->getId(), + 'Deleted by test' + ) + ); + } + /** * @throws InvalidArgumentException|\Random\RandomException */ @@ -135,7 +280,7 @@ public function testUninstallPartnerContactPersonSuccess(): void // Запуск use-case $this->handler->handle( new Command( - $applicationInstallation->getId(), + $contactPerson->getId(), 'Deleted by test' ) ); @@ -157,13 +302,24 @@ public function testUninstallPartnerContactPersonSuccess(): void #[Test] public function testUninstallPartnerContactPersonWithWrongApplicationInstallationId(): void { - $uuidV7 = Uuid::v7(); + // Создадим контактное лицо, но не будем создавать установку приложения, + // чтобы репозиторий вернул ApplicationInstallationNotFoundException при getCurrent() + $externalId = Uuid::v7()->toRfc4122(); + $contactPerson = (new ContactPersonBuilder()) + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId($externalId) + ->build(); + + $this->repository->save($contactPerson); + $this->flusher->flush(); $this->expectException(ApplicationInstallationNotFoundException::class); $this->handler->handle( new Command( - $uuidV7, + $contactPerson->getId(), 'Deleted by test' ) ); diff --git a/tests/Functional/ContactPersons/UseCase/UnlinkContactPerson/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/UnlinkContactPerson/HandlerTest.php deleted file mode 100644 index 7ce0c41..0000000 --- a/tests/Functional/ContactPersons/UseCase/UnlinkContactPerson/HandlerTest.php +++ /dev/null @@ -1,176 +0,0 @@ - - * - * For the full copyright and license information, please view the MIT-LICENSE.txt - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\UnlinkContactPerson; - -use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository; -use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository; -use Bitrix24\Lib\ContactPersons\UseCase\UnlinkContactPerson\Handler; -use Bitrix24\Lib\ContactPersons\UseCase\UnlinkContactPerson\Command; -use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; -use Bitrix24\Lib\Services\Flusher; -use Bitrix24\Lib\Tests\Functional\ApplicationInstallations\Builders\ApplicationInstallationBuilder; -use Bitrix24\Lib\Tests\Functional\Bitrix24Accounts\Builders\Bitrix24AccountBuilder; -use Bitrix24\SDK\Application\ApplicationStatus; -use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationStatus; -use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationContactPersonUnlinkedEvent; -use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonDeletedEvent; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; -use Bitrix24\SDK\Application\PortalLicenseFamily; -use Bitrix24\SDK\Core\Credentials\Scope; -use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; -use Bitrix24\Lib\Tests\EntityManagerFactory; -use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Exceptions\ApplicationInstallationNotFoundException; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\Test; -use PHPUnit\Framework\TestCase; -use Psr\Log\NullLogger; -use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; -use Symfony\Component\EventDispatcher\EventDispatcher; -use Symfony\Component\Stopwatch\Stopwatch; -use Symfony\Component\Uid\Uuid; -use libphonenumber\PhoneNumberUtil; -use libphonenumber\PhoneNumber; -use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; - -/** - * @internal - */ -#[CoversClass(Handler::class)] -class HandlerTest extends TestCase -{ - private Handler $handler; - - private Flusher $flusher; - - private ContactPersonRepository $repository; - - private ApplicationInstallationRepository $applicationInstallationRepository; - - private Bitrix24AccountRepository $bitrix24accountRepository; - - private TraceableEventDispatcher $eventDispatcher; - - #[\Override] - protected function setUp(): void - { - $entityManager = EntityManagerFactory::get(); - $this->eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); - $this->repository = new ContactPersonRepository($entityManager); - $this->applicationInstallationRepository = new ApplicationInstallationRepository($entityManager); - $this->bitrix24accountRepository = new Bitrix24AccountRepository($entityManager); - $this->flusher = new Flusher($entityManager, $this->eventDispatcher); - $this->handler = new Handler( - $this->applicationInstallationRepository, - $this->repository, - $this->flusher, - new NullLogger() - ); - } - - /** - * @throws InvalidArgumentException|\Random\RandomException - */ - #[Test] - public function testUninstallContactPersonSuccess(): void - { - // Подготовка Bitrix24 аккаунта и установки приложения - $applicationToken = Uuid::v7()->toRfc4122(); - $memberId = Uuid::v4()->toRfc4122(); - $externalId = Uuid::v7()->toRfc4122(); - - $bitrix24Account = (new Bitrix24AccountBuilder()) - ->withApplicationScope(new Scope(['crm'])) - ->withStatus(Bitrix24AccountStatus::new) - ->withApplicationToken($applicationToken) - ->withMemberId($memberId) - ->withMaster(true) - ->withSetToken() - ->withInstalled() - ->build(); - - $this->bitrix24accountRepository->save($bitrix24Account); - - $applicationInstallation = (new ApplicationInstallationBuilder()) - ->withApplicationStatus(new ApplicationStatus('F')) - ->withPortalLicenseFamily(PortalLicenseFamily::free) - ->withBitrix24AccountId($bitrix24Account->getId()) - ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) - ->withApplicationToken($applicationToken) - ->withContactPersonId(null) - ->withBitrix24PartnerContactPersonId(null) - ->withExternalId($externalId) - ->build(); - - $this->applicationInstallationRepository->save($applicationInstallation); - - // Создаём контакт и привязываем к установке - $contactPersonBuilder = new ContactPersonBuilder(); - $contactPerson = $contactPersonBuilder - ->withEmail('john.doe@example.com') - ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) - ->withComment('Test comment') - ->withExternalId($externalId) - ->withBitrix24UserId($bitrix24Account->getBitrix24UserId()) - ->build(); - - $this->repository->save($contactPerson); - $applicationInstallation->linkContactPerson($contactPerson->getId()); - $this->applicationInstallationRepository->save($applicationInstallation); - $this->flusher->flush(); - - // Запуск use-case - $this->handler->handle( - new Command( - $applicationInstallation->getId(), - 'Deleted by test' - ) - ); - - // Проверки: события отвязки и удаления контакта - $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); - $this->assertContains(ContactPersonDeletedEvent::class, $dispatchedEvents); - $this->assertContains(ApplicationInstallationContactPersonUnlinkedEvent::class, $dispatchedEvents); - - // Перечитаем установку и проверим, что контакт отвязан - $foundInstallation = $this->applicationInstallationRepository->getById($applicationInstallation->getId()); - $this->assertNull($foundInstallation->getContactPersonId()); - - // Контакт всё ещё доступен в репозитории (с пометкой deleted), сам факт наличия достаточен для данного теста - $this->expectException(ContactPersonNotFoundException::class); - $this->repository->getById($contactPerson->getId()); - } - - #[Test] - public function testUninstallContactPersonWithWrongApplicationInstallationId(): void - { - $uuidV7 = Uuid::v7(); - - $this->expectException(ApplicationInstallationNotFoundException::class); - - $this->handler->handle( - new Command( - $uuidV7, - 'Deleted by test' - ) - ); - } - - private function createPhoneNumber(string $number): PhoneNumber - { - $phoneNumberUtil = PhoneNumberUtil::getInstance(); - return $phoneNumberUtil->parse($number, 'RU'); - } -} From bf37bf5759bd8402d94d9f0b681412875187cf7f Mon Sep 17 00:00:00 2001 From: kirill Date: Thu, 25 Dec 2025 00:08:45 +0300 Subject: [PATCH 14/31] Add email validation and verification timestamp handling in ContactPerson use case --- src/ContactPersons/Entity/ContactPerson.php | 16 +++- .../UseCase/MarkEmailAsVerified/Command.php | 16 +++- .../UseCase/MarkEmailAsVerified/Handler.php | 22 ++++- .../MarkEmailAsVerified/HandlerTest.php | 90 ++++++++++++++++++- 4 files changed, 132 insertions(+), 12 deletions(-) diff --git a/src/ContactPersons/Entity/ContactPerson.php b/src/ContactPersons/Entity/ContactPerson.php index 2c9533b..afb5476 100644 --- a/src/ContactPersons/Entity/ContactPerson.php +++ b/src/ContactPersons/Entity/ContactPerson.php @@ -171,10 +171,15 @@ public function changeEmail(?string $email): void } #[\Override] - public function markEmailAsVerified(): void + public function markEmailAsVerified(?CarbonImmutable $verifiedAt = null): void { $this->isEmailVerified = true; - $this->emailVerifiedAt = new CarbonImmutable(); + + if (null == $verifiedAt) { + $verifiedAt = new CarbonImmutable(); + } + $this->emailVerifiedAt = $verifiedAt; + $this->events[] = new ContactPersonEmailVerifiedEvent( $this->id, $this->emailVerifiedAt, @@ -235,10 +240,13 @@ public function getMobilePhoneVerifiedAt(): ?CarbonImmutable } #[\Override] - public function markMobilePhoneAsVerified(): void + public function markMobilePhoneAsVerified(?CarbonImmutable $verifiedAt = null): void { $this->isMobilePhoneVerified = true; - $this->mobilePhoneVerifiedAt = new CarbonImmutable(); + if (null == $verifiedAt) { + $verifiedAt = new CarbonImmutable(); + } + $this->mobilePhoneVerifiedAt = $verifiedAt; $this->events[] = new ContactPersonMobilePhoneVerifiedEvent( $this->id, $this->mobilePhoneVerifiedAt, diff --git a/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php b/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php index 2fe6861..55c1dbe 100644 --- a/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php +++ b/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php @@ -4,11 +4,23 @@ namespace Bitrix24\Lib\ContactPersons\UseCase\MarkEmailAsVerified; +use Carbon\CarbonImmutable; use Symfony\Component\Uid\Uuid; - +use InvalidArgumentException; readonly class Command { public function __construct( public Uuid $contactPersonId, - ) {} + public string $email, + public ?CarbonImmutable $emailVerifiedAt = null + ) { + $this->validate(); + } + + private function validate(): void + { + if (null !== $this->email && !filter_var($this->email, FILTER_VALIDATE_EMAIL)) { + throw new InvalidArgumentException('Invalid email format.'); + } + } } diff --git a/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php index 5ef1195..fbe00f5 100644 --- a/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php +++ b/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php @@ -9,7 +9,7 @@ use Bitrix24\SDK\Application\Contracts\ContactPersons\Repository\ContactPersonRepositoryInterface; use Bitrix24\SDK\Application\Contracts\Events\AggregateRootEventsEmitterInterface; use Psr\Log\LoggerInterface; - +use InvalidArgumentException; readonly class Handler { public function __construct( @@ -22,17 +22,35 @@ public function handle(Command $command): void { $this->logger->info('ContactPerson.ConfirmEmailVerification.start', [ 'contactPersonId' => $command->contactPersonId->toRfc4122(), + 'email' => $command->email, ]); /** @var null|AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); - $contactPerson->markEmailAsVerified(); + + $actualEmail = $contactPerson->getEmail(); + if (mb_strtolower((string)$actualEmail) !== mb_strtolower($command->email)) { + $this->logger->warning('ContactPerson.ConfirmEmailVerification.emailMismatch', [ + 'contactPersonId' => $command->contactPersonId->toRfc4122(), + 'actualEmail' => $actualEmail, + 'expectedEmail' => $command->email, + ]); + throw new InvalidArgumentException(sprintf( + 'Email mismatch for contact person %s: actual="%s", expected="%s"', + $command->contactPersonId->toRfc4122(), + $actualEmail, + $command->email + )); + } + + $contactPerson->markEmailAsVerified($command->emailVerifiedAt); $this->contactPersonRepository->save($contactPerson); $this->flusher->flush($contactPerson); $this->logger->info('ContactPerson.ConfirmEmailVerification.finish', [ 'contactPersonId' => $contactPerson->getId()->toRfc4122(), + 'emailVerifiedAt' => $contactPerson->getEmailVerifiedAt()?->toIso8601String(), ]); } } diff --git a/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php index 9394860..2c50403 100644 --- a/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php @@ -15,6 +15,8 @@ use Bitrix24\Lib\ContactPersons\UseCase\MarkEmailAsVerified\Handler; use Bitrix24\Lib\ContactPersons\UseCase\MarkEmailAsVerified\Command; +use InvalidArgumentException; +use Carbon\CarbonImmutable; use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; use Bitrix24\Lib\Services\Flusher; use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; @@ -61,7 +63,7 @@ protected function setUp(): void } #[Test] - public function testConfirmEmailVerification(): void + public function testConfirmEmailVerification_Success_WithEmailAndTimestamp(): void { $contactPersonBuilder = new ContactPersonBuilder(); $externalId = Uuid::v7()->toRfc4122(); @@ -81,16 +83,19 @@ public function testConfirmEmailVerification(): void $this->assertFalse($contactPerson->isEmailVerified()); + $verifiedAt = new CarbonImmutable('2025-01-01T10:00:00+00:00'); $this->handler->handle( - new Command($contactPerson->getId()) + new Command($contactPerson->getId(), 'john.doe@example.com', $verifiedAt) ); $updatedContactPerson = $this->repository->getById($contactPerson->getId()); $this->assertTrue($updatedContactPerson->isEmailVerified()); + $this->assertNotNull($updatedContactPerson->getEmailVerifiedAt()); + $this->assertSame($verifiedAt->toISOString(), $updatedContactPerson->getEmailVerifiedAt()?->toISOString()); } #[Test] - public function testConfirmEmailVerificationFailsIfContactPersonNotFound(): void + public function testConfirmEmailVerification_Fails_IfContactPersonNotFound(): void { $contactPersonBuilder = new ContactPersonBuilder(); $externalId = Uuid::v7()->toRfc4122(); @@ -111,7 +116,84 @@ public function testConfirmEmailVerificationFailsIfContactPersonNotFound(): void $this->assertFalse($contactPerson->isEmailVerified()); $this->expectException(ContactPersonNotFoundException::class); - $this->handler->handle(new Command(Uuid::v7())); + $this->handler->handle(new Command(Uuid::v7(), 'john.doe@example.com')); + } + + #[Test] + public function testConfirmEmailVerification_Fails_IfEmailMismatch(): void + { + $contactPersonBuilder = new ContactPersonBuilder(); + $externalId = Uuid::v7()->toRfc4122(); + $bitrix24UserId = random_int(1, 1_000_000); + + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId($externalId) + ->withBitrix24UserId($bitrix24UserId) + ->withBitrix24PartnerId(Uuid::v7()) + ->build(); + + $this->repository->save($contactPerson); + $this->flusher->flush(); + + $this->expectException(InvalidArgumentException::class); + $this->handler->handle( + new Command($contactPerson->getId(), 'another.email@example.com') + ); + } + + #[Test] + public function testConfirmEmailVerification_Fails_IfEntityHasNoEmailButCommandProvidesOne(): void + { + $contactPersonBuilder = new ContactPersonBuilder(); + $externalId = Uuid::v7()->toRfc4122(); + $bitrix24UserId = random_int(1, 1_000_000); + + // Не задаём email в сущности (не вызываем withEmail) + $contactPerson = $contactPersonBuilder + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId($externalId) + ->withBitrix24UserId($bitrix24UserId) + ->withBitrix24PartnerId(Uuid::v7()) + ->build(); + + $this->repository->save($contactPerson); + $this->flusher->flush(); + + // В обработчик передаём email — ожидаем исключение о несоответствии + $this->expectException(InvalidArgumentException::class); + $this->handler->handle( + new Command($contactPerson->getId(), 'john.doe@example.com') + ); + } + + #[Test] + public function testConfirmEmailVerification_Fails_IfInvalidEmailProvided(): void + { + $contactPersonBuilder = new ContactPersonBuilder(); + $externalId = Uuid::v7()->toRfc4122(); + $bitrix24UserId = random_int(1, 1_000_000); + + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withComment('Test comment') + ->withExternalId($externalId) + ->withBitrix24UserId($bitrix24UserId) + ->withBitrix24PartnerId(Uuid::v7()) + ->build(); + + $this->repository->save($contactPerson); + $this->flusher->flush(); + + $this->expectException(InvalidArgumentException::class); + // Неверный email должен упасть на валидации конструктора команды + $this->handler->handle( + new Command($contactPerson->getId(), 'not-an-email') + ); } private function createPhoneNumber(string $number): PhoneNumber From 8a548d6b719933bee4843c9dc0fb336bffb5205c Mon Sep 17 00:00:00 2001 From: kirill Date: Sat, 27 Dec 2025 00:09:21 +0300 Subject: [PATCH 15/31] Validate UUID instance for unlink methods, enhance phone verification with validation and mismatch handling --- .../Entity/ApplicationInstallation.php | 4 +-- src/ContactPersons/Entity/ContactPerson.php | 17 ++------- .../UseCase/Install/Handler.php | 4 +-- .../UseCase/MarkEmailAsVerified/Command.php | 4 +-- .../UseCase/MarkEmailAsVerified/Handler.php | 7 ++-- .../UseCase/MarkPhoneAsVerified/Command.php | 24 ++++++++++++- .../UseCase/MarkPhoneAsVerified/Handler.php | 31 ++++++++++++++-- src/ContactPersons/UseCase/Unlink/Handler.php | 12 +++---- .../ApplicationInstallationBuilder.php | 4 +-- .../UseCase/Install/HandlerTest.php | 4 +-- .../MarkPhoneAsVerified/HandlerTest.php | 35 ++++++++++++++++--- .../UseCase/Unlink/HandlerTest.php | 35 +++++++++++++++++++ 12 files changed, 138 insertions(+), 43 deletions(-) diff --git a/src/ApplicationInstallations/Entity/ApplicationInstallation.php b/src/ApplicationInstallations/Entity/ApplicationInstallation.php index e577a69..c1c0d93 100644 --- a/src/ApplicationInstallations/Entity/ApplicationInstallation.php +++ b/src/ApplicationInstallations/Entity/ApplicationInstallation.php @@ -347,7 +347,7 @@ public function linkContactPerson(Uuid $uuid): void #[\Override] public function unlinkContactPerson(): void { - if (null === $this->contactPersonId) { + if (!$this->contactPersonId instanceof Uuid) { return; } @@ -378,7 +378,7 @@ public function linkBitrix24PartnerContactPerson(Uuid $uuid): void #[\Override] public function unlinkBitrix24PartnerContactPerson(): void { - if (null === $this->bitrix24PartnerContactPersonId) { + if (!$this->bitrix24PartnerContactPersonId instanceof Uuid) { return; } diff --git a/src/ContactPersons/Entity/ContactPerson.php b/src/ContactPersons/Entity/ContactPerson.php index afb5476..693d023 100644 --- a/src/ContactPersons/Entity/ContactPerson.php +++ b/src/ContactPersons/Entity/ContactPerson.php @@ -175,11 +175,7 @@ public function markEmailAsVerified(?CarbonImmutable $verifiedAt = null): void { $this->isEmailVerified = true; - if (null == $verifiedAt) { - $verifiedAt = new CarbonImmutable(); - } - $this->emailVerifiedAt = $verifiedAt; - + $this->emailVerifiedAt = $verifiedAt ?? new CarbonImmutable(); $this->events[] = new ContactPersonEmailVerifiedEvent( $this->id, $this->emailVerifiedAt, @@ -188,11 +184,7 @@ public function markEmailAsVerified(?CarbonImmutable $verifiedAt = null): void public function isPartner(): bool { - if ($this->getBitrix24PartnerId() !== null) { - return true; - } - - return false; + return $this->getBitrix24PartnerId() instanceof Uuid; } #[\Override] @@ -243,10 +235,7 @@ public function getMobilePhoneVerifiedAt(): ?CarbonImmutable public function markMobilePhoneAsVerified(?CarbonImmutable $verifiedAt = null): void { $this->isMobilePhoneVerified = true; - if (null == $verifiedAt) { - $verifiedAt = new CarbonImmutable(); - } - $this->mobilePhoneVerifiedAt = $verifiedAt; + $this->mobilePhoneVerifiedAt = $verifiedAt ?? new CarbonImmutable(); $this->events[] = new ContactPersonMobilePhoneVerifiedEvent( $this->id, $this->mobilePhoneVerifiedAt, diff --git a/src/ContactPersons/UseCase/Install/Handler.php b/src/ContactPersons/UseCase/Install/Handler.php index e6481b3..205eae1 100644 --- a/src/ContactPersons/UseCase/Install/Handler.php +++ b/src/ContactPersons/UseCase/Install/Handler.php @@ -28,7 +28,7 @@ public function handle(Command $command): void $this->logger->info('ContactPerson.InstallContactPerson.start', [ 'applicationInstallationId' => $command->applicationInstallationId, 'bitrix24UserId' => $command->bitrix24UserId, - 'bitrix24PartnerId' => $command->bitrix24PartnerId?->toRfc4122() ?? '' + 'bitrix24PartnerId' => $command->bitrix24PartnerId?->toRfc4122() ?? '', ]); /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $applicationInstallation */ @@ -56,7 +56,7 @@ public function handle(Command $command): void if ($contactPerson->isPartner()) { $applicationInstallation->linkBitrix24PartnerContactPerson($uuidV7); - }else{ + } else { $applicationInstallation->linkContactPerson($uuidV7); } diff --git a/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php b/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php index 55c1dbe..68ad098 100644 --- a/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php +++ b/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php @@ -6,7 +6,7 @@ use Carbon\CarbonImmutable; use Symfony\Component\Uid\Uuid; -use InvalidArgumentException; + readonly class Command { public function __construct( @@ -20,7 +20,7 @@ public function __construct( private function validate(): void { if (null !== $this->email && !filter_var($this->email, FILTER_VALIDATE_EMAIL)) { - throw new InvalidArgumentException('Invalid email format.'); + throw new \InvalidArgumentException('Invalid email format.'); } } } diff --git a/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php index fbe00f5..562e680 100644 --- a/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php +++ b/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php @@ -9,7 +9,7 @@ use Bitrix24\SDK\Application\Contracts\ContactPersons\Repository\ContactPersonRepositoryInterface; use Bitrix24\SDK\Application\Contracts\Events\AggregateRootEventsEmitterInterface; use Psr\Log\LoggerInterface; -use InvalidArgumentException; + readonly class Handler { public function __construct( @@ -29,13 +29,14 @@ public function handle(Command $command): void $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); $actualEmail = $contactPerson->getEmail(); - if (mb_strtolower((string)$actualEmail) !== mb_strtolower($command->email)) { + if (mb_strtolower((string) $actualEmail) !== mb_strtolower($command->email)) { $this->logger->warning('ContactPerson.ConfirmEmailVerification.emailMismatch', [ 'contactPersonId' => $command->contactPersonId->toRfc4122(), 'actualEmail' => $actualEmail, 'expectedEmail' => $command->email, ]); - throw new InvalidArgumentException(sprintf( + + throw new \InvalidArgumentException(sprintf( 'Email mismatch for contact person %s: actual="%s", expected="%s"', $command->contactPersonId->toRfc4122(), $actualEmail, diff --git a/src/ContactPersons/UseCase/MarkPhoneAsVerified/Command.php b/src/ContactPersons/UseCase/MarkPhoneAsVerified/Command.php index 3df1c9f..68f60ee 100644 --- a/src/ContactPersons/UseCase/MarkPhoneAsVerified/Command.php +++ b/src/ContactPersons/UseCase/MarkPhoneAsVerified/Command.php @@ -4,11 +4,33 @@ namespace Bitrix24\Lib\ContactPersons\UseCase\MarkPhoneAsVerified; +use Carbon\CarbonImmutable; +use libphonenumber\PhoneNumber; +use libphonenumber\PhoneNumberType; +use libphonenumber\PhoneNumberUtil; use Symfony\Component\Uid\Uuid; readonly class Command { public function __construct( public Uuid $contactPersonId, - ) {} + public PhoneNumber $phone, + public ?CarbonImmutable $phoneVerifiedAt = null, + ) { + $this->validate(); + } + + private function validate(): void + { + $phoneNumberUtil = PhoneNumberUtil::getInstance(); + $isValidNumber = $phoneNumberUtil->isValidNumber($this->phone); + if (!$isValidNumber) { + throw new \InvalidArgumentException('Invalid phone number.'); + } + + $numberType = $phoneNumberUtil->getNumberType($this->phone); + if (PhoneNumberType::MOBILE !== $numberType) { + throw new \InvalidArgumentException('Phone number must be mobile.'); + } + } } diff --git a/src/ContactPersons/UseCase/MarkPhoneAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkPhoneAsVerified/Handler.php index e555f7e..2ac9a80 100644 --- a/src/ContactPersons/UseCase/MarkPhoneAsVerified/Handler.php +++ b/src/ContactPersons/UseCase/MarkPhoneAsVerified/Handler.php @@ -8,6 +8,8 @@ use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; use Bitrix24\SDK\Application\Contracts\ContactPersons\Repository\ContactPersonRepositoryInterface; use Bitrix24\SDK\Application\Contracts\Events\AggregateRootEventsEmitterInterface; +use libphonenumber\PhoneNumberFormat; +use libphonenumber\PhoneNumberUtil; use Psr\Log\LoggerInterface; readonly class Handler @@ -20,19 +22,42 @@ public function __construct( public function handle(Command $command): void { - $this->logger->info('ContactPerson.ConfirmEmailVerification.start', [ + $phoneNumberUtil = PhoneNumberUtil::getInstance(); + $this->logger->info('ContactPerson.ConfirmPhoneVerification.start', [ 'contactPersonId' => $command->contactPersonId->toRfc4122(), + 'phone' => $phoneNumberUtil->format($command->phone, PhoneNumberFormat::E164), ]); /** @var null|AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); - $contactPerson->markMobilePhoneAsVerified(); + + $actualPhone = $contactPerson->getMobilePhone(); + $expectedE164 = $phoneNumberUtil->format($command->phone, PhoneNumberFormat::E164); + $actualE164 = null !== $actualPhone ? $phoneNumberUtil->format($actualPhone, PhoneNumberFormat::E164) : null; + + if ($expectedE164 !== $actualE164) { + $this->logger->warning('ContactPerson.ConfirmPhoneVerification.phoneMismatch', [ + 'contactPersonId' => $command->contactPersonId->toRfc4122(), + 'actualPhone' => $actualE164, + 'expectedPhone' => $expectedE164, + ]); + + throw new \InvalidArgumentException(sprintf( + 'Phone mismatch for contact person %s: actual="%s", expected="%s"', + $command->contactPersonId->toRfc4122(), + $actualE164, + $expectedE164 + )); + } + + $contactPerson->markMobilePhoneAsVerified($command->phoneVerifiedAt); $this->contactPersonRepository->save($contactPerson); $this->flusher->flush($contactPerson); - $this->logger->info('ContactPerson.ConfirmEmailVerification.finish', [ + $this->logger->info('ContactPerson.ConfirmPhoneVerification.finish', [ 'contactPersonId' => $contactPerson->getId()->toRfc4122(), + 'mobilePhoneVerifiedAt' => $contactPerson->getMobilePhoneVerifiedAt()?->toIso8601String(), ]); } } diff --git a/src/ContactPersons/UseCase/Unlink/Handler.php b/src/ContactPersons/UseCase/Unlink/Handler.php index 1d05848..8d3f77a 100644 --- a/src/ContactPersons/UseCase/Unlink/Handler.php +++ b/src/ContactPersons/UseCase/Unlink/Handler.php @@ -35,17 +35,15 @@ public function handle(Command $command): void $entitiesToFlush = []; if ($contactPerson->isPartner()) { - if ($applicationInstallation->getBitrix24PartnerContactPersonId() !== null){ + if (null !== $applicationInstallation->getBitrix24PartnerContactPersonId()) { $applicationInstallation->unlinkBitrix24PartnerContactPerson(); $this->applicationInstallationRepository->save($applicationInstallation); $entitiesToFlush[] = $applicationInstallation; } - }else{ - if ($applicationInstallation->getContactPersonId() !== null){ - $applicationInstallation->unlinkContactPerson(); - $this->applicationInstallationRepository->save($applicationInstallation); - $entitiesToFlush[] = $applicationInstallation; - } + } elseif (null !== $applicationInstallation->getContactPersonId()) { + $applicationInstallation->unlinkContactPerson(); + $this->applicationInstallationRepository->save($applicationInstallation); + $entitiesToFlush[] = $applicationInstallation; } $contactPerson->markAsDeleted($command->comment); diff --git a/tests/Functional/ApplicationInstallations/Builders/ApplicationInstallationBuilder.php b/tests/Functional/ApplicationInstallations/Builders/ApplicationInstallationBuilder.php index 05e087a..b36f97a 100644 --- a/tests/Functional/ApplicationInstallations/Builders/ApplicationInstallationBuilder.php +++ b/tests/Functional/ApplicationInstallations/Builders/ApplicationInstallationBuilder.php @@ -60,9 +60,9 @@ public function withApplicationToken(string $applicationToken): self return $this; } - public function withBitrix24PartnerId(?Uuid $bitrix24PartnerId): self + public function withBitrix24PartnerId(?Uuid $uuid): self { - $this->bitrix24PartnerId = $bitrix24PartnerId; + $this->bitrix24PartnerId = $uuid; return $this; } diff --git a/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php index 3c2105e..0e10584 100644 --- a/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php @@ -166,7 +166,7 @@ public function testInstallPartnerContactPersonSuccess(): void $applicationToken = Uuid::v7()->toRfc4122(); $memberId = Uuid::v4()->toRfc4122(); $externalId = Uuid::v7()->toRfc4122(); - $bitrix24PartnerId = Uuid::v7(); + $uuidV7 = Uuid::v7(); $bitrix24Account = (new Bitrix24AccountBuilder()) ->withApplicationScope(new Scope(['crm'])) @@ -187,7 +187,7 @@ public function testInstallPartnerContactPersonSuccess(): void ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) ->withApplicationToken($applicationToken) ->withContactPersonId(null) - ->withBitrix24PartnerId($bitrix24PartnerId) + ->withBitrix24PartnerId($uuidV7) ->withExternalId($externalId) ->build(); diff --git a/tests/Functional/ContactPersons/UseCase/MarkPhoneAsVerified/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/MarkPhoneAsVerified/HandlerTest.php index 5964d5d..28ad9c0 100644 --- a/tests/Functional/ContactPersons/UseCase/MarkPhoneAsVerified/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/MarkPhoneAsVerified/HandlerTest.php @@ -66,10 +66,11 @@ public function testConfirmPhoneVerification(): void $contactPersonBuilder = new ContactPersonBuilder(); $externalId = Uuid::v7()->toRfc4122(); $bitrix24UserId = random_int(1, 1_000_000); + $phoneNumber = $this->createPhoneNumber('+79991234567'); $contactPerson = $contactPersonBuilder ->withEmail('john.doe@example.com') - ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) + ->withMobilePhoneNumber($phoneNumber) ->withComment('Test comment') ->withExternalId($externalId) ->withBitrix24UserId($bitrix24UserId) @@ -81,9 +82,7 @@ public function testConfirmPhoneVerification(): void $this->assertFalse($contactPerson->isMobilePhoneVerified()); - $this->handler->handle( - new Command($contactPerson->getId()) - ); + $this->handler->handle(new Command($contactPerson->getId(), $phoneNumber)); $updatedContactPerson = $this->repository->getById($contactPerson->getId()); $this->assertTrue($updatedContactPerson->isMobilePhoneVerified()); @@ -111,7 +110,33 @@ public function testConfirmPhoneVerificationFailsIfContactPersonNotFound(): void $this->assertFalse($contactPerson->isMobilePhoneVerified()); $this->expectException(ContactPersonNotFoundException::class); - $this->handler->handle(new Command(Uuid::v7())); + $this->handler->handle(new Command(Uuid::v7(), $this->createPhoneNumber('+79991234567'))); + } + + #[Test] + public function testConfirmPhoneVerificationFailsOnPhoneMismatch(): void + { + $contactPersonBuilder = new ContactPersonBuilder(); + $externalId = Uuid::v7()->toRfc4122(); + $bitrix24UserId = random_int(1, 1_000_000); + + $phoneNumber = $this->createPhoneNumber('+79991234567'); + $expectedDifferentPhone = $this->createPhoneNumber('+79990000000'); + + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($phoneNumber) + ->withComment('Test comment') + ->withExternalId($externalId) + ->withBitrix24UserId($bitrix24UserId) + ->withBitrix24PartnerId(Uuid::v7()) + ->build(); + + $this->repository->save($contactPerson); + $this->flusher->flush(); + + $this->expectException(\InvalidArgumentException::class); + $this->handler->handle(new Command($contactPerson->getId(), $expectedDifferentPhone)); } private function createPhoneNumber(string $number): PhoneNumber diff --git a/tests/Functional/ContactPersons/UseCase/Unlink/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/Unlink/HandlerTest.php index 44eee1e..521d930 100644 --- a/tests/Functional/ContactPersons/UseCase/Unlink/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/Unlink/HandlerTest.php @@ -45,6 +45,7 @@ use libphonenumber\PhoneNumberUtil; use libphonenumber\PhoneNumber; use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; +use Doctrine\DBAL\Schema\Name\OptionallyQualifiedName; /** * @internal @@ -67,6 +68,7 @@ class HandlerTest extends TestCase #[\Override] protected function setUp(): void { + $this->truncateAllTables(); $entityManager = EntityManagerFactory::get(); $this->eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); $this->repository = new ContactPersonRepository($entityManager); @@ -330,4 +332,37 @@ private function createPhoneNumber(string $number): PhoneNumber $phoneNumberUtil = PhoneNumberUtil::getInstance(); return $phoneNumberUtil->parse($number, 'RU'); } + + private function truncateAllTables(): void + { + $entityManager = EntityManagerFactory::get(); + $connection = $entityManager->getConnection(); + $schemaManager = $connection->createSchemaManager(); + + $names = $schemaManager->introspectTableNames(); + + if ($names === []) { + return; + } + + $quotedTables = []; + + foreach ($names as $name) { + $tableName = $name->toString(); + $quotedTables[] = $tableName; + } + + $sql = 'TRUNCATE ' . implode(', ', $quotedTables) . ' RESTART IDENTITY CASCADE'; + + $connection->beginTransaction(); + try { + $connection->executeStatement($sql); + $connection->commit(); + } catch (\Throwable $throwable) { + $connection->rollBack(); + throw $throwable; + } + + $entityManager->clear(); + } } From 42311c14b9041bc2e5a0c507c593c371d6db5d4a Mon Sep 17 00:00:00 2001 From: kirill Date: Sun, 4 Jan 2026 12:15:50 +0300 Subject: [PATCH 16/31] Remove redundant phone number validation, streamline `ContactPerson` retrieval logic, and update dependencies --- composer.json | 2 +- src/ContactPersons/Entity/ContactPerson.php | 1 + .../UseCase/UpdateData/Command.php | 16 ---------------- .../UseCase/UpdateData/Handler.php | 4 ---- .../UseCase/UpdateData/HandlerTest.php | 4 ++-- 5 files changed, 4 insertions(+), 23 deletions(-) diff --git a/composer.json b/composer.json index 80eb880..b38fce0 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "darsyn/ip-doctrine": "^6", "nesbot/carbon": "^3", "moneyphp/money": "^4", - "bitrix24/b24phpsdk": "dev-dev", + "bitrix24/b24phpsdk": "dev-v3-dev", "doctrine/orm": "^3", "doctrine/doctrine-bundle": "*", "doctrine/doctrine-migrations-bundle": "*", diff --git a/src/ContactPersons/Entity/ContactPerson.php b/src/ContactPersons/Entity/ContactPerson.php index 693d023..fb62ed5 100644 --- a/src/ContactPersons/Entity/ContactPerson.php +++ b/src/ContactPersons/Entity/ContactPerson.php @@ -182,6 +182,7 @@ public function markEmailAsVerified(?CarbonImmutable $verifiedAt = null): void ); } + #[\Override] public function isPartner(): bool { return $this->getBitrix24PartnerId() instanceof Uuid; diff --git a/src/ContactPersons/UseCase/UpdateData/Command.php b/src/ContactPersons/UseCase/UpdateData/Command.php index 75e216a..7c49975 100644 --- a/src/ContactPersons/UseCase/UpdateData/Command.php +++ b/src/ContactPersons/UseCase/UpdateData/Command.php @@ -7,8 +7,6 @@ use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\FullName; use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; use libphonenumber\PhoneNumber; -use libphonenumber\PhoneNumberType; -use libphonenumber\PhoneNumberUtil; use Symfony\Component\Uid\Uuid; readonly class Command @@ -37,19 +35,5 @@ private function validate(): void if (null !== $this->externalId && '' === trim($this->externalId)) { throw new InvalidArgumentException('External ID cannot be empty if provided.'); } - - if ($this->mobilePhoneNumber instanceof PhoneNumber) { - $phoneUtil = PhoneNumberUtil::getInstance(); - $isValidNumber = $phoneUtil->isValidNumber($this->mobilePhoneNumber); - $numberType = $phoneUtil->getNumberType($this->mobilePhoneNumber); - - if (!$isValidNumber) { - throw new InvalidArgumentException('Invalid phone number.'); - } - - if (PhoneNumberType::MOBILE !== $numberType) { - throw new InvalidArgumentException('Phone number must be mobile.'); - } - } } } diff --git a/src/ContactPersons/UseCase/UpdateData/Handler.php b/src/ContactPersons/UseCase/UpdateData/Handler.php index 27e7057..741c0b5 100644 --- a/src/ContactPersons/UseCase/UpdateData/Handler.php +++ b/src/ContactPersons/UseCase/UpdateData/Handler.php @@ -9,7 +9,6 @@ use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\FullName; use Bitrix24\SDK\Application\Contracts\ContactPersons\Repository\ContactPersonRepositoryInterface; use Bitrix24\SDK\Application\Contracts\Events\AggregateRootEventsEmitterInterface; -use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; use libphonenumber\PhoneNumber; use Psr\Log\LoggerInterface; use Symfony\Component\Uid\Uuid; @@ -35,9 +34,6 @@ public function handle(Command $command): void /** @var AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); - if (!$contactPerson) { - throw new InvalidArgumentException('Contact person not found.'); - } if ($command->fullName instanceof FullName) { $contactPerson->changeFullName($command->fullName); diff --git a/tests/Functional/ContactPersons/UseCase/UpdateData/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/UpdateData/HandlerTest.php index 516e515..e40f94d 100644 --- a/tests/Functional/ContactPersons/UseCase/UpdateData/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/UpdateData/HandlerTest.php @@ -121,8 +121,8 @@ public function testUpdateExistingContactPerson(): void $this->assertEquals('Jane Doe', $updatedContactPerson->getFullName()->name); $this->assertEquals('jane.doe@example.com', $updatedContactPerson->getEmail()); $this->assertEquals('+79997654321', $formattedPhone); - $this->assertEquals($contactPerson->getExternalId(), $updatedContactPerson->getExternalId()); - $this->assertEquals($contactPerson->getBitrix24PartnerId(), $updatedContactPerson->getBitrix24PartnerId()); + $this->assertEquals($externalId, $updatedContactPerson->getExternalId()); + $this->assertEquals($uuidV7, $updatedContactPerson->getBitrix24PartnerId()); } private function createPhoneNumber(string $number): PhoneNumber From 7b2551e351ed45e378fa786d0ec7fd91bc0c1d38 Mon Sep 17 00:00:00 2001 From: kirill Date: Wed, 7 Jan 2026 13:42:49 +0300 Subject: [PATCH 17/31] Enhance phone and email verification handling, streamline validation, and update tests for consistency. --- .../Entity/ApplicationInstallation.php | 15 ++-- src/ContactPersons/Entity/ContactPerson.php | 26 +++--- .../UseCase/MarkEmailAsVerified/Handler.php | 45 ++++++---- .../MarkMobilePhoneAsVerified/Command.php | 22 +++++ .../MarkMobilePhoneAsVerified/Handler.php | 85 +++++++++++++++++++ .../UseCase/MarkPhoneAsVerified/Command.php | 36 -------- .../UseCase/MarkPhoneAsVerified/Handler.php | 63 -------------- src/ContactPersons/UseCase/Unlink/Handler.php | 2 +- .../MarkEmailAsVerified/HandlerTest.php | 13 ++- .../HandlerTest.php | 14 ++- .../UseCase/Unlink/HandlerTest.php | 1 + 11 files changed, 175 insertions(+), 147 deletions(-) create mode 100644 src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Command.php create mode 100644 src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php delete mode 100644 src/ContactPersons/UseCase/MarkPhoneAsVerified/Command.php delete mode 100644 src/ContactPersons/UseCase/MarkPhoneAsVerified/Handler.php rename tests/Functional/ContactPersons/UseCase/{MarkPhoneAsVerified => MarkMobilePhoneAsVerified}/HandlerTest.php (90%) diff --git a/src/ApplicationInstallations/Entity/ApplicationInstallation.php b/src/ApplicationInstallations/Entity/ApplicationInstallation.php index c1c0d93..141fbeb 100644 --- a/src/ApplicationInstallations/Entity/ApplicationInstallation.php +++ b/src/ApplicationInstallations/Entity/ApplicationInstallation.php @@ -347,12 +347,10 @@ public function linkContactPerson(Uuid $uuid): void #[\Override] public function unlinkContactPerson(): void { - if (!$this->contactPersonId instanceof Uuid) { + if (null === $this->contactPersonId) { return; } - $this->updatedAt = new CarbonImmutable(); - $this->events[] = new Events\ApplicationInstallationContactPersonUnlinkedEvent( $this->id, $this->updatedAt, @@ -360,13 +358,14 @@ public function unlinkContactPerson(): void ); $this->contactPersonId = null; + $this->updatedAt = new CarbonImmutable(); } #[\Override] public function linkBitrix24PartnerContactPerson(Uuid $uuid): void { - $this->updatedAt = new CarbonImmutable(); $this->bitrix24PartnerContactPersonId = $uuid; + $this->updatedAt = new CarbonImmutable(); $this->events[] = new Events\ApplicationInstallationBitrix24PartnerContactPersonLinkedEvent( $this->id, @@ -378,12 +377,10 @@ public function linkBitrix24PartnerContactPerson(Uuid $uuid): void #[\Override] public function unlinkBitrix24PartnerContactPerson(): void { - if (!$this->bitrix24PartnerContactPersonId instanceof Uuid) { + if (null === $this->bitrix24PartnerContactPersonId) { return; } - $this->updatedAt = new CarbonImmutable(); - $this->events[] = new Events\ApplicationInstallationBitrix24PartnerContactPersonUnlinkedEvent( $this->id, $this->updatedAt, @@ -391,13 +388,15 @@ public function unlinkBitrix24PartnerContactPerson(): void ); $this->bitrix24PartnerContactPersonId = null; + $this->updatedAt = new CarbonImmutable(); + } #[\Override] public function linkBitrix24Partner(Uuid $uuid): void { - $this->updatedAt = new CarbonImmutable(); $this->bitrix24PartnerId = $uuid; + $this->updatedAt = new CarbonImmutable(); $this->events[] = new Events\ApplicationInstallationBitrix24PartnerLinkedEvent( $this->id, diff --git a/src/ContactPersons/Entity/ContactPerson.php b/src/ContactPersons/Entity/ContactPerson.php index fb62ed5..7bba5ec 100644 --- a/src/ContactPersons/Entity/ContactPerson.php +++ b/src/ContactPersons/Entity/ContactPerson.php @@ -21,8 +21,6 @@ use Bitrix24\SDK\Core\Exceptions\LogicException; use Carbon\CarbonImmutable; use libphonenumber\PhoneNumber; -use libphonenumber\PhoneNumberType; -use libphonenumber\PhoneNumberUtil; use Symfony\Component\Uid\Uuid; class ContactPerson extends AggregateRoot implements ContactPersonInterface @@ -194,23 +192,19 @@ public function getEmailVerifiedAt(): ?CarbonImmutable return $this->emailVerifiedAt; } + /** + * Changes the contact's mobile phone number. + * + * Note: This method does not validate the phone number. + * Make sure to use it through the appropriate use case, + * where validation is performed. + * + * If you use this method outside a use case, + * ensure that you pass a valid mobile phone number. + */ #[\Override] public function changeMobilePhone(?PhoneNumber $phoneNumber): void { - if ($phoneNumber instanceof PhoneNumber) { - $phoneUtil = PhoneNumberUtil::getInstance(); - $isValidNumber = $phoneUtil->isValidNumber($phoneNumber); - - if (!$isValidNumber) { - throw new InvalidArgumentException('Invalid phone number.'); - } - - $numberType = $phoneUtil->getNumberType($phoneNumber); - if (PhoneNumberType::MOBILE !== $numberType) { - throw new InvalidArgumentException('Phone number must be mobile.'); - } - } - $this->mobilePhoneNumber = $phoneNumber; $this->updatedAt = new CarbonImmutable(); diff --git a/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php index 562e680..cbfc2aa 100644 --- a/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php +++ b/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php @@ -6,6 +6,7 @@ use Bitrix24\Lib\Services\Flusher; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; use Bitrix24\SDK\Application\Contracts\ContactPersons\Repository\ContactPersonRepositoryInterface; use Bitrix24\SDK\Application\Contracts\Events\AggregateRootEventsEmitterInterface; use Psr\Log\LoggerInterface; @@ -20,36 +21,48 @@ public function __construct( public function handle(Command $command): void { - $this->logger->info('ContactPerson.ConfirmEmailVerification.start', [ + $this->logger->info('ContactPerson.MarkEmailVerification.start', [ 'contactPersonId' => $command->contactPersonId->toRfc4122(), 'email' => $command->email, ]); - /** @var null|AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ - $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); + try { + /** @var AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ + $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); + } catch (ContactPersonNotFoundException $e) { + $this->logger->warning('ContactPerson.MarkEmailVerification.contactPersonNotFound', [ + 'contactPersonId' => $command->contactPersonId->toRfc4122(), + ]); + + throw $e; + } $actualEmail = $contactPerson->getEmail(); - if (mb_strtolower((string) $actualEmail) !== mb_strtolower($command->email)) { - $this->logger->warning('ContactPerson.ConfirmEmailVerification.emailMismatch', [ + if (null == $actualEmail) { + $this->logger->warning('ContactPerson.MarkEmailVerification.currentEmailIsNull', [ 'contactPersonId' => $command->contactPersonId->toRfc4122(), - 'actualEmail' => $actualEmail, + 'actualEmail' => null, 'expectedEmail' => $command->email, ]); - throw new \InvalidArgumentException(sprintf( - 'Email mismatch for contact person %s: actual="%s", expected="%s"', - $command->contactPersonId->toRfc4122(), - $actualEmail, - $command->email - )); + return; } - $contactPerson->markEmailAsVerified($command->emailVerifiedAt); + if (mb_strtolower($actualEmail) == mb_strtolower($command->email)) { + + $contactPerson->markEmailAsVerified($command->emailVerifiedAt); + $this->contactPersonRepository->save($contactPerson); + $this->flusher->flush($contactPerson); - $this->contactPersonRepository->save($contactPerson); - $this->flusher->flush($contactPerson); + }else{ + $this->logger->warning('ContactPerson.MarkEmailVerification.emailMismatch', [ + 'contactPersonId' => $command->contactPersonId->toRfc4122(), + 'actualEmail' => $actualEmail, + 'expectedEmail' => $command->email, + ]); + } - $this->logger->info('ContactPerson.ConfirmEmailVerification.finish', [ + $this->logger->info('ContactPerson.MarkEmailVerification.finish', [ 'contactPersonId' => $contactPerson->getId()->toRfc4122(), 'emailVerifiedAt' => $contactPerson->getEmailVerifiedAt()?->toIso8601String(), ]); diff --git a/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Command.php b/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Command.php new file mode 100644 index 0000000..6e339c6 --- /dev/null +++ b/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Command.php @@ -0,0 +1,22 @@ +validate(); + } + + private function validate(): void {} +} diff --git a/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php new file mode 100644 index 0000000..dc6dcbe --- /dev/null +++ b/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php @@ -0,0 +1,85 @@ +phoneNumberUtil->format($command->phone, PhoneNumberFormat::E164); + + $this->logger->info('ContactPerson.MarkMobilePhoneVerification.start', [ + 'contactPersonId' => $command->contactPersonId->toRfc4122(), + 'phone' => $expectedMobilePhoneE164, + ]); + + try { + /** @var AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ + $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); + } catch (ContactPersonNotFoundException $e) { + $this->logger->warning('ContactPerson.MarkMobilePhoneVerification.contactPersonNotFound', [ + 'contactPersonId' => $command->contactPersonId->toRfc4122(), + ]); + + throw $e; + } + + $actualPhone = $contactPerson->getMobilePhone(); + if (null == $actualPhone) { + $this->logger->warning('ContactPerson.MarkMobilePhoneVerification.currentPhoneIsNull', [ + 'contactPersonId' => $command->contactPersonId->toRfc4122(), + 'actualPhone' => null, + 'expectedPhone' => $expectedMobilePhoneE164, + ]); + + return; + } + + if ($command->phone->equals($actualPhone)) { + $contactPerson->markMobilePhoneAsVerified($command->phoneVerifiedAt); + + $this->contactPersonRepository->save($contactPerson); + $this->flusher->flush($contactPerson); + } else { + // Format the current mobile phone number to the international E.164 format + $actualMobilePhoneE164 = $this->phoneNumberUtil->format($actualPhone, PhoneNumberFormat::E164); + + $this->logger->warning('ContactPerson.MarkMobilePhoneVerification.phoneMismatch', [ + 'contactPersonId' => $command->contactPersonId->toRfc4122(), + 'actualPhone' => $actualMobilePhoneE164, + 'expectedPhone' => $expectedMobilePhoneE164, + ]); + // Do not throw here — just log mismatch and finish without changes + $this->logger->info('ContactPerson.MarkMobilePhoneVerification.finish', [ + 'contactPersonId' => $contactPerson->getId()->toRfc4122(), + 'mobilePhoneVerifiedAt' => $contactPerson->getMobilePhoneVerifiedAt()?->toIso8601String(), + ]); + + return; + } + + $this->logger->info('ContactPerson.MarkMobilePhoneVerification.finish', [ + 'contactPersonId' => $contactPerson->getId()->toRfc4122(), + 'mobilePhoneVerifiedAt' => $contactPerson->getMobilePhoneVerifiedAt()?->toIso8601String(), + ]); + } +} diff --git a/src/ContactPersons/UseCase/MarkPhoneAsVerified/Command.php b/src/ContactPersons/UseCase/MarkPhoneAsVerified/Command.php deleted file mode 100644 index 68f60ee..0000000 --- a/src/ContactPersons/UseCase/MarkPhoneAsVerified/Command.php +++ /dev/null @@ -1,36 +0,0 @@ -validate(); - } - - private function validate(): void - { - $phoneNumberUtil = PhoneNumberUtil::getInstance(); - $isValidNumber = $phoneNumberUtil->isValidNumber($this->phone); - if (!$isValidNumber) { - throw new \InvalidArgumentException('Invalid phone number.'); - } - - $numberType = $phoneNumberUtil->getNumberType($this->phone); - if (PhoneNumberType::MOBILE !== $numberType) { - throw new \InvalidArgumentException('Phone number must be mobile.'); - } - } -} diff --git a/src/ContactPersons/UseCase/MarkPhoneAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkPhoneAsVerified/Handler.php deleted file mode 100644 index 2ac9a80..0000000 --- a/src/ContactPersons/UseCase/MarkPhoneAsVerified/Handler.php +++ /dev/null @@ -1,63 +0,0 @@ -logger->info('ContactPerson.ConfirmPhoneVerification.start', [ - 'contactPersonId' => $command->contactPersonId->toRfc4122(), - 'phone' => $phoneNumberUtil->format($command->phone, PhoneNumberFormat::E164), - ]); - - /** @var null|AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ - $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); - - $actualPhone = $contactPerson->getMobilePhone(); - $expectedE164 = $phoneNumberUtil->format($command->phone, PhoneNumberFormat::E164); - $actualE164 = null !== $actualPhone ? $phoneNumberUtil->format($actualPhone, PhoneNumberFormat::E164) : null; - - if ($expectedE164 !== $actualE164) { - $this->logger->warning('ContactPerson.ConfirmPhoneVerification.phoneMismatch', [ - 'contactPersonId' => $command->contactPersonId->toRfc4122(), - 'actualPhone' => $actualE164, - 'expectedPhone' => $expectedE164, - ]); - - throw new \InvalidArgumentException(sprintf( - 'Phone mismatch for contact person %s: actual="%s", expected="%s"', - $command->contactPersonId->toRfc4122(), - $actualE164, - $expectedE164 - )); - } - - $contactPerson->markMobilePhoneAsVerified($command->phoneVerifiedAt); - - $this->contactPersonRepository->save($contactPerson); - $this->flusher->flush($contactPerson); - - $this->logger->info('ContactPerson.ConfirmPhoneVerification.finish', [ - 'contactPersonId' => $contactPerson->getId()->toRfc4122(), - 'mobilePhoneVerifiedAt' => $contactPerson->getMobilePhoneVerifiedAt()?->toIso8601String(), - ]); - } -} diff --git a/src/ContactPersons/UseCase/Unlink/Handler.php b/src/ContactPersons/UseCase/Unlink/Handler.php index 8d3f77a..61730b9 100644 --- a/src/ContactPersons/UseCase/Unlink/Handler.php +++ b/src/ContactPersons/UseCase/Unlink/Handler.php @@ -50,7 +50,7 @@ public function handle(Command $command): void $this->contactPersonRepository->save($contactPerson); $entitiesToFlush[] = $contactPerson; - $this->flusher->flush(...array_filter($entitiesToFlush, fn ($entity): bool => $entity instanceof AggregateRootEventsEmitterInterface)); + $this->flusher->flush(...$entitiesToFlush); $this->logger->info('ContactPerson.UninstallContactPerson.finish', [ 'contact_person_id' => $command->contactPersonId, diff --git a/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php index 2c50403..46001f4 100644 --- a/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php @@ -138,10 +138,14 @@ public function testConfirmEmailVerification_Fails_IfEmailMismatch(): void $this->repository->save($contactPerson); $this->flusher->flush(); - $this->expectException(InvalidArgumentException::class); + // Больше не бросаем исключение при несовпадении email — только лог и без изменений $this->handler->handle( new Command($contactPerson->getId(), 'another.email@example.com') ); + + // Проверяем, что верификация не произошла + $reloaded = $this->repository->getById($contactPerson->getId()); + $this->assertFalse($reloaded->isEmailVerified()); } #[Test] @@ -163,11 +167,14 @@ public function testConfirmEmailVerification_Fails_IfEntityHasNoEmailButCommandP $this->repository->save($contactPerson); $this->flusher->flush(); - // В обработчик передаём email — ожидаем исключение о несоответствии - $this->expectException(InvalidArgumentException::class); + // В обработчик передаём email — теперь только лог и выход без изменений $this->handler->handle( new Command($contactPerson->getId(), 'john.doe@example.com') ); + + // Проверяем, что верификация не произошла + $reloaded = $this->repository->getById($contactPerson->getId()); + $this->assertFalse($reloaded->isEmailVerified()); } #[Test] diff --git a/tests/Functional/ContactPersons/UseCase/MarkPhoneAsVerified/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php similarity index 90% rename from tests/Functional/ContactPersons/UseCase/MarkPhoneAsVerified/HandlerTest.php rename to tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php index 28ad9c0..7fa5466 100644 --- a/tests/Functional/ContactPersons/UseCase/MarkPhoneAsVerified/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\MarkPhoneAsVerified; +namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\MarkMobilePhoneAsVerified; -use Bitrix24\Lib\ContactPersons\UseCase\MarkPhoneAsVerified\Handler; -use Bitrix24\Lib\ContactPersons\UseCase\MarkPhoneAsVerified\Command; +use Bitrix24\Lib\ContactPersons\UseCase\MarkMobilePhoneAsVerified\Handler; +use Bitrix24\Lib\ContactPersons\UseCase\MarkMobilePhoneAsVerified\Command; use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; use Bitrix24\Lib\Services\Flusher; use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; @@ -52,9 +52,11 @@ protected function setUp(): void $entityManager = EntityManagerFactory::get(); $this->eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); $this->repository = new ContactPersonRepository($entityManager); + $this->phoneNumberUtil = PhoneNumberUtil::getInstance(); $this->flusher = new Flusher($entityManager, $this->eventDispatcher); $this->handler = new Handler( $this->repository, + $this->phoneNumberUtil, $this->flusher, new NullLogger() ); @@ -135,8 +137,12 @@ public function testConfirmPhoneVerificationFailsOnPhoneMismatch(): void $this->repository->save($contactPerson); $this->flusher->flush(); - $this->expectException(\InvalidArgumentException::class); + // No exception should be thrown; phone mismatch is only logged $this->handler->handle(new Command($contactPerson->getId(), $expectedDifferentPhone)); + + // Ensure mobile phone is still not verified + $reloaded = $this->repository->getById($contactPerson->getId()); + $this->assertFalse($reloaded->isMobilePhoneVerified()); } private function createPhoneNumber(string $number): PhoneNumber diff --git a/tests/Functional/ContactPersons/UseCase/Unlink/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/Unlink/HandlerTest.php index 521d930..706f40a 100644 --- a/tests/Functional/ContactPersons/UseCase/Unlink/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/Unlink/HandlerTest.php @@ -134,6 +134,7 @@ public function testUninstallContactPersonSuccess(): void $this->applicationInstallationRepository->save($applicationInstallation); $this->flusher->flush(); + var_dump($contactPerson->getId()); // Запуск use-case $this->handler->handle( new Command( From 4b585e22d1bb956ba52aba90b850dcd1f0b86f35 Mon Sep 17 00:00:00 2001 From: kirill Date: Sat, 10 Jan 2026 00:23:07 +0300 Subject: [PATCH 18/31] Refactor namespaces for consistency, enhance mobile number validation, streamline contact person operations, and update tests accordingly. --- .../Entity/ApplicationInstallation.php | 5 +- .../UseCase/InstallContactPerson}/Command.php | 2 +- .../UseCase/InstallContactPerson}/Handler.php | 30 +++++- .../UseCase/UnlinkContactPerson}/Command.php | 2 +- .../UseCase/UnlinkContactPerson}/Handler.php | 2 +- .../UseCase/ChangeProfile/Command.php | 29 ++++++ .../UseCase/ChangeProfile/Handler.php | 91 +++++++++++++++++++ .../UseCase/MarkEmailAsVerified/Handler.php | 10 +- .../MarkMobilePhoneAsVerified/Handler.php | 28 +++++- .../UseCase/UpdateData/Command.php | 39 -------- .../UseCase/UpdateData/Handler.php | 72 --------------- .../InstallContactPerson}/HandlerTest.php | 27 +++--- .../UnlinkContactPerson}/HandlerTest.php | 18 ++-- .../HandlerTest.php | 14 ++- .../MarkMobilePhoneAsVerified/HandlerTest.php | 4 + 15 files changed, 219 insertions(+), 154 deletions(-) rename src/{ContactPersons/UseCase/Install => ApplicationInstallations/UseCase/InstallContactPerson}/Command.php (94%) rename src/{ContactPersons/UseCase/Install => ApplicationInstallations/UseCase/InstallContactPerson}/Handler.php (67%) rename src/{ContactPersons/UseCase/Unlink => ApplicationInstallations/UseCase/UnlinkContactPerson}/Command.php (82%) rename src/{ContactPersons/UseCase/Unlink => ApplicationInstallations/UseCase/UnlinkContactPerson}/Handler.php (97%) create mode 100644 src/ContactPersons/UseCase/ChangeProfile/Command.php create mode 100644 src/ContactPersons/UseCase/ChangeProfile/Handler.php delete mode 100644 src/ContactPersons/UseCase/UpdateData/Command.php delete mode 100644 src/ContactPersons/UseCase/UpdateData/Handler.php rename tests/Functional/{ContactPersons/UseCase/Install => ApplicationInstallations/UseCase/InstallContactPerson}/HandlerTest.php (95%) rename tests/Functional/{ContactPersons/UseCase/Unlink => ApplicationInstallations/UseCase/UnlinkContactPerson}/HandlerTest.php (97%) rename tests/Functional/ContactPersons/UseCase/{UpdateData => ChangeProfile}/HandlerTest.php (93%) diff --git a/src/ApplicationInstallations/Entity/ApplicationInstallation.php b/src/ApplicationInstallations/Entity/ApplicationInstallation.php index 141fbeb..6ace1ce 100644 --- a/src/ApplicationInstallations/Entity/ApplicationInstallation.php +++ b/src/ApplicationInstallations/Entity/ApplicationInstallation.php @@ -347,7 +347,7 @@ public function linkContactPerson(Uuid $uuid): void #[\Override] public function unlinkContactPerson(): void { - if (null === $this->contactPersonId) { + if (!$this->contactPersonId instanceof Uuid) { return; } @@ -377,7 +377,7 @@ public function linkBitrix24PartnerContactPerson(Uuid $uuid): void #[\Override] public function unlinkBitrix24PartnerContactPerson(): void { - if (null === $this->bitrix24PartnerContactPersonId) { + if (!$this->bitrix24PartnerContactPersonId instanceof Uuid) { return; } @@ -389,7 +389,6 @@ public function unlinkBitrix24PartnerContactPerson(): void $this->bitrix24PartnerContactPersonId = null; $this->updatedAt = new CarbonImmutable(); - } #[\Override] diff --git a/src/ContactPersons/UseCase/Install/Command.php b/src/ApplicationInstallations/UseCase/InstallContactPerson/Command.php similarity index 94% rename from src/ContactPersons/UseCase/Install/Command.php rename to src/ApplicationInstallations/UseCase/InstallContactPerson/Command.php index 21e8db0..d3b09b0 100644 --- a/src/ContactPersons/UseCase/Install/Command.php +++ b/src/ApplicationInstallations/UseCase/InstallContactPerson/Command.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Bitrix24\Lib\ContactPersons\UseCase\Install; +namespace Bitrix24\Lib\ApplicationInstallations\UseCase\InstallContactPerson; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\FullName; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\UserAgentInfo; diff --git a/src/ContactPersons/UseCase/Install/Handler.php b/src/ApplicationInstallations/UseCase/InstallContactPerson/Handler.php similarity index 67% rename from src/ContactPersons/UseCase/Install/Handler.php rename to src/ApplicationInstallations/UseCase/InstallContactPerson/Handler.php index 205eae1..74139c3 100644 --- a/src/ContactPersons/UseCase/Install/Handler.php +++ b/src/ApplicationInstallations/UseCase/InstallContactPerson/Handler.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Bitrix24\Lib\ContactPersons\UseCase\Install; +namespace Bitrix24\Lib\ApplicationInstallations\UseCase\InstallContactPerson; use Bitrix24\Lib\ContactPersons\Entity\ContactPerson; use Bitrix24\Lib\Services\Flusher; @@ -11,6 +11,10 @@ use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; use Bitrix24\SDK\Application\Contracts\ContactPersons\Repository\ContactPersonRepositoryInterface; use Bitrix24\SDK\Application\Contracts\Events\AggregateRootEventsEmitterInterface; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use libphonenumber\PhoneNumber; +use libphonenumber\PhoneNumberType; +use libphonenumber\PhoneNumberUtil; use Psr\Log\LoggerInterface; use Symfony\Component\Uid\Uuid; @@ -19,6 +23,7 @@ public function __construct( private ApplicationInstallationRepositoryInterface $applicationInstallationRepository, private ContactPersonRepositoryInterface $contactPersonRepository, + private PhoneNumberUtil $phoneNumberUtil, private Flusher $flusher, private LoggerInterface $logger ) {} @@ -31,6 +36,10 @@ public function handle(Command $command): void 'bitrix24PartnerId' => $command->bitrix24PartnerId?->toRfc4122() ?? '', ]); + if ($command->mobilePhoneNumber instanceof \libphonenumber\PhoneNumber) { + $this->guardMobilePhoneNumber($command->mobilePhoneNumber); + } + /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $applicationInstallation */ $applicationInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); @@ -68,4 +77,23 @@ public function handle(Command $command): void 'contact_person_id' => $uuidV7->toRfc4122(), ]); } + + private function guardMobilePhoneNumber(PhoneNumber $mobilePhoneNumber): void + { + if (!$this->phoneNumberUtil->isValidNumber($mobilePhoneNumber)) { + $this->logger->warning('ContactPerson.InstallContactPerson.InvalidMobilePhoneNumber', [ + 'mobilePhoneNumber' => (string) $mobilePhoneNumber, + ]); + + throw new InvalidArgumentException('Invalid mobile phone number.'); + } + + if (PhoneNumberType::MOBILE !== $this->phoneNumberUtil->getNumberType($mobilePhoneNumber)) { + $this->logger->warning('ContactPerson.InstallContactPerson.MobilePhoneNumberMustBeMobile', [ + 'mobilePhoneNumber' => (string) $mobilePhoneNumber, + ]); + + throw new InvalidArgumentException('Phone number must be mobile.'); + } + } } diff --git a/src/ContactPersons/UseCase/Unlink/Command.php b/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Command.php similarity index 82% rename from src/ContactPersons/UseCase/Unlink/Command.php rename to src/ApplicationInstallations/UseCase/UnlinkContactPerson/Command.php index c1fa64a..c90201c 100644 --- a/src/ContactPersons/UseCase/Unlink/Command.php +++ b/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Command.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Bitrix24\Lib\ContactPersons\UseCase\Unlink; +namespace Bitrix24\Lib\ApplicationInstallations\UseCase\UnlinkContactPerson; use Symfony\Component\Uid\Uuid; diff --git a/src/ContactPersons/UseCase/Unlink/Handler.php b/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Handler.php similarity index 97% rename from src/ContactPersons/UseCase/Unlink/Handler.php rename to src/ApplicationInstallations/UseCase/UnlinkContactPerson/Handler.php index 61730b9..9b962ec 100644 --- a/src/ContactPersons/UseCase/Unlink/Handler.php +++ b/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Handler.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Bitrix24\Lib\ContactPersons\UseCase\Unlink; +namespace Bitrix24\Lib\ApplicationInstallations\UseCase\UnlinkContactPerson; use Bitrix24\Lib\Services\Flusher; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationInterface; diff --git a/src/ContactPersons/UseCase/ChangeProfile/Command.php b/src/ContactPersons/UseCase/ChangeProfile/Command.php new file mode 100644 index 0000000..123f34e --- /dev/null +++ b/src/ContactPersons/UseCase/ChangeProfile/Command.php @@ -0,0 +1,29 @@ +validate(); + } + + private function validate(): void + { + if ('' === trim($this->email) && !filter_var($this->email, FILTER_VALIDATE_EMAIL)) { + throw new InvalidArgumentException('Invalid email format.'); + } + } +} diff --git a/src/ContactPersons/UseCase/ChangeProfile/Handler.php b/src/ContactPersons/UseCase/ChangeProfile/Handler.php new file mode 100644 index 0000000..d65d75c --- /dev/null +++ b/src/ContactPersons/UseCase/ChangeProfile/Handler.php @@ -0,0 +1,91 @@ +logger->info('ContactPerson.ChangeProfile.start', [ + 'contactPersonId' => $command->contactPersonId, + 'fullName' => (string) $command->fullName, + 'email' => $command->email, + 'mobilePhoneNumber' => (string) $command->mobilePhoneNumber, + ]); + + try { + /** @var AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ + $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); + } catch (ContactPersonNotFoundException $contactPersonNotFoundException) { + $this->logger->warning('ContactPerson.ChangeProfile.contactPersonNotFound', [ + 'contactPersonId' => $command->contactPersonId->toRfc4122(), + ]); + + throw $contactPersonNotFoundException; + } + + if (!$command->fullName->equal($contactPerson->getFullName())) { + $contactPerson->changeFullName($command->fullName); + } + + if ($command->email !== $contactPerson->getEmail()) { + $contactPerson->changeEmail($command->email); + } + + $this->guardMobilePhoneNumber($command->mobilePhoneNumber); + if (!$command->mobilePhoneNumber->equals($contactPerson->getMobilePhone())) { + $contactPerson->changeMobilePhone($command->mobilePhoneNumber); + } + + $this->contactPersonRepository->save($contactPerson); + $this->flusher->flush($contactPerson); + + $this->logger->info('ContactPerson.ChangeProfile.finish', [ + 'contactPersonId' => $contactPerson->getId()->toRfc4122(), + 'updatedFields' => [ + 'fullName' => (string) $command->fullName, + 'email' => $command->email, + 'mobilePhoneNumber' => (string) $command->mobilePhoneNumber, + ], + ]); + } + + private function guardMobilePhoneNumber(PhoneNumber $mobilePhoneNumber): void + { + if (!$this->phoneNumberUtil->isValidNumber($mobilePhoneNumber)) { + $this->logger->warning('ContactPerson.ChangeProfile.InvalidMobilePhoneNumber', [ + 'mobilePhoneNumber' => (string) $mobilePhoneNumber, + ]); + + throw new InvalidArgumentException('Invalid mobile phone number.'); + } + + if (PhoneNumberType::MOBILE !== $this->phoneNumberUtil->getNumberType($mobilePhoneNumber)) { + $this->logger->warning('ContactPerson.ChangeProfile.MobilePhoneNumberMustBeMobile', [ + 'mobilePhoneNumber' => (string) $mobilePhoneNumber, + ]); + + throw new InvalidArgumentException('Phone number must be mobile.'); + } + } +} diff --git a/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php index cbfc2aa..a47c56d 100644 --- a/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php +++ b/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php @@ -29,12 +29,12 @@ public function handle(Command $command): void try { /** @var AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); - } catch (ContactPersonNotFoundException $e) { + } catch (ContactPersonNotFoundException $contactPersonNotFoundException) { $this->logger->warning('ContactPerson.MarkEmailVerification.contactPersonNotFound', [ 'contactPersonId' => $command->contactPersonId->toRfc4122(), ]); - throw $e; + throw $contactPersonNotFoundException; } $actualEmail = $contactPerson->getEmail(); @@ -48,13 +48,11 @@ public function handle(Command $command): void return; } - if (mb_strtolower($actualEmail) == mb_strtolower($command->email)) { - + if (mb_strtolower($actualEmail) === mb_strtolower($command->email)) { $contactPerson->markEmailAsVerified($command->emailVerifiedAt); $this->contactPersonRepository->save($contactPerson); $this->flusher->flush($contactPerson); - - }else{ + } else { $this->logger->warning('ContactPerson.MarkEmailVerification.emailMismatch', [ 'contactPersonId' => $command->contactPersonId->toRfc4122(), 'actualEmail' => $actualEmail, diff --git a/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php index dc6dcbe..9528781 100644 --- a/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php +++ b/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php @@ -9,7 +9,10 @@ use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; use Bitrix24\SDK\Application\Contracts\ContactPersons\Repository\ContactPersonRepositoryInterface; use Bitrix24\SDK\Application\Contracts\Events\AggregateRootEventsEmitterInterface; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumberFormat; +use libphonenumber\PhoneNumberType; use libphonenumber\PhoneNumberUtil; use Psr\Log\LoggerInterface; @@ -32,15 +35,17 @@ public function handle(Command $command): void 'phone' => $expectedMobilePhoneE164, ]); + $this->guardMobilePhoneNumber($command->phone); + try { /** @var AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); - } catch (ContactPersonNotFoundException $e) { + } catch (ContactPersonNotFoundException $contactPersonNotFoundException) { $this->logger->warning('ContactPerson.MarkMobilePhoneVerification.contactPersonNotFound', [ 'contactPersonId' => $command->contactPersonId->toRfc4122(), ]); - throw $e; + throw $contactPersonNotFoundException; } $actualPhone = $contactPerson->getMobilePhone(); @@ -82,4 +87,23 @@ public function handle(Command $command): void 'mobilePhoneVerifiedAt' => $contactPerson->getMobilePhoneVerifiedAt()?->toIso8601String(), ]); } + + private function guardMobilePhoneNumber(PhoneNumber $mobilePhoneNumber): void + { + if (!$this->phoneNumberUtil->isValidNumber($mobilePhoneNumber)) { + $this->logger->warning('ContactPerson.ChangeProfile.InvalidMobilePhoneNumber', [ + 'mobilePhoneNumber' => (string) $mobilePhoneNumber, + ]); + + throw new InvalidArgumentException('Invalid mobile phone number.'); + } + + if (PhoneNumberType::MOBILE !== $this->phoneNumberUtil->getNumberType($mobilePhoneNumber)) { + $this->logger->warning('ContactPerson.ChangeProfile.MobilePhoneNumberMustBeMobile', [ + 'mobilePhoneNumber' => (string) $mobilePhoneNumber, + ]); + + throw new InvalidArgumentException('Phone number must be mobile.'); + } + } } diff --git a/src/ContactPersons/UseCase/UpdateData/Command.php b/src/ContactPersons/UseCase/UpdateData/Command.php deleted file mode 100644 index 7c49975..0000000 --- a/src/ContactPersons/UseCase/UpdateData/Command.php +++ /dev/null @@ -1,39 +0,0 @@ -validate(); - } - - private function validate(): void - { - if ($this->fullName instanceof FullName && '' === trim($this->fullName->name)) { - throw new InvalidArgumentException('Full name cannot be empty.'); - } - - if (null !== $this->email && '' === trim($this->email)) { - throw new InvalidArgumentException('Email cannot be empty if provided.'); - } - - if (null !== $this->externalId && '' === trim($this->externalId)) { - throw new InvalidArgumentException('External ID cannot be empty if provided.'); - } - } -} diff --git a/src/ContactPersons/UseCase/UpdateData/Handler.php b/src/ContactPersons/UseCase/UpdateData/Handler.php deleted file mode 100644 index 741c0b5..0000000 --- a/src/ContactPersons/UseCase/UpdateData/Handler.php +++ /dev/null @@ -1,72 +0,0 @@ -logger->info('ContactPerson.UpdateData.start', [ - 'contactPersonId' => $command->contactPersonId, - 'fullName' => $command->fullName?->name ?? null, - 'email' => $command->email, - 'mobilePhoneNumber' => $command->mobilePhoneNumber?->__toString() ?? null, - 'externalId' => $command->externalId, - 'bitrix24PartnerId' => $command->bitrix24PartnerId?->toRfc4122() ?? null, - ]); - - /** @var AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ - $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); - - if ($command->fullName instanceof FullName) { - $contactPerson->changeFullName($command->fullName); - } - - if (null !== $command->email) { - $contactPerson->changeEmail($command->email); - } - - if ($command->mobilePhoneNumber instanceof PhoneNumber) { - $contactPerson->changeMobilePhone($command->mobilePhoneNumber); - } - - if (null !== $command->externalId) { - $contactPerson->setExternalId($command->externalId); - } - - if ($command->bitrix24PartnerId instanceof Uuid) { - $contactPerson->setBitrix24PartnerId($command->bitrix24PartnerId); - } - - $this->contactPersonRepository->save($contactPerson); - $this->flusher->flush($contactPerson); - - $this->logger->info('ContactPerson.UpdateData.finish', [ - 'contactPersonId' => $contactPerson->getId()->toRfc4122(), - 'updatedFields' => [ - 'fullName' => $command->fullName?->name ?? null, - 'email' => $command->email, - 'mobilePhoneNumber' => $command->mobilePhoneNumber?->__toString() ?? null, - 'externalId' => $command->externalId, - 'bitrix24PartnerId' => $command->bitrix24PartnerId?->toRfc4122() ?? null, - ], - ]); - } -} diff --git a/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php b/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php similarity index 95% rename from tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php rename to tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php index 0e10584..81db705 100644 --- a/tests/Functional/ContactPersons/UseCase/Install/HandlerTest.php +++ b/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php @@ -11,31 +11,30 @@ declare(strict_types=1); -namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\Install; +namespace Bitrix24\Lib\Tests\Functional\ApplicationInstallations\UseCase\InstallContactPerson; use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository; +use Bitrix24\Lib\ApplicationInstallations\UseCase\InstallContactPerson\Command; +use Bitrix24\Lib\ApplicationInstallations\UseCase\InstallContactPerson\Handler; use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository; -use Bitrix24\Lib\ContactPersons\UseCase\Install\Handler; -use Bitrix24\Lib\ContactPersons\UseCase\Install\Command; use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; use Bitrix24\Lib\Services\Flusher; +use Bitrix24\Lib\Tests\EntityManagerFactory; use Bitrix24\Lib\Tests\Functional\ApplicationInstallations\Builders\ApplicationInstallationBuilder; use Bitrix24\Lib\Tests\Functional\Bitrix24Accounts\Builders\Bitrix24AccountBuilder; +use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; use Bitrix24\SDK\Application\ApplicationStatus; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationStatus; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationBitrix24PartnerContactPersonLinkedEvent; -use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationBitrix24PartnerLinkedEvent; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationContactPersonLinkedEvent; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Exceptions\ApplicationInstallationNotFoundException; use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonCreatedEvent; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonEmailChangedEvent; use Bitrix24\SDK\Application\PortalLicenseFamily; use Bitrix24\SDK\Core\Credentials\Scope; -use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; -use Bitrix24\Lib\Tests\EntityManagerFactory; -use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Exceptions\ApplicationInstallationNotFoundException; +use libphonenumber\PhoneNumber; +use libphonenumber\PhoneNumberUtil; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; @@ -44,10 +43,6 @@ use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Uid\Uuid; -use libphonenumber\PhoneNumberUtil; -use libphonenumber\PhoneNumber; -use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; -use Bitrix24\Lib\ContactPersons\Enum\ContactPersonType; /** * @internal @@ -55,6 +50,10 @@ #[CoversClass(Handler::class)] class HandlerTest extends TestCase { + /** + * @var \libphonenumber\PhoneNumberUtil + */ + public $phoneNumberUtil; private Handler $handler; private Flusher $flusher; @@ -75,10 +74,12 @@ protected function setUp(): void $this->repository = new ContactPersonRepository($entityManager); $this->applicationInstallationRepository = new ApplicationInstallationRepository($entityManager); $this->bitrix24accountRepository = new Bitrix24AccountRepository($entityManager); + $this->phoneNumberUtil = PhoneNumberUtil::getInstance(); $this->flusher = new Flusher($entityManager, $this->eventDispatcher); $this->handler = new Handler( $this->applicationInstallationRepository, $this->repository, + $this->phoneNumberUtil, $this->flusher, new NullLogger() ); diff --git a/tests/Functional/ContactPersons/UseCase/Unlink/HandlerTest.php b/tests/Functional/ApplicationInstallations/UseCase/UnlinkContactPerson/HandlerTest.php similarity index 97% rename from tests/Functional/ContactPersons/UseCase/Unlink/HandlerTest.php rename to tests/Functional/ApplicationInstallations/UseCase/UnlinkContactPerson/HandlerTest.php index 706f40a..b825910 100644 --- a/tests/Functional/ContactPersons/UseCase/Unlink/HandlerTest.php +++ b/tests/Functional/ApplicationInstallations/UseCase/UnlinkContactPerson/HandlerTest.php @@ -11,29 +11,31 @@ declare(strict_types=1); -namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\Unlink; +namespace Bitrix24\Lib\Tests\Functional\ApplicationInstallations\UseCase\UnlinkContactPerson; use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository; +use Bitrix24\Lib\ApplicationInstallations\UseCase\UnlinkContactPerson\Command; +use Bitrix24\Lib\ApplicationInstallations\UseCase\UnlinkContactPerson\Handler; use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository; -use Bitrix24\Lib\ContactPersons\UseCase\Unlink\Handler; -use Bitrix24\Lib\ContactPersons\UseCase\Unlink\Command; use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; use Bitrix24\Lib\Services\Flusher; +use Bitrix24\Lib\Tests\EntityManagerFactory; use Bitrix24\Lib\Tests\Functional\ApplicationInstallations\Builders\ApplicationInstallationBuilder; use Bitrix24\Lib\Tests\Functional\Bitrix24Accounts\Builders\Bitrix24AccountBuilder; +use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; use Bitrix24\SDK\Application\ApplicationStatus; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationStatus; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationBitrix24PartnerContactPersonUnlinkedEvent; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationContactPersonUnlinkedEvent; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Exceptions\ApplicationInstallationNotFoundException; use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonDeletedEvent; use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; use Bitrix24\SDK\Application\PortalLicenseFamily; use Bitrix24\SDK\Core\Credentials\Scope; use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; -use Bitrix24\Lib\Tests\EntityManagerFactory; -use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Exceptions\ApplicationInstallationNotFoundException; +use libphonenumber\PhoneNumber; +use libphonenumber\PhoneNumberUtil; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; @@ -42,10 +44,6 @@ use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Uid\Uuid; -use libphonenumber\PhoneNumberUtil; -use libphonenumber\PhoneNumber; -use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; -use Doctrine\DBAL\Schema\Name\OptionallyQualifiedName; /** * @internal diff --git a/tests/Functional/ContactPersons/UseCase/UpdateData/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/ChangeProfile/HandlerTest.php similarity index 93% rename from tests/Functional/ContactPersons/UseCase/UpdateData/HandlerTest.php rename to tests/Functional/ContactPersons/UseCase/ChangeProfile/HandlerTest.php index e40f94d..8d65b47 100644 --- a/tests/Functional/ContactPersons/UseCase/UpdateData/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/ChangeProfile/HandlerTest.php @@ -12,12 +12,12 @@ declare(strict_types=1); -namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\UpdateData; +namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\ChangeProfile; use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository; use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository; -use Bitrix24\Lib\ContactPersons\UseCase\UpdateData\Handler; -use Bitrix24\Lib\ContactPersons\UseCase\UpdateData\Command; +use Bitrix24\Lib\ContactPersons\UseCase\ChangeProfile\Handler; +use Bitrix24\Lib\ContactPersons\UseCase\ChangeProfile\Command; use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; use Bitrix24\Lib\Services\Flusher; use Bitrix24\Lib\Tests\Functional\ApplicationInstallations\Builders\ApplicationInstallationBuilder; @@ -54,6 +54,10 @@ #[CoversClass(Handler::class)] class HandlerTest extends TestCase { + /** + * @var \libphonenumber\PhoneNumberUtil + */ + public $phoneNumberUtil; private Handler $handler; private Flusher $flusher; @@ -68,9 +72,11 @@ protected function setUp(): void $entityManager = EntityManagerFactory::get(); $this->eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); $this->repository = new ContactPersonRepository($entityManager); + $this->phoneNumberUtil = PhoneNumberUtil::getInstance(); $this->flusher = new Flusher($entityManager, $this->eventDispatcher); $this->handler = new Handler( $this->repository, + $this->phoneNumberUtil, $this->flusher, new NullLogger() ); @@ -121,8 +127,6 @@ public function testUpdateExistingContactPerson(): void $this->assertEquals('Jane Doe', $updatedContactPerson->getFullName()->name); $this->assertEquals('jane.doe@example.com', $updatedContactPerson->getEmail()); $this->assertEquals('+79997654321', $formattedPhone); - $this->assertEquals($externalId, $updatedContactPerson->getExternalId()); - $this->assertEquals($uuidV7, $updatedContactPerson->getBitrix24PartnerId()); } private function createPhoneNumber(string $number): PhoneNumber diff --git a/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php index 7fa5466..d0408da 100644 --- a/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php @@ -38,6 +38,10 @@ #[CoversClass(Handler::class)] class HandlerTest extends TestCase { + /** + * @var \libphonenumber\PhoneNumberUtil + */ + public $phoneNumberUtil; private Handler $handler; private Flusher $flusher; From a5d03de4fd14217bc126d9ab2f65fd5b1e729509 Mon Sep 17 00:00:00 2001 From: kirill Date: Sat, 10 Jan 2026 00:28:17 +0300 Subject: [PATCH 19/31] Update email and mobile phone change methods to reset verification status and timestamps --- src/ContactPersons/Entity/ContactPerson.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ContactPersons/Entity/ContactPerson.php b/src/ContactPersons/Entity/ContactPerson.php index 7bba5ec..8915fd1 100644 --- a/src/ContactPersons/Entity/ContactPerson.php +++ b/src/ContactPersons/Entity/ContactPerson.php @@ -160,8 +160,10 @@ public function getEmail(): ?string public function changeEmail(?string $email): void { $this->email = $email; - + $this->isEmailVerified = false; + $this->emailVerifiedAt = null; $this->updatedAt = new CarbonImmutable(); + $this->events[] = new ContactPersonEmailChangedEvent( $this->id, $this->updatedAt, @@ -206,6 +208,8 @@ public function getEmailVerifiedAt(): ?CarbonImmutable public function changeMobilePhone(?PhoneNumber $phoneNumber): void { $this->mobilePhoneNumber = $phoneNumber; + $this->isMobilePhoneVerified = false; + $this->mobilePhoneVerifiedAt = null; $this->updatedAt = new CarbonImmutable(); $this->events[] = new ContactPersonMobilePhoneChangedEvent( From 81720e9b8918de4d38d62e7843f828ba2ccf2c8b Mon Sep 17 00:00:00 2001 From: kirill Date: Mon, 12 Jan 2026 23:47:55 +0300 Subject: [PATCH 20/31] Update `UnlinkContactPerson` use case to include application installation ID, handle exceptions, and improve logging --- .../UseCase/UnlinkContactPerson/Command.php | 1 + .../UseCase/UnlinkContactPerson/Handler.php | 60 ++++++++++++------- .../MarkMobilePhoneAsVerified/Handler.php | 2 - 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Command.php b/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Command.php index c90201c..2059747 100644 --- a/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Command.php +++ b/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Command.php @@ -10,6 +10,7 @@ { public function __construct( public Uuid $contactPersonId, + public Uuid $applicationInstallationId, public ?string $comment = null, ) { $this->validate(); diff --git a/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Handler.php b/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Handler.php index 9b962ec..803c7bb 100644 --- a/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Handler.php +++ b/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Handler.php @@ -6,8 +6,10 @@ use Bitrix24\Lib\Services\Flusher; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationInterface; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Exceptions\ApplicationInstallationNotFoundException; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Repository\ApplicationInstallationRepositoryInterface; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; use Bitrix24\SDK\Application\Contracts\ContactPersons\Repository\ContactPersonRepositoryInterface; use Bitrix24\SDK\Application\Contracts\Events\AggregateRootEventsEmitterInterface; use Psr\Log\LoggerInterface; @@ -23,37 +25,53 @@ public function __construct( public function handle(Command $command): void { - $this->logger->info('ContactPerson.UninstallContactPerson.start', [ + $this->logger->info('ContactPerson.UnlinkContactPerson.start', [ 'contactPersonId' => $command->contactPersonId, + 'applicationInstallationId' => $command->applicationInstallationId, ]); - /** @var AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ - $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); + try { + /** @var AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ + $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); - /** @var AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $applicationInstallation */ - $applicationInstallation = $this->applicationInstallationRepository->getCurrent(); + /** @var AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $applicationInstallation */ + $applicationInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); - $entitiesToFlush = []; - if ($contactPerson->isPartner()) { - if (null !== $applicationInstallation->getBitrix24PartnerContactPersonId()) { - $applicationInstallation->unlinkBitrix24PartnerContactPerson(); + $entitiesToFlush = []; + if ($contactPerson->isPartner()) { + if (null !== $applicationInstallation->getBitrix24PartnerContactPersonId()) { + $applicationInstallation->unlinkBitrix24PartnerContactPerson(); + $this->applicationInstallationRepository->save($applicationInstallation); + $entitiesToFlush[] = $applicationInstallation; + } + } elseif (null !== $applicationInstallation->getContactPersonId()) { + $applicationInstallation->unlinkContactPerson(); $this->applicationInstallationRepository->save($applicationInstallation); $entitiesToFlush[] = $applicationInstallation; + } else { + $this->logger->warning('ContactPerson.UnlinkContactPerson.alreadyUnlinked', [ + 'contactPersonId' => $command->contactPersonId, + 'applicationInstallationId' => $command->applicationInstallationId, + ]); } - } elseif (null !== $applicationInstallation->getContactPersonId()) { - $applicationInstallation->unlinkContactPerson(); - $this->applicationInstallationRepository->save($applicationInstallation); - $entitiesToFlush[] = $applicationInstallation; - } - $contactPerson->markAsDeleted($command->comment); - $this->contactPersonRepository->save($contactPerson); - $entitiesToFlush[] = $contactPerson; + $contactPerson->markAsDeleted($command->comment); + $this->contactPersonRepository->save($contactPerson); + $entitiesToFlush[] = $contactPerson; - $this->flusher->flush(...$entitiesToFlush); + $this->flusher->flush(...$entitiesToFlush); - $this->logger->info('ContactPerson.UninstallContactPerson.finish', [ - 'contact_person_id' => $command->contactPersonId, - ]); + } catch (ContactPersonNotFoundException|ApplicationInstallationNotFoundException $e) { + $this->logger->warning('ContactPerson.UnlinkContactPerson.notFound', [ + 'message' => $e->getMessage() + ]); + throw $e; + + } finally { + $this->logger->info('ContactPerson.UnlinkContactPerson.finish', [ + 'contactPersonId' => $command->contactPersonId, + 'applicationInstallationId' => $command->applicationInstallationId, + ]); + } } } diff --git a/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php index 9528781..06ebd2e 100644 --- a/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php +++ b/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php @@ -35,8 +35,6 @@ public function handle(Command $command): void 'phone' => $expectedMobilePhoneE164, ]); - $this->guardMobilePhoneNumber($command->phone); - try { /** @var AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); From 5864fc1abd1b5c5f74827fd83ec8d275f82aba3f Mon Sep 17 00:00:00 2001 From: kirill Date: Thu, 15 Jan 2026 23:50:13 +0300 Subject: [PATCH 21/31] Refactor exception handling and logging across ContactPerson use cases, streamline method calls, and update test implementations for consistency. --- .../UseCase/InstallContactPerson/Handler.php | 93 +++++++++++-------- .../UseCase/UnlinkContactPerson/Handler.php | 7 +- .../UseCase/ChangeProfile/Handler.php | 55 ++++++----- .../UseCase/MarkEmailAsVerified/Handler.php | 52 +++++------ .../MarkMobilePhoneAsVerified/Handler.php | 91 +++++++----------- .../InstallContactPerson/HandlerTest.php | 1 + .../UnlinkContactPerson/HandlerTest.php | 7 +- .../UseCase/ChangeProfile/HandlerTest.php | 8 +- .../MarkMobilePhoneAsVerified/HandlerTest.php | 1 + 9 files changed, 155 insertions(+), 160 deletions(-) diff --git a/src/ApplicationInstallations/UseCase/InstallContactPerson/Handler.php b/src/ApplicationInstallations/UseCase/InstallContactPerson/Handler.php index 74139c3..06740be 100644 --- a/src/ApplicationInstallations/UseCase/InstallContactPerson/Handler.php +++ b/src/ApplicationInstallations/UseCase/InstallContactPerson/Handler.php @@ -7,6 +7,7 @@ use Bitrix24\Lib\ContactPersons\Entity\ContactPerson; use Bitrix24\Lib\Services\Flusher; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationInterface; +use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Exceptions\ApplicationInstallationNotFoundException; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Repository\ApplicationInstallationRepositoryInterface; use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonStatus; use Bitrix24\SDK\Application\Contracts\ContactPersons\Repository\ContactPersonRepositoryInterface; @@ -36,46 +37,62 @@ public function handle(Command $command): void 'bitrix24PartnerId' => $command->bitrix24PartnerId?->toRfc4122() ?? '', ]); - if ($command->mobilePhoneNumber instanceof \libphonenumber\PhoneNumber) { - $this->guardMobilePhoneNumber($command->mobilePhoneNumber); - } + $createdContactPersonId = ''; + + try { + if ($command->mobilePhoneNumber instanceof PhoneNumber) { + $this->guardMobilePhoneNumber($command->mobilePhoneNumber); + } + + /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $applicationInstallation */ + $applicationInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); + + $uuidV7 = Uuid::v7(); + + $contactPerson = new ContactPerson( + $uuidV7, + ContactPersonStatus::active, + $command->fullName, + $command->email, + null, + $command->mobilePhoneNumber, + null, + $command->comment, + $command->externalId, + $command->bitrix24UserId, + $command->bitrix24PartnerId, + $command->userAgentInfo, + true + ); + + $this->contactPersonRepository->save($contactPerson); + + if ($contactPerson->isPartner()) { + $applicationInstallation->linkBitrix24PartnerContactPerson($uuidV7); + } else { + $applicationInstallation->linkContactPerson($uuidV7); + } + + $this->applicationInstallationRepository->save($applicationInstallation); + + $this->flusher->flush($contactPerson, $applicationInstallation); + + $createdContactPersonId = $uuidV7->toRfc4122(); + } catch (ApplicationInstallationNotFoundException $applicationInstallationNotFoundException) { + $this->logger->warning('ContactPerson.InstallContactPerson.applicationInstallationNotFound', [ + 'applicationInstallationId' => $command->applicationInstallationId, + 'message' => $applicationInstallationNotFoundException->getMessage(), + ]); - /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $applicationInstallation */ - $applicationInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); - - $uuidV7 = Uuid::v7(); - - $contactPerson = new ContactPerson( - $uuidV7, - ContactPersonStatus::active, - $command->fullName, - $command->email, - null, - $command->mobilePhoneNumber, - null, - $command->comment, - $command->externalId, - $command->bitrix24UserId, - $command->bitrix24PartnerId, - $command->userAgentInfo, - true - ); - - $this->contactPersonRepository->save($contactPerson); - - if ($contactPerson->isPartner()) { - $applicationInstallation->linkBitrix24PartnerContactPerson($uuidV7); - } else { - $applicationInstallation->linkContactPerson($uuidV7); + throw $applicationInstallationNotFoundException; + } finally { + $this->logger->info('ContactPerson.InstallContactPerson.finish', [ + 'applicationInstallationId' => $command->applicationInstallationId, + 'bitrix24UserId' => $command->bitrix24UserId, + 'bitrix24PartnerId' => $command->bitrix24PartnerId?->toRfc4122() ?? '', + 'contact_person_id' => $createdContactPersonId, + ]); } - - $this->applicationInstallationRepository->save($applicationInstallation); - - $this->flusher->flush($contactPerson, $applicationInstallation); - - $this->logger->info('ContactPerson.InstallContactPerson.finish', [ - 'contact_person_id' => $uuidV7->toRfc4122(), - ]); } private function guardMobilePhoneNumber(PhoneNumber $mobilePhoneNumber): void diff --git a/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Handler.php b/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Handler.php index 803c7bb..c991208 100644 --- a/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Handler.php +++ b/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Handler.php @@ -60,13 +60,12 @@ public function handle(Command $command): void $entitiesToFlush[] = $contactPerson; $this->flusher->flush(...$entitiesToFlush); - - } catch (ContactPersonNotFoundException|ApplicationInstallationNotFoundException $e) { + } catch (ApplicationInstallationNotFoundException|ContactPersonNotFoundException $e) { $this->logger->warning('ContactPerson.UnlinkContactPerson.notFound', [ - 'message' => $e->getMessage() + 'message' => $e->getMessage(), ]); - throw $e; + throw $e; } finally { $this->logger->info('ContactPerson.UnlinkContactPerson.finish', [ 'contactPersonId' => $command->contactPersonId, diff --git a/src/ContactPersons/UseCase/ChangeProfile/Handler.php b/src/ContactPersons/UseCase/ChangeProfile/Handler.php index d65d75c..54b8333 100644 --- a/src/ContactPersons/UseCase/ChangeProfile/Handler.php +++ b/src/ContactPersons/UseCase/ChangeProfile/Handler.php @@ -36,38 +36,43 @@ public function handle(Command $command): void try { /** @var AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); + + if (!$command->fullName->equal($contactPerson->getFullName())) { + $contactPerson->changeFullName($command->fullName); + } + + if ($command->email !== $contactPerson->getEmail()) { + $contactPerson->changeEmail($command->email); + } + + $this->guardMobilePhoneNumber($command->mobilePhoneNumber); + if (!$command->mobilePhoneNumber->equals($contactPerson->getMobilePhone())) { + $contactPerson->changeMobilePhone($command->mobilePhoneNumber); + } + + $this->contactPersonRepository->save($contactPerson); + $this->flusher->flush($contactPerson); + + $this->logger->info('ContactPerson.ChangeProfile.finish', [ + 'contactPersonId' => $contactPerson->getId()->toRfc4122(), + 'updatedFields' => [ + 'fullName' => (string) $command->fullName, + 'email' => $command->email, + 'mobilePhoneNumber' => (string) $command->mobilePhoneNumber, + ], + ]); } catch (ContactPersonNotFoundException $contactPersonNotFoundException) { $this->logger->warning('ContactPerson.ChangeProfile.contactPersonNotFound', [ 'contactPersonId' => $command->contactPersonId->toRfc4122(), + 'message' => $contactPersonNotFoundException->getMessage(), ]); throw $contactPersonNotFoundException; + } finally { + $this->logger->info('ContactPerson.ChangeProfile.finish', [ + 'contactPersonId' => $command->contactPersonId->toRfc4122(), + ]); } - - if (!$command->fullName->equal($contactPerson->getFullName())) { - $contactPerson->changeFullName($command->fullName); - } - - if ($command->email !== $contactPerson->getEmail()) { - $contactPerson->changeEmail($command->email); - } - - $this->guardMobilePhoneNumber($command->mobilePhoneNumber); - if (!$command->mobilePhoneNumber->equals($contactPerson->getMobilePhone())) { - $contactPerson->changeMobilePhone($command->mobilePhoneNumber); - } - - $this->contactPersonRepository->save($contactPerson); - $this->flusher->flush($contactPerson); - - $this->logger->info('ContactPerson.ChangeProfile.finish', [ - 'contactPersonId' => $contactPerson->getId()->toRfc4122(), - 'updatedFields' => [ - 'fullName' => (string) $command->fullName, - 'email' => $command->email, - 'mobilePhoneNumber' => (string) $command->mobilePhoneNumber, - ], - ]); } private function guardMobilePhoneNumber(PhoneNumber $mobilePhoneNumber): void diff --git a/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php index a47c56d..7d36095 100644 --- a/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php +++ b/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php @@ -29,40 +29,40 @@ public function handle(Command $command): void try { /** @var AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); + + $actualEmail = $contactPerson->getEmail(); + if (null == $actualEmail) { + $this->logger->warning('ContactPerson.MarkEmailVerification.currentEmailIsNull', [ + 'contactPersonId' => $command->contactPersonId->toRfc4122(), + 'actualEmail' => null, + 'expectedEmail' => $command->email, + ]); + + return; + } + + if (mb_strtolower($actualEmail) === mb_strtolower($command->email)) { + $contactPerson->markEmailAsVerified($command->emailVerifiedAt); + $this->contactPersonRepository->save($contactPerson); + $this->flusher->flush($contactPerson); + } else { + $this->logger->warning('ContactPerson.MarkEmailVerification.emailMismatch', [ + 'contactPersonId' => $command->contactPersonId->toRfc4122(), + 'actualEmail' => $actualEmail, + 'expectedEmail' => $command->email, + ]); + } } catch (ContactPersonNotFoundException $contactPersonNotFoundException) { $this->logger->warning('ContactPerson.MarkEmailVerification.contactPersonNotFound', [ 'contactPersonId' => $command->contactPersonId->toRfc4122(), + 'message' => $contactPersonNotFoundException->getMessage(), ]); throw $contactPersonNotFoundException; - } - - $actualEmail = $contactPerson->getEmail(); - if (null == $actualEmail) { - $this->logger->warning('ContactPerson.MarkEmailVerification.currentEmailIsNull', [ - 'contactPersonId' => $command->contactPersonId->toRfc4122(), - 'actualEmail' => null, - 'expectedEmail' => $command->email, - ]); - - return; - } - - if (mb_strtolower($actualEmail) === mb_strtolower($command->email)) { - $contactPerson->markEmailAsVerified($command->emailVerifiedAt); - $this->contactPersonRepository->save($contactPerson); - $this->flusher->flush($contactPerson); - } else { - $this->logger->warning('ContactPerson.MarkEmailVerification.emailMismatch', [ + } finally { + $this->logger->info('ContactPerson.MarkEmailVerification.finish', [ 'contactPersonId' => $command->contactPersonId->toRfc4122(), - 'actualEmail' => $actualEmail, - 'expectedEmail' => $command->email, ]); } - - $this->logger->info('ContactPerson.MarkEmailVerification.finish', [ - 'contactPersonId' => $contactPerson->getId()->toRfc4122(), - 'emailVerifiedAt' => $contactPerson->getEmailVerifiedAt()?->toIso8601String(), - ]); } } diff --git a/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php index 06ebd2e..ad9a07d 100644 --- a/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php +++ b/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php @@ -9,10 +9,7 @@ use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; use Bitrix24\SDK\Application\Contracts\ContactPersons\Repository\ContactPersonRepositoryInterface; use Bitrix24\SDK\Application\Contracts\Events\AggregateRootEventsEmitterInterface; -use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; -use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumberFormat; -use libphonenumber\PhoneNumberType; use libphonenumber\PhoneNumberUtil; use Psr\Log\LoggerInterface; @@ -38,70 +35,46 @@ public function handle(Command $command): void try { /** @var AggregateRootEventsEmitterInterface|ContactPersonInterface $contactPerson */ $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); + + $actualPhone = $contactPerson->getMobilePhone(); + if (null == $actualPhone) { + $this->logger->warning('ContactPerson.MarkMobilePhoneVerification.currentPhoneIsNull', [ + 'contactPersonId' => $command->contactPersonId->toRfc4122(), + 'actualPhone' => null, + 'expectedPhone' => $expectedMobilePhoneE164, + ]); + + return; + } + + if ($command->phone->equals($actualPhone)) { + $contactPerson->markMobilePhoneAsVerified($command->phoneVerifiedAt); + + $this->contactPersonRepository->save($contactPerson); + $this->flusher->flush($contactPerson); + } else { + // Format the current mobile phone number to the international E.164 format + $actualMobilePhoneE164 = $this->phoneNumberUtil->format($actualPhone, PhoneNumberFormat::E164); + + $this->logger->warning('ContactPerson.MarkMobilePhoneVerification.phoneMismatch', [ + 'contactPersonId' => $command->contactPersonId->toRfc4122(), + 'actualPhone' => $actualMobilePhoneE164, + 'expectedPhone' => $expectedMobilePhoneE164, + ]); + + return; + } } catch (ContactPersonNotFoundException $contactPersonNotFoundException) { $this->logger->warning('ContactPerson.MarkMobilePhoneVerification.contactPersonNotFound', [ 'contactPersonId' => $command->contactPersonId->toRfc4122(), + 'message' => $contactPersonNotFoundException->getMessage(), ]); throw $contactPersonNotFoundException; - } - - $actualPhone = $contactPerson->getMobilePhone(); - if (null == $actualPhone) { - $this->logger->warning('ContactPerson.MarkMobilePhoneVerification.currentPhoneIsNull', [ - 'contactPersonId' => $command->contactPersonId->toRfc4122(), - 'actualPhone' => null, - 'expectedPhone' => $expectedMobilePhoneE164, - ]); - - return; - } - - if ($command->phone->equals($actualPhone)) { - $contactPerson->markMobilePhoneAsVerified($command->phoneVerifiedAt); - - $this->contactPersonRepository->save($contactPerson); - $this->flusher->flush($contactPerson); - } else { - // Format the current mobile phone number to the international E.164 format - $actualMobilePhoneE164 = $this->phoneNumberUtil->format($actualPhone, PhoneNumberFormat::E164); - - $this->logger->warning('ContactPerson.MarkMobilePhoneVerification.phoneMismatch', [ - 'contactPersonId' => $command->contactPersonId->toRfc4122(), - 'actualPhone' => $actualMobilePhoneE164, - 'expectedPhone' => $expectedMobilePhoneE164, - ]); - // Do not throw here — just log mismatch and finish without changes + } finally { $this->logger->info('ContactPerson.MarkMobilePhoneVerification.finish', [ - 'contactPersonId' => $contactPerson->getId()->toRfc4122(), - 'mobilePhoneVerifiedAt' => $contactPerson->getMobilePhoneVerifiedAt()?->toIso8601String(), - ]); - - return; - } - - $this->logger->info('ContactPerson.MarkMobilePhoneVerification.finish', [ - 'contactPersonId' => $contactPerson->getId()->toRfc4122(), - 'mobilePhoneVerifiedAt' => $contactPerson->getMobilePhoneVerifiedAt()?->toIso8601String(), - ]); - } - - private function guardMobilePhoneNumber(PhoneNumber $mobilePhoneNumber): void - { - if (!$this->phoneNumberUtil->isValidNumber($mobilePhoneNumber)) { - $this->logger->warning('ContactPerson.ChangeProfile.InvalidMobilePhoneNumber', [ - 'mobilePhoneNumber' => (string) $mobilePhoneNumber, - ]); - - throw new InvalidArgumentException('Invalid mobile phone number.'); - } - - if (PhoneNumberType::MOBILE !== $this->phoneNumberUtil->getNumberType($mobilePhoneNumber)) { - $this->logger->warning('ContactPerson.ChangeProfile.MobilePhoneNumberMustBeMobile', [ - 'mobilePhoneNumber' => (string) $mobilePhoneNumber, + 'contactPersonId' => $command->contactPersonId->toRfc4122(), ]); - - throw new InvalidArgumentException('Phone number must be mobile.'); } } } diff --git a/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php b/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php index 81db705..a6e156e 100644 --- a/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php +++ b/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php @@ -54,6 +54,7 @@ class HandlerTest extends TestCase * @var \libphonenumber\PhoneNumberUtil */ public $phoneNumberUtil; + private Handler $handler; private Flusher $flusher; diff --git a/tests/Functional/ApplicationInstallations/UseCase/UnlinkContactPerson/HandlerTest.php b/tests/Functional/ApplicationInstallations/UseCase/UnlinkContactPerson/HandlerTest.php index b825910..ee82d51 100644 --- a/tests/Functional/ApplicationInstallations/UseCase/UnlinkContactPerson/HandlerTest.php +++ b/tests/Functional/ApplicationInstallations/UseCase/UnlinkContactPerson/HandlerTest.php @@ -132,11 +132,11 @@ public function testUninstallContactPersonSuccess(): void $this->applicationInstallationRepository->save($applicationInstallation); $this->flusher->flush(); - var_dump($contactPerson->getId()); // Запуск use-case $this->handler->handle( new Command( $contactPerson->getId(), + $applicationInstallation->getId(), 'Deleted by test' ) ); @@ -195,6 +195,7 @@ public function testUninstallContactPersonNotFound(): void $this->handler->handle( new Command( Uuid::v7(), + $applicationInstallation->getId(), 'Deleted by test' ) ); @@ -221,6 +222,7 @@ public function testUninstallContactPersonWithWrongApplicationInstallationId(): $this->handler->handle( new Command( $contactPerson->getId(), + Uuid::v7(), 'Deleted by test' ) ); @@ -282,6 +284,7 @@ public function testUninstallPartnerContactPersonSuccess(): void $this->handler->handle( new Command( $contactPerson->getId(), + $applicationInstallation->getId(), 'Deleted by test' ) ); @@ -295,7 +298,6 @@ public function testUninstallPartnerContactPersonSuccess(): void $foundInstallation = $this->applicationInstallationRepository->getById($applicationInstallation->getId()); $this->assertNull($foundInstallation->getBitrix24PartnerContactPersonId()); - // Контакт доступен в репозитории (с пометкой deleted) $this->expectException(ContactPersonNotFoundException::class); $this->repository->getById($contactPerson->getId()); } @@ -321,6 +323,7 @@ public function testUninstallPartnerContactPersonWithWrongApplicationInstallatio $this->handler->handle( new Command( $contactPerson->getId(), + Uuid::v7(), 'Deleted by test' ) ); diff --git a/tests/Functional/ContactPersons/UseCase/ChangeProfile/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/ChangeProfile/HandlerTest.php index 8d65b47..1098a35 100644 --- a/tests/Functional/ContactPersons/UseCase/ChangeProfile/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/ChangeProfile/HandlerTest.php @@ -58,6 +58,7 @@ class HandlerTest extends TestCase * @var \libphonenumber\PhoneNumberUtil */ public $phoneNumberUtil; + private Handler $handler; private Flusher $flusher; @@ -99,18 +100,13 @@ public function testUpdateExistingContactPerson(): void $this->repository->save($contactPerson); $this->flusher->flush(); - $externalId = Uuid::v7()->toRfc4122(); - $uuidV7 = Uuid::v7(); - // Обновляем контактное лицо через команду $this->handler->handle( new Command( $contactPerson->getId(), new FullName('Jane Doe'), 'jane.doe@example.com', - $this->createPhoneNumber('+79997654321'), - $externalId, - $uuidV7, + $this->createPhoneNumber('+79997654321') ) ); diff --git a/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php index d0408da..759ae2e 100644 --- a/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php @@ -42,6 +42,7 @@ class HandlerTest extends TestCase * @var \libphonenumber\PhoneNumberUtil */ public $phoneNumberUtil; + private Handler $handler; private Flusher $flusher; From 688ce0f1e04b090651e8c0266452f943ff30262f Mon Sep 17 00:00:00 2001 From: kirill Date: Sun, 18 Jan 2026 15:56:18 +0300 Subject: [PATCH 22/31] Replace UUID instance checks with null checks in `unlinkContactPerson` and `unlinkBitrix24PartnerContactPerson` methods. --- .../Entity/ApplicationInstallation.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ApplicationInstallations/Entity/ApplicationInstallation.php b/src/ApplicationInstallations/Entity/ApplicationInstallation.php index 6ace1ce..bda0eea 100644 --- a/src/ApplicationInstallations/Entity/ApplicationInstallation.php +++ b/src/ApplicationInstallations/Entity/ApplicationInstallation.php @@ -347,7 +347,7 @@ public function linkContactPerson(Uuid $uuid): void #[\Override] public function unlinkContactPerson(): void { - if (!$this->contactPersonId instanceof Uuid) { + if (null === $this->contactPersonId) { return; } @@ -377,7 +377,7 @@ public function linkBitrix24PartnerContactPerson(Uuid $uuid): void #[\Override] public function unlinkBitrix24PartnerContactPerson(): void { - if (!$this->bitrix24PartnerContactPersonId instanceof Uuid) { + if (null === $this->bitrix24PartnerContactPersonId) { return; } From 76c7c2e79ea2e83c9abbe0951aa836c84473db06 Mon Sep 17 00:00:00 2001 From: kirill Date: Tue, 20 Jan 2026 23:52:06 +0300 Subject: [PATCH 23/31] Refactor handling of email and phone verification, streamline null checks, improve logging, and update validation logic across ContactPerson use cases. --- .../UseCase/InstallContactPerson/Handler.php | 13 +++++++++---- .../UseCase/UnlinkContactPerson/Handler.php | 19 ++++++------------- .../UseCase/MarkEmailAsVerified/Handler.php | 11 +---------- .../MarkMobilePhoneAsVerified/Handler.php | 11 +---------- 4 files changed, 17 insertions(+), 37 deletions(-) diff --git a/src/ApplicationInstallations/UseCase/InstallContactPerson/Handler.php b/src/ApplicationInstallations/UseCase/InstallContactPerson/Handler.php index 06740be..ac9e0b3 100644 --- a/src/ApplicationInstallations/UseCase/InstallContactPerson/Handler.php +++ b/src/ApplicationInstallations/UseCase/InstallContactPerson/Handler.php @@ -40,8 +40,14 @@ public function handle(Command $command): void $createdContactPersonId = ''; try { - if ($command->mobilePhoneNumber instanceof PhoneNumber) { - $this->guardMobilePhoneNumber($command->mobilePhoneNumber); + if ($command->mobilePhoneNumber !== null) { + try { + $this->guardMobilePhoneNumber($command->mobilePhoneNumber); + } catch (InvalidArgumentException $exception) { + // Ошибка уже залогирована внутри гарда. + // Прерываем создание контакта, но не останавливаем установку приложения. + return; + } } /** @var null|AggregateRootEventsEmitterInterface|ApplicationInstallationInterface $applicationInstallation */ @@ -85,7 +91,7 @@ public function handle(Command $command): void ]); throw $applicationInstallationNotFoundException; - } finally { + }finally { $this->logger->info('ContactPerson.InstallContactPerson.finish', [ 'applicationInstallationId' => $command->applicationInstallationId, 'bitrix24UserId' => $command->bitrix24UserId, @@ -101,7 +107,6 @@ private function guardMobilePhoneNumber(PhoneNumber $mobilePhoneNumber): void $this->logger->warning('ContactPerson.InstallContactPerson.InvalidMobilePhoneNumber', [ 'mobilePhoneNumber' => (string) $mobilePhoneNumber, ]); - throw new InvalidArgumentException('Invalid mobile phone number.'); } diff --git a/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Handler.php b/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Handler.php index c991208..f2b47c3 100644 --- a/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Handler.php +++ b/src/ApplicationInstallations/UseCase/UnlinkContactPerson/Handler.php @@ -38,23 +38,16 @@ public function handle(Command $command): void $applicationInstallation = $this->applicationInstallationRepository->getById($command->applicationInstallationId); $entitiesToFlush = []; + if ($contactPerson->isPartner()) { - if (null !== $applicationInstallation->getBitrix24PartnerContactPersonId()) { - $applicationInstallation->unlinkBitrix24PartnerContactPerson(); - $this->applicationInstallationRepository->save($applicationInstallation); - $entitiesToFlush[] = $applicationInstallation; - } - } elseif (null !== $applicationInstallation->getContactPersonId()) { - $applicationInstallation->unlinkContactPerson(); - $this->applicationInstallationRepository->save($applicationInstallation); - $entitiesToFlush[] = $applicationInstallation; + $applicationInstallation->unlinkBitrix24PartnerContactPerson(); } else { - $this->logger->warning('ContactPerson.UnlinkContactPerson.alreadyUnlinked', [ - 'contactPersonId' => $command->contactPersonId, - 'applicationInstallationId' => $command->applicationInstallationId, - ]); + $applicationInstallation->unlinkContactPerson(); } + $this->applicationInstallationRepository->save($applicationInstallation); + $entitiesToFlush[] = $applicationInstallation; + $contactPerson->markAsDeleted($command->comment); $this->contactPersonRepository->save($contactPerson); $entitiesToFlush[] = $contactPerson; diff --git a/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php index 7d36095..28b242f 100644 --- a/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php +++ b/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php @@ -31,17 +31,8 @@ public function handle(Command $command): void $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); $actualEmail = $contactPerson->getEmail(); - if (null == $actualEmail) { - $this->logger->warning('ContactPerson.MarkEmailVerification.currentEmailIsNull', [ - 'contactPersonId' => $command->contactPersonId->toRfc4122(), - 'actualEmail' => null, - 'expectedEmail' => $command->email, - ]); - - return; - } - if (mb_strtolower($actualEmail) === mb_strtolower($command->email)) { + if (mb_strtolower((string)$actualEmail) === mb_strtolower($command->email)) { $contactPerson->markEmailAsVerified($command->emailVerifiedAt); $this->contactPersonRepository->save($contactPerson); $this->flusher->flush($contactPerson); diff --git a/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php index ad9a07d..513c35f 100644 --- a/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php +++ b/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php @@ -37,17 +37,8 @@ public function handle(Command $command): void $contactPerson = $this->contactPersonRepository->getById($command->contactPersonId); $actualPhone = $contactPerson->getMobilePhone(); - if (null == $actualPhone) { - $this->logger->warning('ContactPerson.MarkMobilePhoneVerification.currentPhoneIsNull', [ - 'contactPersonId' => $command->contactPersonId->toRfc4122(), - 'actualPhone' => null, - 'expectedPhone' => $expectedMobilePhoneE164, - ]); - - return; - } - if ($command->phone->equals($actualPhone)) { + if ($actualPhone !== null && $command->phone->equals($actualPhone)) { $contactPerson->markMobilePhoneAsVerified($command->phoneVerifiedAt); $this->contactPersonRepository->save($contactPerson); From 1c910091da96c5e99bf81d7e2cab917cc391f26a Mon Sep 17 00:00:00 2001 From: kirill Date: Fri, 23 Jan 2026 23:46:06 +0300 Subject: [PATCH 24/31] Refactor tests and handlers for improved consistency, streamline null checks, enhance validation logic, and update email/phone verification handling across ContactPerson use cases. --- rector.php | 4 +- .../UseCase/InstallContactPerson/Handler.php | 7 +- .../UseCase/MarkEmailAsVerified/Handler.php | 2 +- .../MarkMobilePhoneAsVerified/Handler.php | 2 +- .../InstallContactPerson/HandlerTest.php | 97 ++--------------- .../UseCase/ChangeProfile/HandlerTest.php | 102 +++++++++++++----- .../MarkEmailAsVerified/HandlerTest.php | 52 ++++----- .../MarkMobilePhoneAsVerified/HandlerTest.php | 30 +++--- 8 files changed, 140 insertions(+), 156 deletions(-) diff --git a/rector.php b/rector.php index 3ce8e42..59026b6 100644 --- a/rector.php +++ b/rector.php @@ -15,6 +15,7 @@ use Rector\Naming\Rector\Class_\RenamePropertyToMatchTypeRector; use Rector\PHPUnit\Set\PHPUnitSetList; use Rector\Set\ValueObject\DowngradeLevelSetList; +use Rector\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector; return RectorConfig::configure() ->withPaths([ @@ -48,5 +49,6 @@ strictBooleans: true ) ->withSkip([ - RenamePropertyToMatchTypeRector::class + RenamePropertyToMatchTypeRector::class, + FlipTypeControlToUseExclusiveTypeRector::class, ]); \ No newline at end of file diff --git a/src/ApplicationInstallations/UseCase/InstallContactPerson/Handler.php b/src/ApplicationInstallations/UseCase/InstallContactPerson/Handler.php index ac9e0b3..c29bcd6 100644 --- a/src/ApplicationInstallations/UseCase/InstallContactPerson/Handler.php +++ b/src/ApplicationInstallations/UseCase/InstallContactPerson/Handler.php @@ -40,10 +40,10 @@ public function handle(Command $command): void $createdContactPersonId = ''; try { - if ($command->mobilePhoneNumber !== null) { + if (null !== $command->mobilePhoneNumber) { try { $this->guardMobilePhoneNumber($command->mobilePhoneNumber); - } catch (InvalidArgumentException $exception) { + } catch (InvalidArgumentException) { // Ошибка уже залогирована внутри гарда. // Прерываем создание контакта, но не останавливаем установку приложения. return; @@ -91,7 +91,7 @@ public function handle(Command $command): void ]); throw $applicationInstallationNotFoundException; - }finally { + } finally { $this->logger->info('ContactPerson.InstallContactPerson.finish', [ 'applicationInstallationId' => $command->applicationInstallationId, 'bitrix24UserId' => $command->bitrix24UserId, @@ -107,6 +107,7 @@ private function guardMobilePhoneNumber(PhoneNumber $mobilePhoneNumber): void $this->logger->warning('ContactPerson.InstallContactPerson.InvalidMobilePhoneNumber', [ 'mobilePhoneNumber' => (string) $mobilePhoneNumber, ]); + throw new InvalidArgumentException('Invalid mobile phone number.'); } diff --git a/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php index 28b242f..40f2fb6 100644 --- a/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php +++ b/src/ContactPersons/UseCase/MarkEmailAsVerified/Handler.php @@ -32,7 +32,7 @@ public function handle(Command $command): void $actualEmail = $contactPerson->getEmail(); - if (mb_strtolower((string)$actualEmail) === mb_strtolower($command->email)) { + if (mb_strtolower((string) $actualEmail) === mb_strtolower($command->email)) { $contactPerson->markEmailAsVerified($command->emailVerifiedAt); $this->contactPersonRepository->save($contactPerson); $this->flusher->flush($contactPerson); diff --git a/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php b/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php index 513c35f..9d45766 100644 --- a/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php +++ b/src/ContactPersons/UseCase/MarkMobilePhoneAsVerified/Handler.php @@ -38,7 +38,7 @@ public function handle(Command $command): void $actualPhone = $contactPerson->getMobilePhone(); - if ($actualPhone !== null && $command->phone->equals($actualPhone)) { + if (null !== $actualPhone && $command->phone->equals($actualPhone)) { $contactPerson->markMobilePhoneAsVerified($command->phoneVerifiedAt); $this->contactPersonRepository->save($contactPerson); diff --git a/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php b/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php index a6e156e..3708d8a 100644 --- a/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php +++ b/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php @@ -25,11 +25,9 @@ use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; use Bitrix24\SDK\Application\ApplicationStatus; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationStatus; -use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationBitrix24PartnerContactPersonLinkedEvent; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Events\ApplicationInstallationContactPersonLinkedEvent; use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Exceptions\ApplicationInstallationNotFoundException; use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Entity\ContactPersonInterface; use Bitrix24\SDK\Application\Contracts\ContactPersons\Events\ContactPersonCreatedEvent; use Bitrix24\SDK\Application\PortalLicenseFamily; use Bitrix24\SDK\Core\Credentials\Scope; @@ -51,7 +49,7 @@ class HandlerTest extends TestCase { /** - * @var \libphonenumber\PhoneNumberUtil + * @var PhoneNumberUtil */ public $phoneNumberUtil; @@ -102,7 +100,8 @@ public function testInstallContactPersonSuccess(): void ->withMaster(true) ->withSetToken() ->withInstalled() - ->build(); + ->build() + ; $this->bitrix24accountRepository->save($bitrix24Account); @@ -115,7 +114,8 @@ public function testInstallContactPersonSuccess(): void ->withContactPersonId(null) ->withBitrix24PartnerContactPersonId(null) ->withExternalId($externalId) - ->build(); + ->build() + ; $this->applicationInstallationRepository->save($applicationInstallation); $this->flusher->flush(); @@ -129,7 +129,8 @@ public function testInstallContactPersonSuccess(): void ->withExternalId($externalId) ->withBitrix24UserId($bitrix24Account->getBitrix24UserId()) ->withBitrix24PartnerId($applicationInstallation->getBitrix24PartnerId()) - ->build(); + ->build() + ; // Запуск use-case $this->handler->handle( @@ -151,89 +152,11 @@ public function testInstallContactPersonSuccess(): void $this->assertContains(ContactPersonCreatedEvent::class, $dispatchedEvents); $this->assertContains(ApplicationInstallationContactPersonLinkedEvent::class, $dispatchedEvents); - // Перечитаем установку и проверим привязку контактного лица (без поиска по externalId) $foundInstallation = $this->applicationInstallationRepository->getById($applicationInstallation->getId()); $this->assertNotNull($foundInstallation->getContactPersonId()); $uuid = $foundInstallation->getContactPersonId(); $foundContactPerson = $this->repository->getById($uuid); - $this->assertInstanceOf(ContactPersonInterface::class, $foundContactPerson); - $this->assertEquals($foundContactPerson->getId(), $uuid); - } - - #[Test] - public function testInstallPartnerContactPersonSuccess(): void - { - // Подготовка Bitrix24 аккаунта и установки приложения - $applicationToken = Uuid::v7()->toRfc4122(); - $memberId = Uuid::v4()->toRfc4122(); - $externalId = Uuid::v7()->toRfc4122(); - $uuidV7 = Uuid::v7(); - - $bitrix24Account = (new Bitrix24AccountBuilder()) - ->withApplicationScope(new Scope(['crm'])) - ->withStatus(Bitrix24AccountStatus::new) - ->withApplicationToken($applicationToken) - ->withMemberId($memberId) - ->withMaster(true) - ->withSetToken() - ->withInstalled() - ->build(); - - $this->bitrix24accountRepository->save($bitrix24Account); - - $applicationInstallation = (new ApplicationInstallationBuilder()) - ->withApplicationStatus(new ApplicationStatus('F')) - ->withPortalLicenseFamily(PortalLicenseFamily::free) - ->withBitrix24AccountId($bitrix24Account->getId()) - ->withApplicationStatusInstallation(ApplicationInstallationStatus::active) - ->withApplicationToken($applicationToken) - ->withContactPersonId(null) - ->withBitrix24PartnerId($uuidV7) - ->withExternalId($externalId) - ->build(); - - $this->applicationInstallationRepository->save($applicationInstallation); - $this->flusher->flush(); - - // Данные контакта - $contactPersonBuilder = new ContactPersonBuilder(); - $contactPerson = $contactPersonBuilder - ->withEmail('john.doe@example.com') - ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) - ->withComment('Test comment') - ->withExternalId($externalId) - ->withBitrix24UserId($bitrix24Account->getBitrix24UserId()) - ->withBitrix24PartnerId($applicationInstallation->getBitrix24PartnerId()) - ->build(); - - // Запуск use-case - $this->handler->handle( - new Command( - $applicationInstallation->getId(), - $contactPerson->getFullName(), - $bitrix24Account->getBitrix24UserId(), - $contactPerson->getUserAgentInfo(), - $contactPerson->getEmail(), - $contactPerson->getMobilePhone(), - $contactPerson->getComment(), - $contactPerson->getExternalId(), - $contactPerson->getBitrix24PartnerId(), - ) - ); - - // Проверки: событие, связь и наличие контакта - $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); - $this->assertContains(ContactPersonCreatedEvent::class, $dispatchedEvents); - $this->assertContains(ApplicationInstallationBitrix24PartnerContactPersonLinkedEvent::class, $dispatchedEvents); - - // Перечитаем установку и проверим привязку контактного лица (без поиска по externalId) - $foundInstallation = $this->applicationInstallationRepository->getById($applicationInstallation->getId()); - $this->assertNotNull($foundInstallation->getBitrix24PartnerContactPersonId()); - - $uuid = $foundInstallation->getBitrix24PartnerContactPersonId(); - $foundContactPerson = $this->repository->getById($uuid); - $this->assertInstanceOf(ContactPersonInterface::class, $foundContactPerson); $this->assertEquals($foundContactPerson->getId(), $uuid); } @@ -247,7 +170,8 @@ public function testInstallContactPersonWithWrongApplicationInstallationId(): vo ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) ->withComment('Test comment') ->withExternalId(Uuid::v7()->toRfc4122()) - ->build(); + ->build() + ; $uuidV7 = Uuid::v7(); @@ -271,6 +195,7 @@ public function testInstallContactPersonWithWrongApplicationInstallationId(): vo private function createPhoneNumber(string $number): PhoneNumber { $phoneNumberUtil = PhoneNumberUtil::getInstance(); + return $phoneNumberUtil->parse($number, 'RU'); } -} \ No newline at end of file +} diff --git a/tests/Functional/ContactPersons/UseCase/ChangeProfile/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/ChangeProfile/HandlerTest.php index 1098a35..2237383 100644 --- a/tests/Functional/ContactPersons/UseCase/ChangeProfile/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/ChangeProfile/HandlerTest.php @@ -1,6 +1,5 @@ withExternalId(Uuid::v7()->toRfc4122()) ->withBitrix24UserId(random_int(1, 1_000_000)) ->withBitrix24PartnerId(Uuid::v7()) - ->build(); + ->build() + ; $this->repository->save($contactPerson); $this->flusher->flush(); @@ -110,11 +99,9 @@ public function testUpdateExistingContactPerson(): void ) ); - // Проверяем, что изменения сохранились $updatedContactPerson = $this->repository->getById($contactPerson->getId()); - $phoneNumberUtil = PhoneNumberUtil::getInstance(); - $formattedPhone = $phoneNumberUtil->format($updatedContactPerson->getMobilePhone(), PhoneNumberFormat::E164); + $formattedPhone = $this->phoneNumberUtil->format($updatedContactPerson->getMobilePhone(), PhoneNumberFormat::E164); $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); $this->assertContains(ContactPersonEmailChangedEvent::class, $dispatchedEvents); @@ -125,9 +112,70 @@ public function testUpdateExistingContactPerson(): void $this->assertEquals('+79997654321', $formattedPhone); } + #[Test] + public function testUpdateWithNonExistentContactPerson(): void + { + $this->expectException(ContactPersonNotFoundException::class); + + $this->handler->handle( + new Command( + Uuid::v7(), + new FullName('Jane Doe'), + 'jane.doe@example.com', + $this->createPhoneNumber('+79997654321') + ) + ); + } + + #[Test] + public function testUpdateWithSameData(): void + { + // Создаем контактное лицо через билдера + $email = 'john.doe@example.com'; + $fullName = new FullName('John Doe'); + $phone = '+79991234567'; + + $contactPersonBuilder = new ContactPersonBuilder(); + $contactPerson = $contactPersonBuilder + ->withEmail($email) + ->withFullName($fullName) + ->withMobilePhoneNumber($this->createPhoneNumber($phone)) + ->withExternalId(Uuid::v7()->toRfc4122()) + ->withBitrix24UserId(random_int(1, 1_000_000)) + ->withBitrix24PartnerId(Uuid::v7()) + ->build() + ; + + $this->repository->save($contactPerson); + $this->flusher->flush(); + + // Обновляем контактное лицо теми же данными + $this->handler->handle( + new Command( + $contactPerson->getId(), + $fullName, + $email, + $this->createPhoneNumber($phone) + ) + ); + + // Проверяем, что события не были отправлены + $dispatchedEvents = $this->eventDispatcher->getOrphanedEvents(); + $this->assertNotContains(ContactPersonEmailChangedEvent::class, $dispatchedEvents); + $this->assertNotContains(ContactPersonMobilePhoneChangedEvent::class, $dispatchedEvents); + $this->assertNotContains(ContactPersonFullNameChangedEvent::class, $dispatchedEvents); + + // Проверяем, что данные не изменились + $updatedContactPerson = $this->repository->getById($contactPerson->getId()); + $this->assertEquals($fullName->name, $updatedContactPerson->getFullName()->name); + $this->assertEquals($email, $updatedContactPerson->getEmail()); + $this->assertEquals($phone, $this->phoneNumberUtil->format($updatedContactPerson->getMobilePhone(), PhoneNumberFormat::E164)); + } + private function createPhoneNumber(string $number): PhoneNumber { $phoneNumberUtil = PhoneNumberUtil::getInstance(); + return $phoneNumberUtil->parse($number, 'RU'); } -} \ No newline at end of file +} diff --git a/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php index 46001f4..940f870 100644 --- a/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php @@ -13,14 +13,16 @@ namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\MarkEmailAsVerified; -use Bitrix24\Lib\ContactPersons\UseCase\MarkEmailAsVerified\Handler; -use Bitrix24\Lib\ContactPersons\UseCase\MarkEmailAsVerified\Command; -use InvalidArgumentException; -use Carbon\CarbonImmutable; use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; +use Bitrix24\Lib\ContactPersons\UseCase\MarkEmailAsVerified\Command; +use Bitrix24\Lib\ContactPersons\UseCase\MarkEmailAsVerified\Handler; use Bitrix24\Lib\Services\Flusher; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; use Bitrix24\Lib\Tests\EntityManagerFactory; +use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; +use Carbon\CarbonImmutable; +use libphonenumber\PhoneNumber; +use libphonenumber\PhoneNumberUtil; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; @@ -29,10 +31,6 @@ use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Uid\Uuid; -use libphonenumber\PhoneNumberUtil; -use libphonenumber\PhoneNumber; -use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; - /** * @internal @@ -63,7 +61,7 @@ protected function setUp(): void } #[Test] - public function testConfirmEmailVerification_Success_WithEmailAndTimestamp(): void + public function testConfirmEmailVerificationSuccess(): void { $contactPersonBuilder = new ContactPersonBuilder(); $externalId = Uuid::v7()->toRfc4122(); @@ -76,7 +74,8 @@ public function testConfirmEmailVerification_Success_WithEmailAndTimestamp(): vo ->withExternalId($externalId) ->withBitrix24UserId($bitrix24UserId) ->withBitrix24PartnerId(Uuid::v7()) - ->build(); + ->build() + ; $this->repository->save($contactPerson); $this->flusher->flush(); @@ -95,7 +94,7 @@ public function testConfirmEmailVerification_Success_WithEmailAndTimestamp(): vo } #[Test] - public function testConfirmEmailVerification_Fails_IfContactPersonNotFound(): void + public function testConfirmEmailVerificationFailsIfContactPersonNotFound(): void { $contactPersonBuilder = new ContactPersonBuilder(); $externalId = Uuid::v7()->toRfc4122(); @@ -108,7 +107,8 @@ public function testConfirmEmailVerification_Fails_IfContactPersonNotFound(): vo ->withExternalId($externalId) ->withBitrix24UserId($bitrix24UserId) ->withBitrix24PartnerId(Uuid::v7()) - ->build(); + ->build() + ; $this->repository->save($contactPerson); $this->flusher->flush(); @@ -120,7 +120,7 @@ public function testConfirmEmailVerification_Fails_IfContactPersonNotFound(): vo } #[Test] - public function testConfirmEmailVerification_Fails_IfEmailMismatch(): void + public function testConfirmEmailVerificationFailsIfEmailMismatch(): void { $contactPersonBuilder = new ContactPersonBuilder(); $externalId = Uuid::v7()->toRfc4122(); @@ -133,12 +133,13 @@ public function testConfirmEmailVerification_Fails_IfEmailMismatch(): void ->withExternalId($externalId) ->withBitrix24UserId($bitrix24UserId) ->withBitrix24PartnerId(Uuid::v7()) - ->build(); + ->build() + ; $this->repository->save($contactPerson); $this->flusher->flush(); - // Больше не бросаем исключение при несовпадении email — только лог и без изменений + // We no longer throw an exception when the email doesn't match — we only log it and make no changes. $this->handler->handle( new Command($contactPerson->getId(), 'another.email@example.com') ); @@ -149,7 +150,7 @@ public function testConfirmEmailVerification_Fails_IfEmailMismatch(): void } #[Test] - public function testConfirmEmailVerification_Fails_IfEntityHasNoEmailButCommandProvidesOne(): void + public function testConfirmEmailVerificationFailsIfEntityHasNoEmailButCommandProvidesOne(): void { $contactPersonBuilder = new ContactPersonBuilder(); $externalId = Uuid::v7()->toRfc4122(); @@ -162,12 +163,13 @@ public function testConfirmEmailVerification_Fails_IfEntityHasNoEmailButCommandP ->withExternalId($externalId) ->withBitrix24UserId($bitrix24UserId) ->withBitrix24PartnerId(Uuid::v7()) - ->build(); + ->build() + ; $this->repository->save($contactPerson); $this->flusher->flush(); - // В обработчик передаём email — теперь только лог и выход без изменений + // We no longer throw an exception when the email doesn't match — we only log it and make no changes. $this->handler->handle( new Command($contactPerson->getId(), 'john.doe@example.com') ); @@ -178,7 +180,7 @@ public function testConfirmEmailVerification_Fails_IfEntityHasNoEmailButCommandP } #[Test] - public function testConfirmEmailVerification_Fails_IfInvalidEmailProvided(): void + public function testConfirmEmailVerificationFailsIfInvalidEmailProvided(): void { $contactPersonBuilder = new ContactPersonBuilder(); $externalId = Uuid::v7()->toRfc4122(); @@ -191,13 +193,14 @@ public function testConfirmEmailVerification_Fails_IfInvalidEmailProvided(): voi ->withExternalId($externalId) ->withBitrix24UserId($bitrix24UserId) ->withBitrix24PartnerId(Uuid::v7()) - ->build(); + ->build() + ; $this->repository->save($contactPerson); $this->flusher->flush(); - $this->expectException(InvalidArgumentException::class); - // Неверный email должен упасть на валидации конструктора команды + $this->expectException(\InvalidArgumentException::class); + // An invalid email should fail during validation in the command constructor. $this->handler->handle( new Command($contactPerson->getId(), 'not-an-email') ); @@ -206,6 +209,7 @@ public function testConfirmEmailVerification_Fails_IfInvalidEmailProvided(): voi private function createPhoneNumber(string $number): PhoneNumber { $phoneNumberUtil = PhoneNumberUtil::getInstance(); + return $phoneNumberUtil->parse($number, 'RU'); } -} \ No newline at end of file +} diff --git a/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php index 759ae2e..9d07882 100644 --- a/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php @@ -13,12 +13,15 @@ namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\MarkMobilePhoneAsVerified; -use Bitrix24\Lib\ContactPersons\UseCase\MarkMobilePhoneAsVerified\Handler; -use Bitrix24\Lib\ContactPersons\UseCase\MarkMobilePhoneAsVerified\Command; use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; +use Bitrix24\Lib\ContactPersons\UseCase\MarkMobilePhoneAsVerified\Command; +use Bitrix24\Lib\ContactPersons\UseCase\MarkMobilePhoneAsVerified\Handler; use Bitrix24\Lib\Services\Flusher; -use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; use Bitrix24\Lib\Tests\EntityManagerFactory; +use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; +use Bitrix24\SDK\Application\Contracts\ContactPersons\Exceptions\ContactPersonNotFoundException; +use libphonenumber\PhoneNumber; +use libphonenumber\PhoneNumberUtil; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; @@ -27,10 +30,6 @@ use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Uid\Uuid; -use libphonenumber\PhoneNumberUtil; -use libphonenumber\PhoneNumber; -use Bitrix24\Lib\Tests\Functional\ContactPersons\Builders\ContactPersonBuilder; - /** * @internal @@ -39,7 +38,7 @@ class HandlerTest extends TestCase { /** - * @var \libphonenumber\PhoneNumberUtil + * @var PhoneNumberUtil */ public $phoneNumberUtil; @@ -73,6 +72,7 @@ public function testConfirmPhoneVerification(): void $contactPersonBuilder = new ContactPersonBuilder(); $externalId = Uuid::v7()->toRfc4122(); $bitrix24UserId = random_int(1, 1_000_000); + $uuidV7 = Uuid::v7(); $phoneNumber = $this->createPhoneNumber('+79991234567'); $contactPerson = $contactPersonBuilder @@ -81,8 +81,9 @@ public function testConfirmPhoneVerification(): void ->withComment('Test comment') ->withExternalId($externalId) ->withBitrix24UserId($bitrix24UserId) - ->withBitrix24PartnerId(Uuid::v7()) - ->build(); + ->withBitrix24PartnerId($uuidV7) + ->build() + ; $this->repository->save($contactPerson); $this->flusher->flush(); @@ -109,7 +110,8 @@ public function testConfirmPhoneVerificationFailsIfContactPersonNotFound(): void ->withExternalId($externalId) ->withBitrix24UserId($bitrix24UserId) ->withBitrix24PartnerId(Uuid::v7()) - ->build(); + ->build() + ; $this->repository->save($contactPerson); $this->flusher->flush(); @@ -137,7 +139,8 @@ public function testConfirmPhoneVerificationFailsOnPhoneMismatch(): void ->withExternalId($externalId) ->withBitrix24UserId($bitrix24UserId) ->withBitrix24PartnerId(Uuid::v7()) - ->build(); + ->build() + ; $this->repository->save($contactPerson); $this->flusher->flush(); @@ -153,6 +156,7 @@ public function testConfirmPhoneVerificationFailsOnPhoneMismatch(): void private function createPhoneNumber(string $number): PhoneNumber { $phoneNumberUtil = PhoneNumberUtil::getInstance(); + return $phoneNumberUtil->parse($number, 'RU'); } -} \ No newline at end of file +} From 67238ed4d51d9636df355862914229b1146897c4 Mon Sep 17 00:00:00 2001 From: kirill Date: Tue, 27 Jan 2026 23:25:35 +0300 Subject: [PATCH 25/31] Refactor and enhance test coverage for email and phone verification use cases; add data providers, streamline test logic, and improve contact person creation methods. --- .../InstallContactPerson/HandlerTest.php | 92 +++++++++- .../MarkEmailAsVerified/HandlerTest.php | 161 +++++------------- .../MarkMobilePhoneAsVerified/HandlerTest.php | 110 ++++++------ 3 files changed, 190 insertions(+), 173 deletions(-) diff --git a/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php b/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php index 3708d8a..2f4970d 100644 --- a/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php +++ b/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php @@ -34,6 +34,7 @@ use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumberUtil; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; @@ -92,7 +93,7 @@ public function testInstallContactPersonSuccess(): void $memberId = Uuid::v4()->toRfc4122(); $externalId = Uuid::v7()->toRfc4122(); - $bitrix24Account = (new Bitrix24AccountBuilder()) + $bitrix24Account = new Bitrix24AccountBuilder() ->withApplicationScope(new Scope(['crm'])) ->withStatus(Bitrix24AccountStatus::new) ->withApplicationToken($applicationToken) @@ -105,7 +106,7 @@ public function testInstallContactPersonSuccess(): void $this->bitrix24accountRepository->save($bitrix24Account); - $applicationInstallation = (new ApplicationInstallationBuilder()) + $applicationInstallation = new ApplicationInstallationBuilder() ->withApplicationStatus(new ApplicationStatus('F')) ->withPortalLicenseFamily(PortalLicenseFamily::free) ->withBitrix24AccountId($bitrix24Account->getId()) @@ -192,6 +193,93 @@ public function testInstallContactPersonWithWrongApplicationInstallationId(): vo ); } + #[Test] + public function testInstallContactPersonWithInvalidEmail(): void + { + // Подготовим входные данные контакта + $contactPersonBuilder = new ContactPersonBuilder(); + $contactPerson = $contactPersonBuilder + ->withEmail('invalid-email') + ->build() + ; + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid email format.'); + + new Command( + Uuid::v7(), + $contactPerson->getFullName(), + 1, + $contactPerson->getUserAgentInfo(), + $contactPerson->getEmail(), + $contactPerson->getMobilePhone(), + $contactPerson->getComment(), + $contactPerson->getExternalId(), + $contactPerson->getBitrix24PartnerId(), + ); + } + + #[Test] + #[DataProvider('invalidPhoneProvider')] + public function testInstallContactPersonWithInvalidPhone(string $phoneNumber, string $region): void + { + // Подготовка Bitrix24 аккаунта и установки приложения + $applicationToken = Uuid::v7()->toRfc4122(); + $memberId = Uuid::v7()->toRfc4122(); + + $bitrix24Account = new Bitrix24AccountBuilder() + ->withApplicationToken($applicationToken) + ->withMemberId($memberId) + ->build() + ; + $this->bitrix24accountRepository->save($bitrix24Account); + + $applicationInstallation = new ApplicationInstallationBuilder() + ->withBitrix24AccountId($bitrix24Account->getId()) + ->withApplicationToken($applicationToken) + ->withApplicationStatus(new ApplicationStatus('F')) + ->withPortalLicenseFamily(PortalLicenseFamily::free) + ->build() + ; + $this->applicationInstallationRepository->save($applicationInstallation); + $this->flusher->flush(); + + $invalidPhoneNumber = $this->phoneNumberUtil->parse($phoneNumber, $region); + + $contactPersonBuilder = new ContactPersonBuilder(); + $contactPerson = $contactPersonBuilder + ->withEmail('john.doe@example.com') + ->withMobilePhoneNumber($invalidPhoneNumber) + ->build() + ; + + $this->handler->handle( + new Command( + $applicationInstallation->getId(), + $contactPerson->getFullName(), + $bitrix24Account->getBitrix24UserId(), + $contactPerson->getUserAgentInfo(), + $contactPerson->getEmail(), + $contactPerson->getMobilePhone(), + $contactPerson->getComment(), + $contactPerson->getExternalId(), + $contactPerson->getBitrix24PartnerId(), + ) + ); + + // Проверяем, что контакт не был создан + $foundInstallation = $this->applicationInstallationRepository->getById($applicationInstallation->getId()); + $this->assertNull($foundInstallation->getBitrix24PartnerId()); + } + + public static function invalidPhoneProvider(): array + { + return [ + 'invalid format' => ['123', 'RU'], + 'not mobile' => ['+74951234567', 'RU'], // Moscow landline + ]; + } + private function createPhoneNumber(string $number): PhoneNumber { $phoneNumberUtil = PhoneNumberUtil::getInstance(); diff --git a/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php index 940f870..176627f 100644 --- a/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/MarkEmailAsVerified/HandlerTest.php @@ -13,6 +13,7 @@ namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\MarkEmailAsVerified; +use Bitrix24\Lib\ContactPersons\Entity\ContactPerson; use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; use Bitrix24\Lib\ContactPersons\UseCase\MarkEmailAsVerified\Command; use Bitrix24\Lib\ContactPersons\UseCase\MarkEmailAsVerified\Handler; @@ -24,6 +25,7 @@ use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumberUtil; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; @@ -63,24 +65,7 @@ protected function setUp(): void #[Test] public function testConfirmEmailVerificationSuccess(): void { - $contactPersonBuilder = new ContactPersonBuilder(); - $externalId = Uuid::v7()->toRfc4122(); - $bitrix24UserId = random_int(1, 1_000_000); - - $contactPerson = $contactPersonBuilder - ->withEmail('john.doe@example.com') - ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) - ->withComment('Test comment') - ->withExternalId($externalId) - ->withBitrix24UserId($bitrix24UserId) - ->withBitrix24PartnerId(Uuid::v7()) - ->build() - ; - - $this->repository->save($contactPerson); - $this->flusher->flush(); - - $this->assertFalse($contactPerson->isEmailVerified()); + $contactPerson = $this->createContactPerson('john.doe@example.com'); $verifiedAt = new CarbonImmutable('2025-01-01T10:00:00+00:00'); $this->handler->handle( @@ -89,109 +74,61 @@ public function testConfirmEmailVerificationSuccess(): void $updatedContactPerson = $this->repository->getById($contactPerson->getId()); $this->assertTrue($updatedContactPerson->isEmailVerified()); - $this->assertNotNull($updatedContactPerson->getEmailVerifiedAt()); $this->assertSame($verifiedAt->toISOString(), $updatedContactPerson->getEmailVerifiedAt()?->toISOString()); } #[Test] - public function testConfirmEmailVerificationFailsIfContactPersonNotFound(): void - { - $contactPersonBuilder = new ContactPersonBuilder(); - $externalId = Uuid::v7()->toRfc4122(); - $bitrix24UserId = random_int(1, 1_000_000); - - $contactPerson = $contactPersonBuilder - ->withEmail('john.doe@example.com') - ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) - ->withComment('Test comment') - ->withExternalId($externalId) - ->withBitrix24UserId($bitrix24UserId) - ->withBitrix24PartnerId(Uuid::v7()) - ->build() - ; - - $this->repository->save($contactPerson); - $this->flusher->flush(); - - $this->assertFalse($contactPerson->isEmailVerified()); - - $this->expectException(ContactPersonNotFoundException::class); - $this->handler->handle(new Command(Uuid::v7(), 'john.doe@example.com')); + #[DataProvider('invalidMarkEmailVerificationProvider')] + public function testConfirmEmailVerificationFails( + bool $useRealContactId, + string $emailInCommand, + ?string $expectedExceptionClass = null + ): void { + $contactPerson = $this->createContactPerson('john.doe@example.com'); + $contactId = $useRealContactId ? $contactPerson->getId() : Uuid::v7(); + + if (null !== $expectedExceptionClass) { + $this->expectException($expectedExceptionClass); + } + + $this->handler->handle(new Command($contactId, $emailInCommand)); + + if (null === $expectedExceptionClass) { + // Если исключение не ожидалось (например, при несовпадении email), проверяем, что статус не изменился + $reloaded = $this->repository->getById($contactPerson->getId()); + $this->assertFalse($reloaded->isEmailVerified()); + } } - #[Test] - public function testConfirmEmailVerificationFailsIfEmailMismatch(): void + public static function invalidMarkEmailVerificationProvider(): array { - $contactPersonBuilder = new ContactPersonBuilder(); - $externalId = Uuid::v7()->toRfc4122(); - $bitrix24UserId = random_int(1, 1_000_000); - - $contactPerson = $contactPersonBuilder - ->withEmail('john.doe@example.com') - ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) - ->withComment('Test comment') - ->withExternalId($externalId) - ->withBitrix24UserId($bitrix24UserId) - ->withBitrix24PartnerId(Uuid::v7()) - ->build() - ; - - $this->repository->save($contactPerson); - $this->flusher->flush(); - - // We no longer throw an exception when the email doesn't match — we only log it and make no changes. - $this->handler->handle( - new Command($contactPerson->getId(), 'another.email@example.com') - ); - - // Проверяем, что верификация не произошла - $reloaded = $this->repository->getById($contactPerson->getId()); - $this->assertFalse($reloaded->isEmailVerified()); - } - - #[Test] - public function testConfirmEmailVerificationFailsIfEntityHasNoEmailButCommandProvidesOne(): void - { - $contactPersonBuilder = new ContactPersonBuilder(); - $externalId = Uuid::v7()->toRfc4122(); - $bitrix24UserId = random_int(1, 1_000_000); - - // Не задаём email в сущности (не вызываем withEmail) - $contactPerson = $contactPersonBuilder - ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) - ->withComment('Test comment') - ->withExternalId($externalId) - ->withBitrix24UserId($bitrix24UserId) - ->withBitrix24PartnerId(Uuid::v7()) - ->build() - ; - - $this->repository->save($contactPerson); - $this->flusher->flush(); - - // We no longer throw an exception when the email doesn't match — we only log it and make no changes. - $this->handler->handle( - new Command($contactPerson->getId(), 'john.doe@example.com') - ); - - // Проверяем, что верификация не произошла - $reloaded = $this->repository->getById($contactPerson->getId()); - $this->assertFalse($reloaded->isEmailVerified()); + return [ + 'contact person not found' => [ + 'useRealContactId' => false, + 'emailInCommand' => 'john.doe@example.com', + 'expectedExceptionClass' => ContactPersonNotFoundException::class, + ], + 'email mismatch' => [ + 'useRealContactId' => true, + 'emailInCommand' => 'another.email@example.com', + 'expectedExceptionClass' => null, + ], + 'invalid email format' => [ + 'useRealContactId' => true, + 'emailInCommand' => 'not-an-email', + 'expectedExceptionClass' => \InvalidArgumentException::class, + ], + ]; } - #[Test] - public function testConfirmEmailVerificationFailsIfInvalidEmailProvided(): void + private function createContactPerson(string $email): ContactPerson { - $contactPersonBuilder = new ContactPersonBuilder(); - $externalId = Uuid::v7()->toRfc4122(); - $bitrix24UserId = random_int(1, 1_000_000); - - $contactPerson = $contactPersonBuilder - ->withEmail('john.doe@example.com') + $contactPerson = (new ContactPersonBuilder()) + ->withEmail($email) ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) ->withComment('Test comment') - ->withExternalId($externalId) - ->withBitrix24UserId($bitrix24UserId) + ->withExternalId(Uuid::v7()->toRfc4122()) + ->withBitrix24UserId(random_int(1, 1_000_000)) ->withBitrix24PartnerId(Uuid::v7()) ->build() ; @@ -199,11 +136,7 @@ public function testConfirmEmailVerificationFailsIfInvalidEmailProvided(): void $this->repository->save($contactPerson); $this->flusher->flush(); - $this->expectException(\InvalidArgumentException::class); - // An invalid email should fail during validation in the command constructor. - $this->handler->handle( - new Command($contactPerson->getId(), 'not-an-email') - ); + return $contactPerson; } private function createPhoneNumber(string $number): PhoneNumber diff --git a/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php index 9d07882..27a572d 100644 --- a/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php @@ -13,6 +13,7 @@ namespace Bitrix24\Lib\Tests\Functional\ContactPersons\UseCase\MarkMobilePhoneAsVerified; +use Bitrix24\Lib\ContactPersons\Entity\ContactPerson; use Bitrix24\Lib\ContactPersons\Infrastructure\Doctrine\ContactPersonRepository; use Bitrix24\Lib\ContactPersons\UseCase\MarkMobilePhoneAsVerified\Command; use Bitrix24\Lib\ContactPersons\UseCase\MarkMobilePhoneAsVerified\Handler; @@ -23,6 +24,7 @@ use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumberUtil; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; @@ -69,24 +71,8 @@ protected function setUp(): void #[Test] public function testConfirmPhoneVerification(): void { - $contactPersonBuilder = new ContactPersonBuilder(); - $externalId = Uuid::v7()->toRfc4122(); - $bitrix24UserId = random_int(1, 1_000_000); - $uuidV7 = Uuid::v7(); $phoneNumber = $this->createPhoneNumber('+79991234567'); - - $contactPerson = $contactPersonBuilder - ->withEmail('john.doe@example.com') - ->withMobilePhoneNumber($phoneNumber) - ->withComment('Test comment') - ->withExternalId($externalId) - ->withBitrix24UserId($bitrix24UserId) - ->withBitrix24PartnerId($uuidV7) - ->build() - ; - - $this->repository->save($contactPerson); - $this->flusher->flush(); + $contactPerson = $this->createContactPerson($phoneNumber); $this->assertFalse($contactPerson->isMobilePhoneVerified()); @@ -97,47 +83,62 @@ public function testConfirmPhoneVerification(): void } #[Test] - public function testConfirmPhoneVerificationFailsIfContactPersonNotFound(): void - { - $contactPersonBuilder = new ContactPersonBuilder(); - $externalId = Uuid::v7()->toRfc4122(); - $bitrix24UserId = random_int(1, 1_000_000); - - $contactPerson = $contactPersonBuilder - ->withEmail('john.doe@example.com') - ->withMobilePhoneNumber($this->createPhoneNumber('+79991234567')) - ->withComment('Test comment') - ->withExternalId($externalId) - ->withBitrix24UserId($bitrix24UserId) - ->withBitrix24PartnerId(Uuid::v7()) - ->build() - ; - - $this->repository->save($contactPerson); - $this->flusher->flush(); - - $this->assertFalse($contactPerson->isMobilePhoneVerified()); - - $this->expectException(ContactPersonNotFoundException::class); - $this->handler->handle(new Command(Uuid::v7(), $this->createPhoneNumber('+79991234567'))); + #[DataProvider('invalidPhoneVerificationProvider')] + public function testConfirmPhoneVerificationFails( + bool $useRealContactId, + string $phoneNumberInCommand, + ?string $expectedExceptionClass = null + ): void { + $realPhoneNumber = $this->createPhoneNumber('+79991234567'); + $contactPerson = $this->createContactPerson($realPhoneNumber); + + $contactId = $useRealContactId ? $contactPerson->getId() : Uuid::v7(); + + if (null !== $expectedExceptionClass) { + $this->expectException($expectedExceptionClass); + } + + $commandPhone = $this->createPhoneNumber($phoneNumberInCommand); + $this->handler->handle(new Command($contactId, $commandPhone)); + + if (null === $expectedExceptionClass) { + // Если исключение не ожидалось (например, при несовпадении телефона), проверяем, что статус не изменился + $reloaded = $this->repository->getById($contactPerson->getId()); + $this->assertFalse($reloaded->isMobilePhoneVerified()); + } } - #[Test] - public function testConfirmPhoneVerificationFailsOnPhoneMismatch(): void + public static function invalidPhoneVerificationProvider(): array { - $contactPersonBuilder = new ContactPersonBuilder(); - $externalId = Uuid::v7()->toRfc4122(); - $bitrix24UserId = random_int(1, 1_000_000); - - $phoneNumber = $this->createPhoneNumber('+79991234567'); - $expectedDifferentPhone = $this->createPhoneNumber('+79990000000'); + return [ + 'contact person not found' => [ + 'useRealContactId' => false, + 'phoneNumberInCommand' => '+79991234567', + 'expectedExceptionClass' => ContactPersonNotFoundException::class, + ], + 'phone mismatch' => [ + 'useRealContactId' => true, + 'phoneNumberInCommand' => '+79990000000', + 'expectedExceptionClass' => null, + ], + 'invalid phone format' => [ + 'useRealContactId' => true, + 'phoneNumberInCommand' => '123', + 'expectedExceptionClass' => null, // Handler catches it or Command validates it? + // Actually Command doesn't validate phone format in this package, it's a PhoneNumber object. + // In Handler.php there's no guard for phone in MarkMobilePhoneAsVerified, it just compares them. + ], + ]; + } - $contactPerson = $contactPersonBuilder + private function createContactPerson(PhoneNumber $phoneNumber): ContactPerson + { + $contactPerson = (new ContactPersonBuilder()) ->withEmail('john.doe@example.com') ->withMobilePhoneNumber($phoneNumber) ->withComment('Test comment') - ->withExternalId($externalId) - ->withBitrix24UserId($bitrix24UserId) + ->withExternalId(Uuid::v7()->toRfc4122()) + ->withBitrix24UserId(random_int(1, 1_000_000)) ->withBitrix24PartnerId(Uuid::v7()) ->build() ; @@ -145,12 +146,7 @@ public function testConfirmPhoneVerificationFailsOnPhoneMismatch(): void $this->repository->save($contactPerson); $this->flusher->flush(); - // No exception should be thrown; phone mismatch is only logged - $this->handler->handle(new Command($contactPerson->getId(), $expectedDifferentPhone)); - - // Ensure mobile phone is still not verified - $reloaded = $this->repository->getById($contactPerson->getId()); - $this->assertFalse($reloaded->isMobilePhoneVerified()); + return $contactPerson; } private function createPhoneNumber(string $number): PhoneNumber From d8fe02b2552acbd17b2ec82ee679552f37a2a417 Mon Sep 17 00:00:00 2001 From: kirill Date: Wed, 28 Jan 2026 23:20:34 +0300 Subject: [PATCH 26/31] Add comprehensive unit tests for various `Command` classes, improve email validation logic, and enhance coverage for `ChangeProfile` and verification use cases. --- .../UseCase/ChangeProfile/Command.php | 2 +- .../InstallContactPerson/CommandTest.php | 135 ++++++++++++++++++ .../UnlinkContactPerson/CommandTest.php | 52 +++++++ .../Entity/ContactPersonTest.php | 60 ++++++++ .../UseCase/ChangeProfile/CommandTest.php | 65 +++++++++ .../MarkEmailAsVerified/CommandTest.php | 76 ++++++++++ .../MarkMobilePhoneAsVerified/CommandTest.php | 54 +++++++ 7 files changed, 443 insertions(+), 1 deletion(-) create mode 100644 tests/Unit/ApplicationInstallations/UseCase/InstallContactPerson/CommandTest.php create mode 100644 tests/Unit/ApplicationInstallations/UseCase/UnlinkContactPerson/CommandTest.php create mode 100644 tests/Unit/ContactPersons/Entity/ContactPersonTest.php create mode 100644 tests/Unit/ContactPersons/UseCase/ChangeProfile/CommandTest.php create mode 100644 tests/Unit/ContactPersons/UseCase/MarkEmailAsVerified/CommandTest.php create mode 100644 tests/Unit/ContactPersons/UseCase/MarkMobilePhoneAsVerified/CommandTest.php diff --git a/src/ContactPersons/UseCase/ChangeProfile/Command.php b/src/ContactPersons/UseCase/ChangeProfile/Command.php index 123f34e..a23e345 100644 --- a/src/ContactPersons/UseCase/ChangeProfile/Command.php +++ b/src/ContactPersons/UseCase/ChangeProfile/Command.php @@ -22,7 +22,7 @@ public function __construct( private function validate(): void { - if ('' === trim($this->email) && !filter_var($this->email, FILTER_VALIDATE_EMAIL)) { + if ('' === trim($this->email) || !filter_var($this->email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email format.'); } } diff --git a/tests/Unit/ApplicationInstallations/UseCase/InstallContactPerson/CommandTest.php b/tests/Unit/ApplicationInstallations/UseCase/InstallContactPerson/CommandTest.php new file mode 100644 index 0000000..0494deb --- /dev/null +++ b/tests/Unit/ApplicationInstallations/UseCase/InstallContactPerson/CommandTest.php @@ -0,0 +1,135 @@ +applicationInstallationId); + self::assertSame($fullName, $command->fullName); + self::assertSame($bitrix24UserId, $command->bitrix24UserId); + self::assertSame($userAgentInfo, $command->userAgentInfo); + self::assertSame($email, $command->email); + self::assertSame($mobilePhoneNumber, $command->mobilePhoneNumber); + self::assertSame($comment, $command->comment); + self::assertSame($externalId, $command->externalId); + self::assertSame($bitrix24PartnerId, $command->bitrix24PartnerId); + } + + #[Test] + #[DataProvider('invalidEmailProvider')] + public function testInvalidEmailThrows(string $email): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid email format.'); + + new Command( + Uuid::v7(), + new FullName('John Doe'), + 123, + new UserAgentInfo(null), + $email, + null, + null, + null, + null + ); + } + + public static function invalidEmailProvider(): array + { + return [ + 'empty' => [''], + 'spaces' => [' '], + 'invalid format' => ['not-an-email'], + ]; + } + + #[Test] + public function testEmptyExternalIdThrows(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('External ID cannot be empty if provided.'); + + new Command( + Uuid::v7(), + new FullName('John Doe'), + 123, + new UserAgentInfo(null), + null, + null, + null, + ' ', + null + ); + } + + #[Test] + #[DataProvider('invalidUserIdProvider')] + public function testInvalidBitrix24UserIdThrows(int $userId): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Bitrix24 User ID must be a positive integer.'); + + new Command( + Uuid::v7(), + new FullName('John Doe'), + $userId, + new UserAgentInfo(null), + null, + null, + null, + null, + null + ); + } + + public static function invalidUserIdProvider(): array + { + return [ + 'zero' => [0], + 'negative' => [-1], + ]; + } +} diff --git a/tests/Unit/ApplicationInstallations/UseCase/UnlinkContactPerson/CommandTest.php b/tests/Unit/ApplicationInstallations/UseCase/UnlinkContactPerson/CommandTest.php new file mode 100644 index 0000000..ce28753 --- /dev/null +++ b/tests/Unit/ApplicationInstallations/UseCase/UnlinkContactPerson/CommandTest.php @@ -0,0 +1,52 @@ +contactPersonId); + self::assertSame($applicationInstallationId, $command->applicationInstallationId); + self::assertSame($comment, $command->comment); + } + + #[Test] + public function testValidCommandWithoutComment(): void + { + $contactPersonId = Uuid::v7(); + $applicationInstallationId = Uuid::v7(); + + $command = new Command( + $contactPersonId, + $applicationInstallationId + ); + + self::assertSame($contactPersonId, $command->contactPersonId); + self::assertSame($applicationInstallationId, $command->applicationInstallationId); + self::assertNull($command->comment); + } +} diff --git a/tests/Unit/ContactPersons/Entity/ContactPersonTest.php b/tests/Unit/ContactPersons/Entity/ContactPersonTest.php new file mode 100644 index 0000000..ec93af4 --- /dev/null +++ b/tests/Unit/ContactPersons/Entity/ContactPersonTest.php @@ -0,0 +1,60 @@ +createDummyPhone() + ); + + self::assertSame('john.doe@example.com', $command->email); + } + + #[Test] + #[DataProvider('invalidEmailProvider')] + public function testInvalidEmailThrows(string $email): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid email format.'); + + new Command( + Uuid::v7(), + new FullName('John Doe'), + $email, + $this->createDummyPhone() + ); + } + + public static function invalidEmailProvider(): array + { + return [ + 'empty' => [''], + 'spaces' => [' '], + 'invalid format' => ['not-an-email'], + ]; + } + + private function createDummyPhone(): PhoneNumber + { + // Нам не важно содержимое, т.к. Command телефон не валидирует. + return new PhoneNumber(); + } +} diff --git a/tests/Unit/ContactPersons/UseCase/MarkEmailAsVerified/CommandTest.php b/tests/Unit/ContactPersons/UseCase/MarkEmailAsVerified/CommandTest.php new file mode 100644 index 0000000..2b68b38 --- /dev/null +++ b/tests/Unit/ContactPersons/UseCase/MarkEmailAsVerified/CommandTest.php @@ -0,0 +1,76 @@ +contactPersonId); + self::assertSame($email, $command->email); + self::assertSame($verifiedAt, $command->emailVerifiedAt); + } + + #[Test] + public function testValidCommandWithoutDate(): void + { + $id = Uuid::v7(); + $email = 'john.doe@example.com'; + + $command = new Command( + $id, + $email + ); + + self::assertEquals($id, $command->contactPersonId); + self::assertSame($email, $command->email); + self::assertNull($command->emailVerifiedAt); + } + + #[Test] + #[DataProvider('invalidEmailProvider')] + public function testInvalidEmailThrows(string $email): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid email format.'); + + new Command( + Uuid::v7(), + $email + ); + } + + public static function invalidEmailProvider(): array + { + return [ + 'empty' => [''], + 'spaces' => [' '], + 'invalid format' => ['not-an-email'], + ]; + } +} diff --git a/tests/Unit/ContactPersons/UseCase/MarkMobilePhoneAsVerified/CommandTest.php b/tests/Unit/ContactPersons/UseCase/MarkMobilePhoneAsVerified/CommandTest.php new file mode 100644 index 0000000..5b8d181 --- /dev/null +++ b/tests/Unit/ContactPersons/UseCase/MarkMobilePhoneAsVerified/CommandTest.php @@ -0,0 +1,54 @@ +contactPersonId); + self::assertSame($phone, $command->phone); + self::assertSame($phoneVerifiedAt, $command->phoneVerifiedAt); + } + + #[Test] + public function testValidCommandWithoutDate(): void + { + $contactPersonId = Uuid::v7(); + $phone = new PhoneNumber(); + + $command = new Command( + $contactPersonId, + $phone + ); + + self::assertSame($contactPersonId, $command->contactPersonId); + self::assertSame($phone, $command->phone); + self::assertNull($command->phoneVerifiedAt); + } +} From 149819550048d9c5903edb89ff67b33c2ec20eb4 Mon Sep 17 00:00:00 2001 From: kirill Date: Tue, 3 Feb 2026 00:23:41 +0300 Subject: [PATCH 27/31] Remove outdated unit tests for unused `UnlinkContactPerson` and `MarkMobilePhoneAsVerified` command classes; refactor `InstallContactPerson` and `MarkEmailAsVerified` tests with data providers, improve validation logic, and update PHP version constraint in `composer.json`. --- composer.json | 2 +- .../UseCase/ChangeProfile/Command.php | 2 +- .../InstallContactPerson/HandlerTest.php | 8 +- .../InstallContactPerson/CommandTest.php | 183 ++++++++++-------- .../UnlinkContactPerson/CommandTest.php | 52 ----- .../UseCase/ChangeProfile/CommandTest.php | 72 ++++--- .../MarkEmailAsVerified/CommandTest.php | 85 ++++---- .../MarkMobilePhoneAsVerified/CommandTest.php | 54 ------ 8 files changed, 192 insertions(+), 266 deletions(-) delete mode 100644 tests/Unit/ApplicationInstallations/UseCase/UnlinkContactPerson/CommandTest.php delete mode 100644 tests/Unit/ContactPersons/UseCase/MarkMobilePhoneAsVerified/CommandTest.php diff --git a/composer.json b/composer.json index b38fce0..dcbf29e 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ } }, "require": { - "php": "8.3.* || 8.4.*", + "php": "^8.4", "ext-json": "*", "ext-curl": "*", "ext-bcmath": "*", diff --git a/src/ContactPersons/UseCase/ChangeProfile/Command.php b/src/ContactPersons/UseCase/ChangeProfile/Command.php index a23e345..c3e8c6a 100644 --- a/src/ContactPersons/UseCase/ChangeProfile/Command.php +++ b/src/ContactPersons/UseCase/ChangeProfile/Command.php @@ -22,7 +22,7 @@ public function __construct( private function validate(): void { - if ('' === trim($this->email) || !filter_var($this->email, FILTER_VALIDATE_EMAIL)) { + if ('' !== trim($this->email) && !filter_var($this->email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email format.'); } } diff --git a/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php b/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php index 2f4970d..3ea95dc 100644 --- a/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php +++ b/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php @@ -93,7 +93,7 @@ public function testInstallContactPersonSuccess(): void $memberId = Uuid::v4()->toRfc4122(); $externalId = Uuid::v7()->toRfc4122(); - $bitrix24Account = new Bitrix24AccountBuilder() + $bitrix24Account = (new Bitrix24AccountBuilder()) ->withApplicationScope(new Scope(['crm'])) ->withStatus(Bitrix24AccountStatus::new) ->withApplicationToken($applicationToken) @@ -106,7 +106,7 @@ public function testInstallContactPersonSuccess(): void $this->bitrix24accountRepository->save($bitrix24Account); - $applicationInstallation = new ApplicationInstallationBuilder() + $applicationInstallation = (new ApplicationInstallationBuilder()) ->withApplicationStatus(new ApplicationStatus('F')) ->withPortalLicenseFamily(PortalLicenseFamily::free) ->withBitrix24AccountId($bitrix24Account->getId()) @@ -227,14 +227,14 @@ public function testInstallContactPersonWithInvalidPhone(string $phoneNumber, st $applicationToken = Uuid::v7()->toRfc4122(); $memberId = Uuid::v7()->toRfc4122(); - $bitrix24Account = new Bitrix24AccountBuilder() + $bitrix24Account = (new Bitrix24AccountBuilder()) ->withApplicationToken($applicationToken) ->withMemberId($memberId) ->build() ; $this->bitrix24accountRepository->save($bitrix24Account); - $applicationInstallation = new ApplicationInstallationBuilder() + $applicationInstallation = (new ApplicationInstallationBuilder()) ->withBitrix24AccountId($bitrix24Account->getId()) ->withApplicationToken($applicationToken) ->withApplicationStatus(new ApplicationStatus('F')) diff --git a/tests/Unit/ApplicationInstallations/UseCase/InstallContactPerson/CommandTest.php b/tests/Unit/ApplicationInstallations/UseCase/InstallContactPerson/CommandTest.php index 0494deb..5df7c53 100644 --- a/tests/Unit/ApplicationInstallations/UseCase/InstallContactPerson/CommandTest.php +++ b/tests/Unit/ApplicationInstallations/UseCase/InstallContactPerson/CommandTest.php @@ -22,17 +22,22 @@ final class CommandTest extends TestCase { #[Test] - public function testValidCommand(): void - { - $applicationInstallationId = Uuid::v7(); - $fullName = new FullName('John Doe'); - $bitrix24UserId = 123; - $userAgentInfo = new UserAgentInfo(IP::factory('127.0.0.1')); - $email = 'john.doe@example.com'; - $mobilePhoneNumber = new PhoneNumber(); - $comment = 'Test comment'; - $externalId = 'ext-123'; - $bitrix24PartnerId = Uuid::v7(); + #[DataProvider('commandDataProvider')] + public function testCommand( + Uuid $applicationInstallationId, + FullName $fullName, + int $bitrix24UserId, + UserAgentInfo $userAgentInfo, + ?string $email = null, + ?PhoneNumber $mobilePhoneNumber = null, + ?string $comment = null, + ?string $externalId = null, + ?Uuid $bitrix24PartnerId = null, + ?string $expectedException = null, + ): void { + if (null !== $expectedException) { + $this->expectException($expectedException); + } $command = new Command( $applicationInstallationId, @@ -57,79 +62,95 @@ public function testValidCommand(): void self::assertSame($bitrix24PartnerId, $command->bitrix24PartnerId); } - #[Test] - #[DataProvider('invalidEmailProvider')] - public function testInvalidEmailThrows(string $email): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid email format.'); - - new Command( - Uuid::v7(), - new FullName('John Doe'), - 123, - new UserAgentInfo(null), - $email, - null, - null, - null, - null - ); - } - - public static function invalidEmailProvider(): array - { - return [ - 'empty' => [''], - 'spaces' => [' '], - 'invalid format' => ['not-an-email'], - ]; - } - - #[Test] - public function testEmptyExternalIdThrows(): void + public static function commandDataProvider(): array { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('External ID cannot be empty if provided.'); - - new Command( - Uuid::v7(), - new FullName('John Doe'), - 123, - new UserAgentInfo(null), - null, - null, - null, - ' ', - null - ); - } - - #[Test] - #[DataProvider('invalidUserIdProvider')] - public function testInvalidBitrix24UserIdThrows(int $userId): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Bitrix24 User ID must be a positive integer.'); - - new Command( - Uuid::v7(), - new FullName('John Doe'), - $userId, - new UserAgentInfo(null), - null, - null, - null, - null, - null - ); - } + $fullName = new FullName('John Doe'); + $userAgentInfo = new UserAgentInfo(null); - public static function invalidUserIdProvider(): array - { return [ - 'zero' => [0], - 'negative' => [-1], + 'valid data' => [ + Uuid::v7(), + $fullName, + 123, + $userAgentInfo, + 'john.doe@example.com', + new PhoneNumber(), + 'Test comment', + 'ext-123', + Uuid::v7(), + ], + 'invalid email: empty' => [ + Uuid::v7(), + $fullName, + 123, + $userAgentInfo, + '', + null, + null, + null, + null, + \InvalidArgumentException::class, + ], + 'invalid email: spaces' => [ + Uuid::v7(), + $fullName, + 123, + $userAgentInfo, + ' ', + null, + null, + null, + null, + \InvalidArgumentException::class, + ], + 'invalid email: format' => [ + Uuid::v7(), + $fullName, + 123, + $userAgentInfo, + 'not-an-email', + null, + null, + null, + null, + \InvalidArgumentException::class, + ], + 'invalid external id: empty string' => [ + Uuid::v7(), + $fullName, + 123, + $userAgentInfo, + null, + null, + null, + ' ', + null, + \InvalidArgumentException::class, + ], + 'invalid user id: zero' => [ + Uuid::v7(), + $fullName, + 0, + $userAgentInfo, + null, + null, + null, + null, + null, + \InvalidArgumentException::class, + ], + 'invalid user id: negative' => [ + Uuid::v7(), + $fullName, + -1, + $userAgentInfo, + null, + null, + null, + null, + null, + \InvalidArgumentException::class, + ], ]; } } diff --git a/tests/Unit/ApplicationInstallations/UseCase/UnlinkContactPerson/CommandTest.php b/tests/Unit/ApplicationInstallations/UseCase/UnlinkContactPerson/CommandTest.php deleted file mode 100644 index ce28753..0000000 --- a/tests/Unit/ApplicationInstallations/UseCase/UnlinkContactPerson/CommandTest.php +++ /dev/null @@ -1,52 +0,0 @@ -contactPersonId); - self::assertSame($applicationInstallationId, $command->applicationInstallationId); - self::assertSame($comment, $command->comment); - } - - #[Test] - public function testValidCommandWithoutComment(): void - { - $contactPersonId = Uuid::v7(); - $applicationInstallationId = Uuid::v7(); - - $command = new Command( - $contactPersonId, - $applicationInstallationId - ); - - self::assertSame($contactPersonId, $command->contactPersonId); - self::assertSame($applicationInstallationId, $command->applicationInstallationId); - self::assertNull($command->comment); - } -} diff --git a/tests/Unit/ContactPersons/UseCase/ChangeProfile/CommandTest.php b/tests/Unit/ContactPersons/UseCase/ChangeProfile/CommandTest.php index 314a9e9..241fb14 100644 --- a/tests/Unit/ContactPersons/UseCase/ChangeProfile/CommandTest.php +++ b/tests/Unit/ContactPersons/UseCase/ChangeProfile/CommandTest.php @@ -21,45 +21,55 @@ final class CommandTest extends TestCase { #[Test] - public function testValidCommand(): void - { + #[DataProvider('commandDataProvider')] + public function testCommand( + Uuid $contactPersonId, + FullName $fullName, + string $email, + PhoneNumber $mobilePhoneNumber, + ?string $expectedException = null, + ): void { + if (null !== $expectedException) { + $this->expectException($expectedException); + } + $command = new Command( - Uuid::v7(), - new FullName('John Doe'), - 'john.doe@example.com', - $this->createDummyPhone() + $contactPersonId, + $fullName, + $email, + $mobilePhoneNumber ); - self::assertSame('john.doe@example.com', $command->email); + self::assertEquals($contactPersonId, $command->contactPersonId); + self::assertEquals($fullName, $command->fullName); + self::assertSame($email, $command->email); + self::assertEquals($mobilePhoneNumber, $command->mobilePhoneNumber); } - #[Test] - #[DataProvider('invalidEmailProvider')] - public function testInvalidEmailThrows(string $email): void + public static function commandDataProvider(): array { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid email format.'); - - new Command( - Uuid::v7(), - new FullName('John Doe'), - $email, - $this->createDummyPhone() - ); - } + $fullName = new FullName('John Doe'); - public static function invalidEmailProvider(): array - { return [ - 'empty' => [''], - 'spaces' => [' '], - 'invalid format' => ['not-an-email'], + 'valid data' => [ + Uuid::v7(), + $fullName, + 'john.doe@example.com', + new PhoneNumber(), + ], + 'empty email is valid' => [ + Uuid::v7(), + $fullName, + '', + new PhoneNumber(), + ], + 'invalid email format' => [ + Uuid::v7(), + $fullName, + 'not-an-email', + new PhoneNumber(), + InvalidArgumentException::class, + ], ]; } - - private function createDummyPhone(): PhoneNumber - { - // Нам не важно содержимое, т.к. Command телефон не валидирует. - return new PhoneNumber(); - } } diff --git a/tests/Unit/ContactPersons/UseCase/MarkEmailAsVerified/CommandTest.php b/tests/Unit/ContactPersons/UseCase/MarkEmailAsVerified/CommandTest.php index 2b68b38..5c92318 100644 --- a/tests/Unit/ContactPersons/UseCase/MarkEmailAsVerified/CommandTest.php +++ b/tests/Unit/ContactPersons/UseCase/MarkEmailAsVerified/CommandTest.php @@ -19,58 +19,59 @@ final class CommandTest extends TestCase { #[Test] - public function testValidCommand(): void - { - $id = Uuid::v7(); - $email = 'john.doe@example.com'; - $verifiedAt = new CarbonImmutable(); + #[DataProvider('commandDataProvider')] + public function testCommand( + Uuid $contactPersonId, + string $email, + ?CarbonImmutable $emailVerifiedAt = null, + ?string $expectedException = null, + ): void { + if (null !== $expectedException) { + $this->expectException($expectedException); + } $command = new Command( - $id, + $contactPersonId, $email, - $verifiedAt - ); - - self::assertEquals($id, $command->contactPersonId); - self::assertSame($email, $command->email); - self::assertSame($verifiedAt, $command->emailVerifiedAt); - } - - #[Test] - public function testValidCommandWithoutDate(): void - { - $id = Uuid::v7(); - $email = 'john.doe@example.com'; - - $command = new Command( - $id, - $email + $emailVerifiedAt ); - self::assertEquals($id, $command->contactPersonId); + self::assertEquals($contactPersonId, $command->contactPersonId); self::assertSame($email, $command->email); - self::assertNull($command->emailVerifiedAt); - } - - #[Test] - #[DataProvider('invalidEmailProvider')] - public function testInvalidEmailThrows(string $email): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid email format.'); - - new Command( - Uuid::v7(), - $email - ); + self::assertEquals($emailVerifiedAt, $command->emailVerifiedAt); } - public static function invalidEmailProvider(): array + public static function commandDataProvider(): array { return [ - 'empty' => [''], - 'spaces' => [' '], - 'invalid format' => ['not-an-email'], + 'valid data' => [ + Uuid::v7(), + 'john.doe@example.com', + new CarbonImmutable(), + ], + 'valid data without date' => [ + Uuid::v7(), + 'john.doe@example.com', + null, + ], + 'invalid email: empty' => [ + Uuid::v7(), + '', + null, + \InvalidArgumentException::class, + ], + 'invalid email: spaces' => [ + Uuid::v7(), + ' ', + null, + \InvalidArgumentException::class, + ], + 'invalid email: format' => [ + Uuid::v7(), + 'not-an-email', + null, + \InvalidArgumentException::class, + ], ]; } } diff --git a/tests/Unit/ContactPersons/UseCase/MarkMobilePhoneAsVerified/CommandTest.php b/tests/Unit/ContactPersons/UseCase/MarkMobilePhoneAsVerified/CommandTest.php deleted file mode 100644 index 5b8d181..0000000 --- a/tests/Unit/ContactPersons/UseCase/MarkMobilePhoneAsVerified/CommandTest.php +++ /dev/null @@ -1,54 +0,0 @@ -contactPersonId); - self::assertSame($phone, $command->phone); - self::assertSame($phoneVerifiedAt, $command->phoneVerifiedAt); - } - - #[Test] - public function testValidCommandWithoutDate(): void - { - $contactPersonId = Uuid::v7(); - $phone = new PhoneNumber(); - - $command = new Command( - $contactPersonId, - $phone - ); - - self::assertSame($contactPersonId, $command->contactPersonId); - self::assertSame($phone, $command->phone); - self::assertNull($command->phoneVerifiedAt); - } -} From e22af57e55c770b401637b9e8938902efcad2c30 Mon Sep 17 00:00:00 2001 From: kirill Date: Tue, 3 Feb 2026 22:58:52 +0300 Subject: [PATCH 28/31] Refactor tests for improved consistency: rename variables, enhance assertions in `HandlerTest`, adopt uniform naming in `CommandTest`, and override method in `ContactPersonTest`. --- .../InstallContactPerson/HandlerTest.php | 17 ++++++++++++----- .../MarkMobilePhoneAsVerified/HandlerTest.php | 6 +++--- .../ContactPersons/Entity/ContactPersonTest.php | 1 + .../UseCase/ChangeProfile/CommandTest.php | 6 +++--- .../UseCase/MarkEmailAsVerified/CommandTest.php | 6 +++--- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php b/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php index 3ea95dc..076c62b 100644 --- a/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php +++ b/tests/Functional/ApplicationInstallations/UseCase/InstallContactPerson/HandlerTest.php @@ -154,11 +154,18 @@ public function testInstallContactPersonSuccess(): void $this->assertContains(ApplicationInstallationContactPersonLinkedEvent::class, $dispatchedEvents); $foundInstallation = $this->applicationInstallationRepository->getById($applicationInstallation->getId()); - $this->assertNotNull($foundInstallation->getContactPersonId()); - - $uuid = $foundInstallation->getContactPersonId(); - $foundContactPerson = $this->repository->getById($uuid); - $this->assertEquals($foundContactPerson->getId(), $uuid); + $contactPersonId = $foundInstallation->getContactPersonId(); + $this->assertNotNull($contactPersonId); + + $foundContactPerson = $this->repository->getById($contactPersonId); + $this->assertEquals($contactPersonId, $foundContactPerson->getId()); + $this->assertEquals($contactPerson->getEmail(), $foundContactPerson->getEmail()); + $this->assertEquals($contactPerson->getMobilePhone(), $foundContactPerson->getMobilePhone()); + $this->assertEquals($contactPerson->getFullName(), $foundContactPerson->getFullName()); + $this->assertEquals($contactPerson->getComment(), $foundContactPerson->getComment()); + $this->assertEquals($contactPerson->getExternalId(), $foundContactPerson->getExternalId()); + $this->assertEquals($contactPerson->getBitrix24UserId(), $foundContactPerson->getBitrix24UserId()); + $this->assertEquals($contactPerson->getBitrix24PartnerId(), $foundContactPerson->getBitrix24PartnerId()); } #[Test] diff --git a/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php b/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php index 27a572d..dfe0113 100644 --- a/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php +++ b/tests/Functional/ContactPersons/UseCase/MarkMobilePhoneAsVerified/HandlerTest.php @@ -98,8 +98,8 @@ public function testConfirmPhoneVerificationFails( $this->expectException($expectedExceptionClass); } - $commandPhone = $this->createPhoneNumber($phoneNumberInCommand); - $this->handler->handle(new Command($contactId, $commandPhone)); + $phoneNumber = $this->createPhoneNumber($phoneNumberInCommand); + $this->handler->handle(new Command($contactId, $phoneNumber)); if (null === $expectedExceptionClass) { // Если исключение не ожидалось (например, при несовпадении телефона), проверяем, что статус не изменился @@ -124,7 +124,7 @@ public static function invalidPhoneVerificationProvider(): array 'invalid phone format' => [ 'useRealContactId' => true, 'phoneNumberInCommand' => '123', - 'expectedExceptionClass' => null, // Handler catches it or Command validates it? + 'expectedExceptionClass' => null, // Actually Command doesn't validate phone format in this package, it's a PhoneNumber object. // In Handler.php there's no guard for phone in MarkMobilePhoneAsVerified, it just compares them. ], diff --git a/tests/Unit/ContactPersons/Entity/ContactPersonTest.php b/tests/Unit/ContactPersons/Entity/ContactPersonTest.php index ec93af4..03b9c38 100644 --- a/tests/Unit/ContactPersons/Entity/ContactPersonTest.php +++ b/tests/Unit/ContactPersons/Entity/ContactPersonTest.php @@ -22,6 +22,7 @@ */ class ContactPersonTest extends ContactPersonInterfaceTest { + #[\Override] protected function createContactPersonImplementation( Uuid $uuid, CarbonImmutable $createdAt, diff --git a/tests/Unit/ContactPersons/UseCase/ChangeProfile/CommandTest.php b/tests/Unit/ContactPersons/UseCase/ChangeProfile/CommandTest.php index 241fb14..43c81d1 100644 --- a/tests/Unit/ContactPersons/UseCase/ChangeProfile/CommandTest.php +++ b/tests/Unit/ContactPersons/UseCase/ChangeProfile/CommandTest.php @@ -23,7 +23,7 @@ final class CommandTest extends TestCase #[Test] #[DataProvider('commandDataProvider')] public function testCommand( - Uuid $contactPersonId, + Uuid $uuid, FullName $fullName, string $email, PhoneNumber $mobilePhoneNumber, @@ -34,13 +34,13 @@ public function testCommand( } $command = new Command( - $contactPersonId, + $uuid, $fullName, $email, $mobilePhoneNumber ); - self::assertEquals($contactPersonId, $command->contactPersonId); + self::assertEquals($uuid, $command->contactPersonId); self::assertEquals($fullName, $command->fullName); self::assertSame($email, $command->email); self::assertEquals($mobilePhoneNumber, $command->mobilePhoneNumber); diff --git a/tests/Unit/ContactPersons/UseCase/MarkEmailAsVerified/CommandTest.php b/tests/Unit/ContactPersons/UseCase/MarkEmailAsVerified/CommandTest.php index 5c92318..61c9c2b 100644 --- a/tests/Unit/ContactPersons/UseCase/MarkEmailAsVerified/CommandTest.php +++ b/tests/Unit/ContactPersons/UseCase/MarkEmailAsVerified/CommandTest.php @@ -21,7 +21,7 @@ final class CommandTest extends TestCase #[Test] #[DataProvider('commandDataProvider')] public function testCommand( - Uuid $contactPersonId, + Uuid $uuid, string $email, ?CarbonImmutable $emailVerifiedAt = null, ?string $expectedException = null, @@ -31,12 +31,12 @@ public function testCommand( } $command = new Command( - $contactPersonId, + $uuid, $email, $emailVerifiedAt ); - self::assertEquals($contactPersonId, $command->contactPersonId); + self::assertEquals($uuid, $command->contactPersonId); self::assertSame($email, $command->email); self::assertEquals($emailVerifiedAt, $command->emailVerifiedAt); } From de9ed5c2f4ca5985d30e870c22bf2cc904e4895e Mon Sep 17 00:00:00 2001 From: kirill Date: Tue, 3 Feb 2026 23:31:12 +0300 Subject: [PATCH 29/31] Improve email validation and trimming logic in `MarkEmailAsVerified` command and `ContactPerson` entity; prevent empty email values and reset verification status consistently. --- src/ContactPersons/Entity/ContactPerson.php | 5 +++++ .../UseCase/MarkEmailAsVerified/Command.php | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/ContactPersons/Entity/ContactPerson.php b/src/ContactPersons/Entity/ContactPerson.php index 8915fd1..ac1a3cb 100644 --- a/src/ContactPersons/Entity/ContactPerson.php +++ b/src/ContactPersons/Entity/ContactPerson.php @@ -159,6 +159,11 @@ public function getEmail(): ?string #[\Override] public function changeEmail(?string $email): void { + $email = null !== $email ? trim($email) : null; + if ('' === $email) { + $email = null; + } + $this->email = $email; $this->isEmailVerified = false; $this->emailVerifiedAt = null; diff --git a/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php b/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php index 68ad098..8ae5f91 100644 --- a/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php +++ b/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php @@ -19,7 +19,13 @@ public function __construct( private function validate(): void { - if (null !== $this->email && !filter_var($this->email, FILTER_VALIDATE_EMAIL)) { + $email = trim($this->email); + + if ('' === $email) { + throw new \InvalidArgumentException('Cannot confirm an empty email.'); + } + + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new \InvalidArgumentException('Invalid email format.'); } } From 9f69eb7a2c7865cd11a2f1c09f5ae3becf172706 Mon Sep 17 00:00:00 2001 From: kirill Date: Thu, 12 Feb 2026 22:59:03 +0300 Subject: [PATCH 30/31] Normalize email handling in `ContactPerson` entity and related use cases; ensure empty emails are converted to `null` and add clear validation comments for profile changes and email verification. --- src/ContactPersons/Entity/ContactPerson.php | 15 ++++++++++++--- .../UseCase/ChangeProfile/Command.php | 3 +++ .../UseCase/MarkEmailAsVerified/Command.php | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/ContactPersons/Entity/ContactPerson.php b/src/ContactPersons/Entity/ContactPerson.php index ac1a3cb..e98793f 100644 --- a/src/ContactPersons/Entity/ContactPerson.php +++ b/src/ContactPersons/Entity/ContactPerson.php @@ -156,12 +156,21 @@ public function getEmail(): ?string return $this->email; } + + /** + * Changes the contact person's email address. + * + * If an empty string is provided (including a string containing only whitespace), + * it will be normalized to `null` so that the database stores `NULL` instead of an empty value. + */ #[\Override] public function changeEmail(?string $email): void { - $email = null !== $email ? trim($email) : null; - if ('' === $email) { - $email = null; + if (null !== $email) { + $email = trim($email); + if ('' === $email) { + $email = null; + } } $this->email = $email; diff --git a/src/ContactPersons/UseCase/ChangeProfile/Command.php b/src/ContactPersons/UseCase/ChangeProfile/Command.php index c3e8c6a..0dc45cc 100644 --- a/src/ContactPersons/UseCase/ChangeProfile/Command.php +++ b/src/ContactPersons/UseCase/ChangeProfile/Command.php @@ -22,6 +22,9 @@ public function __construct( private function validate(): void { + // Note: empty email is allowed for profile changes. + // If you pass an empty string (or whitespace), it will be normalized to `null` + // on the entity level, so the database will store `NULL` instead of an empty string. if ('' !== trim($this->email) && !filter_var($this->email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email format.'); } diff --git a/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php b/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php index 8ae5f91..82013e5 100644 --- a/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php +++ b/src/ContactPersons/UseCase/MarkEmailAsVerified/Command.php @@ -21,6 +21,8 @@ private function validate(): void { $email = trim($this->email); + // Email verification requires a real (non-empty) email address. + // An empty value cannot be confirmed, so we fail fast with a clear error. if ('' === $email) { throw new \InvalidArgumentException('Cannot confirm an empty email.'); } From 025434a1055ac46217166d71cfa1c16ffa39cf37 Mon Sep 17 00:00:00 2001 From: kirill Date: Mon, 2 Mar 2026 22:08:44 +0300 Subject: [PATCH 31/31] Update `bitrix24/b24phpsdk` dependency to stable version `^3` in `composer.json` and remove unnecessary whitespace in `ContactPerson` entity. --- composer.json | 2 +- src/ContactPersons/Entity/ContactPerson.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index dcbf29e..1df6090 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "darsyn/ip-doctrine": "^6", "nesbot/carbon": "^3", "moneyphp/money": "^4", - "bitrix24/b24phpsdk": "dev-v3-dev", + "bitrix24/b24phpsdk": "^3", "doctrine/orm": "^3", "doctrine/doctrine-bundle": "*", "doctrine/doctrine-migrations-bundle": "*", diff --git a/src/ContactPersons/Entity/ContactPerson.php b/src/ContactPersons/Entity/ContactPerson.php index e98793f..5764db6 100644 --- a/src/ContactPersons/Entity/ContactPerson.php +++ b/src/ContactPersons/Entity/ContactPerson.php @@ -156,7 +156,6 @@ public function getEmail(): ?string return $this->email; } - /** * Changes the contact person's email address. *