-
Notifications
You must be signed in to change notification settings - Fork 3
Description
version(s) affected
0.4.0
Description
Краткое описание
Сейчас ApplicationInstallations\UseCase\Install\Handler вызывает applicationInstalled($applicationToken) и для Bitrix24Account, и для ApplicationInstallation даже в том случае, когда applicationToken === null.
В результате:
Bitrix24Accountпереводится изnewвactiveApplicationInstallationпереводится изnewвactive- диспатчатся
ApplicationInstallationFinishedEventиBitrix24AccountApplicationInstalledEvent
Это происходит раньше, чем Bitrix24 присылает отдельный webhook/event с application_token.
Ожидаемое поведение
Нужно различать два сценария:
- В
Install\CommandпереданapplicationToken
- статус аккаунта становится
active - статус установки становится
active - токен сохраняется сразу
- В
Install\CommandпереданnullвместоapplicationToken
- статус аккаунта остаётся
new - статус установки остаётся
new - токен не сохраняется
- переход в
activeдолжен происходить позже, когда придётapplication_tokenчерез webhook / отдельный finish-flow
Фактическое поведение
Сейчас оба сценария приводят к одному результату: и аккаунт, и установка оказываются в active.
Причина:
Install\Handlerвсегда вызываетapplicationInstalled($command->applicationToken)applicationInstalled(null)всё равно меняет статус наactive
Задействованные участки кода:
src/ApplicationInstallations/UseCase/Install/Handler.phpsrc/ApplicationInstallations/Entity/ApplicationInstallation.phpsrc/Bitrix24Accounts/Entity/Bitrix24Account.php
Почему это баг
UI install-flow в Bitrix24 двухшаговый:
- фронтенд начинает установку и отправляет auth payload без
application_token - позже Bitrix24 присылает webhook/event с
application_token
До шага 2 доменные сущности должны оставаться в статусе new.
Текущее поведение приводит к тому, что backend считает установку завершённой до того, как пришёл application_token.
Как проверить исправление
После фикса нужно запустить reproduction-test.
Ожидаемый результат:
testHandleWithApplicationTokenMarksInstallationAndAccountAsActiveпроходитtestHandleWithoutApplicationTokenKeepsInstallationAndAccountInNewStatusUntilOnAppInstallпроходит
How to reproduce
Минимальное воспроизведение
Приложен functional reproduction-test:
repro-bitrix24-php-lib-install-handler-status-test.php
Во втором кейсе тест сейчас должен падать:
- с токеном: ожидается
active - без токена: ожидается
new
Possible Solution
Возможные направления исправления
Подойдёт один из вариантов:
- Изменить
ApplicationInstallations\UseCase\Install\Handler, чтобыapplicationInstalled()вызывался только еслиapplicationToken !== null - Ввести отдельный use case для старта установки
ApplicationInstallation, по аналогии с уже существующимBitrix24Accounts\UseCase\InstallStart - Оставить переход
new -> activeтолько webhook-driven finish-логике
Additional Context
<?php
declare(strict_types=1);
namespace Bitrix24\Lib\Tests\Functional\ApplicationInstallations\UseCase\Install;
use Bitrix24\Lib\ApplicationInstallations\Infrastructure\Doctrine\ApplicationInstallationRepository;
use Bitrix24\Lib\ApplicationInstallations\UseCase\Install\Command;
use Bitrix24\Lib\ApplicationInstallations\UseCase\Install\Handler;
use Bitrix24\Lib\Bitrix24Accounts\Infrastructure\Doctrine\Bitrix24AccountRepository;
use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain;
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\SDK\Application\ApplicationStatus;
use Bitrix24\SDK\Application\Contracts\ApplicationInstallations\Entity\ApplicationInstallationStatus;
use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus;
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;
#[CoversClass(Handler::class)]
final class InstallHandlerStatusByTokenTest extends TestCase
{
private Handler $handler;
private ApplicationInstallationRepository $applicationInstallationRepository;
private Bitrix24AccountRepository $bitrix24AccountRepository;
#[\Override]
protected function setUp(): void
{
$entityManager = EntityManagerFactory::get();
$eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$this->applicationInstallationRepository = new ApplicationInstallationRepository($entityManager);
$this->bitrix24AccountRepository = new Bitrix24AccountRepository($entityManager);
$this->handler = new Handler(
$this->bitrix24AccountRepository,
$this->applicationInstallationRepository,
new Flusher($entityManager, $eventDispatcher),
new NullLogger(),
);
}
#[Test]
public function testHandleWithApplicationTokenMarksInstallationAndAccountAsActive(): void
{
$bitrix24AccountBuilder = (new Bitrix24AccountBuilder())
->withApplicationScope(new Scope(['crm']))
->build();
$applicationInstallationBuilder = (new ApplicationInstallationBuilder())
->withApplicationStatus(new ApplicationStatus('F'))
->withPortalLicenseFamily(PortalLicenseFamily::free)
->build();
$applicationToken = Uuid::v7()->toRfc4122();
$this->handler->handle(
new Command(
$bitrix24AccountBuilder->getMemberId(),
new Domain($bitrix24AccountBuilder->getDomainUrl()),
$bitrix24AccountBuilder->getAuthToken(),
$bitrix24AccountBuilder->getApplicationVersion(),
$bitrix24AccountBuilder->getApplicationScope(),
$bitrix24AccountBuilder->getBitrix24UserId(),
$bitrix24AccountBuilder->isBitrix24UserAdmin(),
$applicationInstallationBuilder->getApplicationStatus(),
$applicationInstallationBuilder->getPortalLicenseFamily(),
$applicationToken,
$applicationInstallationBuilder->getPortalUsersCount(),
$applicationInstallationBuilder->getContactPersonId(),
$applicationInstallationBuilder->getBitrix24PartnerContactPersonId(),
$applicationInstallationBuilder->getBitrix24PartnerId(),
$applicationInstallationBuilder->getExternalId(),
$applicationInstallationBuilder->getComment(),
),
);
$installation = $this->applicationInstallationRepository->findByApplicationToken($applicationToken);
self::assertNotNull($installation);
self::assertSame(ApplicationInstallationStatus::active, $installation->getStatus());
$accounts = $this->bitrix24AccountRepository->findByApplicationToken($applicationToken);
self::assertCount(1, $accounts);
self::assertSame(Bitrix24AccountStatus::active, $accounts[0]->getStatus());
}
#[Test]
public function testHandleWithoutApplicationTokenKeepsInstallationAndAccountInNewStatusUntilOnAppInstall(): void
{
$bitrix24AccountBuilder = (new Bitrix24AccountBuilder())
->withApplicationScope(new Scope(['crm']))
->build();
$applicationInstallationBuilder = (new ApplicationInstallationBuilder())
->withApplicationStatus(new ApplicationStatus('F'))
->withPortalLicenseFamily(PortalLicenseFamily::free)
->build();
$memberId = $bitrix24AccountBuilder->getMemberId();
$this->handler->handle(
new Command(
$memberId,
new Domain($bitrix24AccountBuilder->getDomainUrl()),
$bitrix24AccountBuilder->getAuthToken(),
$bitrix24AccountBuilder->getApplicationVersion(),
$bitrix24AccountBuilder->getApplicationScope(),
$bitrix24AccountBuilder->getBitrix24UserId(),
$bitrix24AccountBuilder->isBitrix24UserAdmin(),
$applicationInstallationBuilder->getApplicationStatus(),
$applicationInstallationBuilder->getPortalLicenseFamily(),
null,
$applicationInstallationBuilder->getPortalUsersCount(),
$applicationInstallationBuilder->getContactPersonId(),
$applicationInstallationBuilder->getBitrix24PartnerContactPersonId(),
$applicationInstallationBuilder->getBitrix24PartnerId(),
$applicationInstallationBuilder->getExternalId(),
$applicationInstallationBuilder->getComment(),
),
);
$installation = $this->applicationInstallationRepository->findByBitrix24AccountMemberId($memberId);
self::assertNotNull($installation);
self::assertSame(ApplicationInstallationStatus::new, $installation->getStatus());
$accounts = $this->bitrix24AccountRepository->findByMemberId($memberId);
self::assertCount(1, $accounts);
self::assertSame(Bitrix24AccountStatus::new, $accounts[0]->getStatus());
}
}