diff --git a/config/xml/Bitrix24.Lib.Journal.Entity.JournalItem.dcm.xml b/config/xml/Bitrix24.Lib.Journal.Entity.JournalItem.dcm.xml index fd2ac06..99fb5c3 100644 --- a/config/xml/Bitrix24.Lib.Journal.Entity.JournalItem.dcm.xml +++ b/config/xml/Bitrix24.Lib.Journal.Entity.JournalItem.dcm.xml @@ -6,6 +6,8 @@ + + @@ -13,12 +15,17 @@ + + + + - + - + + diff --git a/config/xml/Bitrix24.Lib.Journal.ValueObjects.JournalContext.dcm.xml b/config/xml/Bitrix24.Lib.Journal.Entity.ValueObjects.Context.dcm.xml similarity index 67% rename from config/xml/Bitrix24.Lib.Journal.ValueObjects.JournalContext.dcm.xml rename to config/xml/Bitrix24.Lib.Journal.Entity.ValueObjects.Context.dcm.xml index 97fdee3..fff5a50 100644 --- a/config/xml/Bitrix24.Lib.Journal.ValueObjects.JournalContext.dcm.xml +++ b/config/xml/Bitrix24.Lib.Journal.Entity.ValueObjects.Context.dcm.xml @@ -1,13 +1,11 @@ - - - + - + diff --git a/rector.php b/rector.php index 3ce8e42..c957618 100644 --- a/rector.php +++ b/rector.php @@ -14,7 +14,8 @@ use Rector\Config\RectorConfig; use Rector\Naming\Rector\Class_\RenamePropertyToMatchTypeRector; use Rector\PHPUnit\Set\PHPUnitSetList; -use Rector\Set\ValueObject\DowngradeLevelSetList; +use Rector\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector; +use Rector\Naming\Rector\ClassMethod\RenameParamToMatchTypeRector; return RectorConfig::configure() ->withPaths([ @@ -48,5 +49,7 @@ strictBooleans: true ) ->withSkip([ - RenamePropertyToMatchTypeRector::class + RenamePropertyToMatchTypeRector::class, + RenameParamToMatchTypeRector::class, + FlipTypeControlToUseExclusiveTypeRector::class, ]); \ No newline at end of file diff --git a/src/ApplicationInstallations/UseCase/Install/Command.php b/src/ApplicationInstallations/UseCase/Install/Command.php index 224f9ba..8e5e191 100644 --- a/src/ApplicationInstallations/UseCase/Install/Command.php +++ b/src/ApplicationInstallations/UseCase/Install/Command.php @@ -4,7 +4,7 @@ namespace Bitrix24\Lib\ApplicationInstallations\UseCase\Install; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; use Bitrix24\SDK\Application\ApplicationStatus; use Bitrix24\SDK\Application\PortalLicenseFamily; use Bitrix24\SDK\Core\Credentials\AuthToken; diff --git a/src/ApplicationInstallations/UseCase/OnAppInstall/Command.php b/src/ApplicationInstallations/UseCase/OnAppInstall/Command.php index 5ce5b06..a0a88fc 100644 --- a/src/ApplicationInstallations/UseCase/OnAppInstall/Command.php +++ b/src/ApplicationInstallations/UseCase/OnAppInstall/Command.php @@ -4,7 +4,7 @@ namespace Bitrix24\Lib\ApplicationInstallations\UseCase\OnAppInstall; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; /** * Command is called when installation occurs through UI. diff --git a/src/ApplicationInstallations/UseCase/Uninstall/Command.php b/src/ApplicationInstallations/UseCase/Uninstall/Command.php index 84debaa..a3a0ee8 100644 --- a/src/ApplicationInstallations/UseCase/Uninstall/Command.php +++ b/src/ApplicationInstallations/UseCase/Uninstall/Command.php @@ -4,7 +4,7 @@ namespace Bitrix24\Lib\ApplicationInstallations\UseCase\Uninstall; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; readonly class Command { diff --git a/src/Bitrix24Accounts/UseCase/ChangeDomainUrl/Command.php b/src/Bitrix24Accounts/UseCase/ChangeDomainUrl/Command.php index 0d640a8..8ceb9e6 100644 --- a/src/Bitrix24Accounts/UseCase/ChangeDomainUrl/Command.php +++ b/src/Bitrix24Accounts/UseCase/ChangeDomainUrl/Command.php @@ -4,7 +4,7 @@ namespace Bitrix24\Lib\Bitrix24Accounts\UseCase\ChangeDomainUrl; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; readonly class Command { diff --git a/src/Bitrix24Accounts/UseCase/InstallFinish/Command.php b/src/Bitrix24Accounts/UseCase/InstallFinish/Command.php index 979b0e1..30de5e0 100644 --- a/src/Bitrix24Accounts/UseCase/InstallFinish/Command.php +++ b/src/Bitrix24Accounts/UseCase/InstallFinish/Command.php @@ -4,7 +4,7 @@ namespace Bitrix24\Lib\Bitrix24Accounts\UseCase\InstallFinish; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; readonly class Command { diff --git a/src/Bitrix24Accounts/UseCase/InstallStart/Command.php b/src/Bitrix24Accounts/UseCase/InstallStart/Command.php index e480182..10f989e 100644 --- a/src/Bitrix24Accounts/UseCase/InstallStart/Command.php +++ b/src/Bitrix24Accounts/UseCase/InstallStart/Command.php @@ -4,7 +4,7 @@ namespace Bitrix24\Lib\Bitrix24Accounts\UseCase\InstallStart; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; use Bitrix24\SDK\Core\Credentials\AuthToken; use Bitrix24\SDK\Core\Credentials\Scope; diff --git a/src/Journal/Controller/JournalAdminController.php b/src/Journal/Controller/JournalAdminController.php index 0aab3bb..8dd1560 100644 --- a/src/Journal/Controller/JournalAdminController.php +++ b/src/Journal/Controller/JournalAdminController.php @@ -14,7 +14,8 @@ namespace Bitrix24\Lib\Journal\Controller; use Bitrix24\Lib\Journal\Entity\LogLevel; -use Bitrix24\Lib\Journal\ReadModel\JournalItemReadRepository; +use Bitrix24\Lib\Journal\Infrastructure\Doctrine\JournalItemReadRepository; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -22,22 +23,22 @@ /** * Admin controller for journal management - * Developer should configure routes in their application + * Developer should configure routes in their application. */ class JournalAdminController extends AbstractController { public function __construct( private readonly JournalItemReadRepository $journalReadRepository - ) { - } + ) {} /** - * List journal items with filters and pagination + * List journal items with filters and pagination. */ public function list(Request $request): Response { $page = max(1, $request->query->getInt('page', 1)); $domainUrl = $request->query->get('domain'); + $memberId = $request->query->get('member_id'); $levelValue = $request->query->get('level'); $label = $request->query->get('label'); @@ -47,8 +48,9 @@ public function list(Request $request): Response } $pagination = $this->journalReadRepository->findWithFilters( - domainUrl: $domainUrl ?: null, - level: $level, + memberId: $memberId ?: null, + domain: $domainUrl ? new Domain($domainUrl) : null, + logLevel: $level, label: $label ?: null, page: $page, limit: 50 @@ -61,6 +63,7 @@ public function list(Request $request): Response 'pagination' => $pagination, 'currentFilters' => [ 'domain' => $domainUrl, + 'member_id' => $memberId, 'level' => $levelValue, 'label' => $label, ], @@ -71,7 +74,7 @@ public function list(Request $request): Response } /** - * Show journal item details + * Show journal item details. */ public function show(string $id): Response { @@ -83,7 +86,7 @@ public function show(string $id): Response $journalItem = $this->journalReadRepository->findById($uuid); - if (!$journalItem) { + if (null === $journalItem) { throw $this->createNotFoundException('Journal item not found'); } diff --git a/src/Journal/Docs/README.md b/src/Journal/Docs/README.md index d30442c..7e8b9a5 100644 --- a/src/Journal/Docs/README.md +++ b/src/Journal/Docs/README.md @@ -20,7 +20,8 @@ $factory = $container->get(JournalLoggerFactory::class); // Создаем логгер для конкретной установки приложения $installationId = Uuid::fromString('...'); -$logger = $factory->createLogger($installationId); +$memberId = '...'; +$logger = $factory->createLogger($memberId, $installationId); // Используем как обычный PSR-3 логгер $logger->info('Синхронизация завершена', [ @@ -50,6 +51,7 @@ use Bitrix24\Lib\Journal\Services\JournalLogger; use Bitrix24\Lib\Journal\Infrastructure\Doctrine\DoctrineDbalJournalItemRepository; $logger = new JournalLogger( + memberId: $memberId, applicationInstallationId: $installationId, repository: $repository, entityManager: $entityManager @@ -74,13 +76,14 @@ $logger->debug('Отладочная информация'); use Bitrix24\Lib\Journal\Entity\JournalItem; // Создание через статические методы -$item = JournalItem::info($installationId, 'Сообщение', [ +$item = JournalItem::info($memberId, $installationId, 'Сообщение', [ 'label' => 'custom.label', 'payload' => ['key' => 'value'] ]); // Или через create с явным указанием уровня $item = JournalItem::create( + memberId: $memberId, applicationInstallationId: $installationId, level: LogLevel::error, message: 'Сообщение об ошибке', @@ -103,10 +106,9 @@ $entityManager->flush(); // Поиск $item = $repository->findById($uuid); -$items = $repository->findByApplicationInstallationId($installationId, LogLevel::error, 50, 0); +$items = $repository->findByApplicationInstallationId($memberId, $installationId, LogLevel::error, 50, 0); // Очистка -$deleted = $repository->deleteByApplicationInstallationId($installationId); $deleted = $repository->deleteOlderThan(new CarbonImmutable('-30 days')); ``` @@ -126,14 +128,15 @@ $repository->clear(); ### 4. Admin UI (ReadModel) ```php -use Bitrix24\Lib\Journal\ReadModel\JournalItemReadRepository; +use Bitrix24\Lib\Journal\Infrastructure\Doctrine\JournalItemReadRepository; $readRepo = new JournalItemReadRepository($entityManager, $paginator); // Получение с фильтрами и пагинацией $pagination = $readRepo->findWithFilters( - domainUrl: 'example.bitrix24.ru', - level: LogLevel::error, + memberId: '66c9893d5f30e6.45265697', + domain: new Domain('example.bitrix24.ru'), + logLevel: LogLevel::error, label: 'b24.api.error', page: 1, limit: 50 @@ -185,6 +188,7 @@ class MyTest extends TestCase $entityManager = $this->createMock(EntityManagerInterface::class); $this->logger = new JournalLogger( + '66c9893d5f30e6.45265697', Uuid::v7(), $this->repository, $entityManager @@ -215,6 +219,7 @@ class MyTest extends TestCase Таблица `journal_item` с полями: - `id` (UUID) - PK +- `member_id` (string) - ID портала Bitrix24 - `application_installation_id` (UUID) - FK к установке приложения - `created_at_utc` (timestamp) - время создания - `level` (string) - уровень логирования @@ -222,6 +227,7 @@ class MyTest extends TestCase - `label`, `payload`, `bitrix24_user_id`, `ip_address` - поля контекста Индексы: -- `application_installation_id` +- `member_id, application_installation_id, level, created_at_utc` (composite) +- `member_id` - `created_at_utc` - `level` diff --git a/src/Journal/Entity/JournalItem.php b/src/Journal/Entity/JournalItem.php index 36a1011..327b8e6 100644 --- a/src/Journal/Entity/JournalItem.php +++ b/src/Journal/Entity/JournalItem.php @@ -14,14 +14,14 @@ namespace Bitrix24\Lib\Journal\Entity; use Bitrix24\Lib\AggregateRoot; -use Bitrix24\Lib\Journal\ValueObjects\JournalContext; +use Bitrix24\Lib\Journal\Entity\ValueObjects\Context; use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; use Carbon\CarbonImmutable; use Symfony\Component\Uid\Uuid; /** * Journal item entity - * Each journal record contains domain business events for technical support staff + * Each journal record contains domain business events for technical support staff. */ class JournalItem extends AggregateRoot implements JournalItemInterface { @@ -30,15 +30,26 @@ class JournalItem extends AggregateRoot implements JournalItemInterface private readonly CarbonImmutable $createdAt; public function __construct( - private Uuid $applicationInstallationId, - private LogLevel $level, - private string $message, - private JournalContext $context + private readonly string $memberId, + private readonly Uuid $applicationInstallationId, + private readonly LogLevel $level, + private readonly string $message, + private readonly string $label, + private readonly ?string $userId, + private readonly Context $context ) { + if ('' === trim($this->memberId)) { + throw new InvalidArgumentException('memberId cannot be empty'); + } + if ('' === trim($this->message)) { throw new InvalidArgumentException('Journal message cannot be empty'); } + if ('' === trim($this->label)) { + throw new InvalidArgumentException('Journal label cannot be empty'); + } + $this->id = Uuid::v7(); $this->createdAt = new CarbonImmutable(); } @@ -55,6 +66,12 @@ public function getApplicationInstallationId(): Uuid return $this->applicationInstallationId; } + #[\Override] + public function getMemberId(): string + { + return $this->memberId; + } + #[\Override] public function getCreatedAt(): CarbonImmutable { @@ -74,68 +91,86 @@ public function getMessage(): string } #[\Override] - public function getContext(): JournalContext + public function getLabel(): string + { + return $this->label; + } + + #[\Override] + public function getUserId(): ?string + { + return $this->userId; + } + + #[\Override] + public function getContext(): Context { return $this->context; } /** - * Create journal item with custom log level + * Create journal item with custom log level. */ public static function create( + string $memberId, Uuid $applicationInstallationId, LogLevel $level, string $message, - JournalContext $context + string $label, + ?string $userId, + Context $context ): self { return new self( + memberId: $memberId, applicationInstallationId: $applicationInstallationId, level: $level, message: $message, + label: $label, + userId: $userId, context: $context ); } /** - * PSR-3 compatible factory methods + * PSR-3 compatible factory methods. */ - public static function emergency(Uuid $applicationInstallationId, string $message, JournalContext $context): self + public static function emergency(string $memberId, Uuid $applicationInstallationId, string $message, string $label, ?string $userId, Context $context): self { - return self::create($applicationInstallationId, LogLevel::emergency, $message, $context); + return self::create($memberId, $applicationInstallationId, LogLevel::emergency, $message, $label, $userId, $context); } - public static function alert(Uuid $applicationInstallationId, string $message, JournalContext $context): self + public static function alert(string $memberId, Uuid $applicationInstallationId, string $message, string $label, ?string $userId, Context $context): self { - return self::create($applicationInstallationId, LogLevel::alert, $message, $context); + return self::create($memberId, $applicationInstallationId, LogLevel::alert, $message, $label, $userId, $context); } - public static function critical(Uuid $applicationInstallationId, string $message, JournalContext $context): self + public static function critical(string $memberId, Uuid $applicationInstallationId, string $message, string $label, ?string $userId, Context $context): self { - return self::create($applicationInstallationId, LogLevel::critical, $message, $context); + return self::create($memberId, $applicationInstallationId, LogLevel::critical, $message, $label, $userId, $context); } - public static function error(Uuid $applicationInstallationId, string $message, JournalContext $context): self + public static function error(string $memberId, Uuid $applicationInstallationId, string $message, string $label, ?string $userId, Context $context): self { - return self::create($applicationInstallationId, LogLevel::error, $message, $context); + return self::create($memberId, $applicationInstallationId, LogLevel::error, $message, $label, $userId, $context); } - public static function warning(Uuid $applicationInstallationId, string $message, JournalContext $context): self + public static function warning(string $memberId, Uuid $applicationInstallationId, string $message, string $label, ?string $userId, Context $context): self { - return self::create($applicationInstallationId, LogLevel::warning, $message, $context); + return self::create($memberId, $applicationInstallationId, LogLevel::warning, $message, $label, $userId, $context); } - public static function notice(Uuid $applicationInstallationId, string $message, JournalContext $context): self + public static function notice(string $memberId, Uuid $applicationInstallationId, string $message, string $label, ?string $userId, Context $context): self { - return self::create($applicationInstallationId, LogLevel::notice, $message, $context); + return self::create($memberId, $applicationInstallationId, LogLevel::notice, $message, $label, $userId, $context); } - public static function info(Uuid $applicationInstallationId, string $message, JournalContext $context): self + public static function info(string $memberId, Uuid $applicationInstallationId, string $message, string $label, ?string $userId, Context $context): self { - return self::create($applicationInstallationId, LogLevel::info, $message, $context); + return self::create($memberId, $applicationInstallationId, LogLevel::info, $message, $label, $userId, $context); } - public static function debug(Uuid $applicationInstallationId, string $message, JournalContext $context): self + public static function debug(string $memberId, Uuid $applicationInstallationId, string $message, string $label, ?string $userId, Context $context): self { - return self::create($applicationInstallationId, LogLevel::debug, $message, $context); + return self::create($memberId, $applicationInstallationId, LogLevel::debug, $message, $label, $userId, $context); } } diff --git a/src/Journal/Entity/JournalItemInterface.php b/src/Journal/Entity/JournalItemInterface.php index bccbddb..b87edd6 100644 --- a/src/Journal/Entity/JournalItemInterface.php +++ b/src/Journal/Entity/JournalItemInterface.php @@ -13,12 +13,12 @@ namespace Bitrix24\Lib\Journal\Entity; -use Bitrix24\Lib\Journal\ValueObjects\JournalContext; +use Bitrix24\Lib\Journal\Entity\ValueObjects\Context; use Carbon\CarbonImmutable; use Symfony\Component\Uid\Uuid; /** - * Journal item interface for SDK contract extraction + * Journal item interface for SDK contract extraction. */ interface JournalItemInterface { @@ -26,11 +26,17 @@ public function getId(): Uuid; public function getApplicationInstallationId(): Uuid; + public function getMemberId(): string; + public function getCreatedAt(): CarbonImmutable; public function getLevel(): LogLevel; public function getMessage(): string; - public function getContext(): JournalContext; + public function getUserId(): ?string; + + public function getLabel(): string; + + public function getContext(): Context; } diff --git a/src/Journal/Entity/LogLevel.php b/src/Journal/Entity/LogLevel.php index bc4dfbb..1f8c891 100644 --- a/src/Journal/Entity/LogLevel.php +++ b/src/Journal/Entity/LogLevel.php @@ -14,7 +14,7 @@ namespace Bitrix24\Lib\Journal\Entity; /** - * PSR-3 compatible log level enum + * PSR-3 compatible log level enum. */ enum LogLevel: string { @@ -28,7 +28,7 @@ enum LogLevel: string case debug = 'debug'; /** - * Creates LogLevel from PSR-3 log level string + * Creates LogLevel from PSR-3 log level string. */ public static function fromPsr3Level(string $level): self { diff --git a/src/Journal/ValueObjects/JournalContext.php b/src/Journal/Entity/ValueObjects/Context.php similarity index 77% rename from src/Journal/ValueObjects/JournalContext.php rename to src/Journal/Entity/ValueObjects/Context.php index 627e5cd..046e987 100644 --- a/src/Journal/ValueObjects/JournalContext.php +++ b/src/Journal/Entity/ValueObjects/Context.php @@ -11,27 +11,20 @@ declare(strict_types=1); -namespace Bitrix24\Lib\Journal\ValueObjects; +namespace Bitrix24\Lib\Journal\Entity\ValueObjects; use Darsyn\IP\Version\Multi as IP; /** - * Journal context value object + * Journal context value object. */ -readonly class JournalContext +readonly class Context { public function __construct( - private string $label, private ?array $payload = null, private ?int $bitrix24UserId = null, private ?IP $ipAddress = null - ) { - } - - public function getLabel(): string - { - return $this->label; - } + ) {} public function getPayload(): ?array { @@ -49,12 +42,11 @@ public function getIpAddress(): ?IP } /** - * Convert to array + * Convert to array. */ public function toArray(): array { return [ - 'label' => $this->label, 'payload' => $this->payload, 'bitrix24UserId' => $this->bitrix24UserId, 'ipAddress' => $this->ipAddress?->getCompactedAddress(), diff --git a/src/Journal/Infrastructure/Doctrine/DoctrineDbalJournalItemRepository.php b/src/Journal/Infrastructure/Doctrine/DoctrineDbalJournalItemRepository.php index a9bccc7..927856e 100644 --- a/src/Journal/Infrastructure/Doctrine/DoctrineDbalJournalItemRepository.php +++ b/src/Journal/Infrastructure/Doctrine/DoctrineDbalJournalItemRepository.php @@ -22,24 +22,26 @@ use Doctrine\ORM\EntityRepository; use Symfony\Component\Uid\Uuid; -class DoctrineDbalJournalItemRepository extends EntityRepository implements JournalItemRepositoryInterface +class DoctrineDbalJournalItemRepository implements JournalItemRepositoryInterface { + private readonly EntityRepository $repository; + public function __construct( - EntityManagerInterface $entityManager + private readonly EntityManagerInterface $entityManager ) { - parent::__construct($entityManager, $entityManager->getClassMetadata(JournalItem::class)); + $this->repository = $this->entityManager->getRepository(JournalItem::class); } #[\Override] public function save(JournalItemInterface $journalItem): void { - $this->getEntityManager()->persist($journalItem); + $this->entityManager->persist($journalItem); } #[\Override] - public function findById(Uuid $id): ?JournalItemInterface + public function findById(Uuid $uuid): ?JournalItemInterface { - return $this->getEntityManager()->getRepository(JournalItem::class)->find($id); + return $this->repository->find($uuid); } /** @@ -47,69 +49,88 @@ public function findById(Uuid $id): ?JournalItemInterface */ #[\Override] public function findByApplicationInstallationId( + string $memberId, Uuid $applicationInstallationId, - ?LogLevel $level = null, + ?LogLevel $logLevel = null, ?int $limit = null, ?int $offset = null ): array { - $qb = $this->getEntityManager()->getRepository(JournalItem::class) + $queryBuilder = $this->repository ->createQueryBuilder('j') - ->where('j.applicationInstallationId = :appId') + ->where('j.memberId = :memberId') + ->setParameter('memberId', $memberId) + ->andWhere('j.applicationInstallationId = :appId') ->setParameter('appId', $applicationInstallationId) - ->orderBy('j.createdAt', 'DESC'); + ->orderBy('j.createdAt', 'DESC') + ; - if (null !== $level) { - $qb->andWhere('j.level = :level') - ->setParameter('level', $level); + if (null !== $logLevel) { + $queryBuilder->andWhere('j.level = :level') + ->setParameter('level', $logLevel) + ; } if (null !== $limit) { - $qb->setMaxResults($limit); + $queryBuilder->setMaxResults($limit); } if (null !== $offset) { - $qb->setFirstResult($offset); + $queryBuilder->setFirstResult($offset); } - return $qb->getQuery()->getResult(); + return $queryBuilder->getQuery()->getResult(); } + /** + * @return JournalItemInterface[] + */ #[\Override] - public function deleteByApplicationInstallationId(Uuid $applicationInstallationId): int - { - return $this->getEntityManager()->createQueryBuilder() - ->delete(JournalItem::class, 'j') - ->where('j.applicationInstallationId = :appId') - ->setParameter('appId', $applicationInstallationId) - ->getQuery() - ->execute(); + public function findByMemberId( + string $memberId, + ?LogLevel $logLevel = null, + ?int $limit = null, + ?int $offset = null + ): array { + $queryBuilder = $this->repository + ->createQueryBuilder('j') + ->where('j.memberId = :memberId') + ->setParameter('memberId', $memberId) + ->orderBy('j.createdAt', 'DESC') + ; + + if (null !== $logLevel) { + $queryBuilder->andWhere('j.level = :level') + ->setParameter('level', $logLevel) + ; + } + + if (null !== $limit) { + $queryBuilder->setMaxResults($limit); + } + + if (null !== $offset) { + $queryBuilder->setFirstResult($offset); + } + + return $queryBuilder->getQuery()->getResult(); } #[\Override] - public function deleteOlderThan(CarbonImmutable $date): int - { - return $this->getEntityManager()->createQueryBuilder() + public function deleteOlderThan( + string $memberId, + Uuid $applicationInstallationId, + CarbonImmutable $date + ): int { + return $this->entityManager->createQueryBuilder() ->delete(JournalItem::class, 'j') - ->where('j.createdAt < :date') + ->where('j.memberId = :memberId') + ->andWhere('j.applicationInstallationId = :appId') + ->andWhere('j.createdAt < :date') + ->setParameter('memberId', $memberId) + ->setParameter('appId', $applicationInstallationId) ->setParameter('date', $date) ->getQuery() - ->execute(); - } - - #[\Override] - public function countByApplicationInstallationId(Uuid $applicationInstallationId, ?LogLevel $level = null): int - { - $qb = $this->getEntityManager()->getRepository(JournalItem::class) - ->createQueryBuilder('j') - ->select('COUNT(j.id)') - ->where('j.applicationInstallationId = :appId') - ->setParameter('appId', $applicationInstallationId); - - if (null !== $level) { - $qb->andWhere('j.level = :level') - ->setParameter('level', $level); - } - - return (int) $qb->getQuery()->getSingleScalarResult(); + ->execute() + ; } } diff --git a/src/Journal/Infrastructure/Doctrine/JournalItemReadRepository.php b/src/Journal/Infrastructure/Doctrine/JournalItemReadRepository.php new file mode 100644 index 0000000..404cd8e --- /dev/null +++ b/src/Journal/Infrastructure/Doctrine/JournalItemReadRepository.php @@ -0,0 +1,156 @@ + + * + * 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\Journal\Infrastructure\Doctrine; + +use Bitrix24\Lib\ApplicationInstallations\Entity\ApplicationInstallation; +use Bitrix24\Lib\Bitrix24Accounts\Entity\Bitrix24Account; +use Bitrix24\Lib\Journal\Entity\JournalItem; +use Bitrix24\Lib\Journal\Entity\JournalItemInterface; +use Bitrix24\Lib\Journal\Entity\LogLevel; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\QueryBuilder; +use Knp\Component\Pager\Pagination\PaginationInterface; +use Knp\Component\Pager\PaginatorInterface; +use Symfony\Component\Uid\Uuid; + +/** + * Read the model repository for journal items with filtering and pagination. + */ +readonly class JournalItemReadRepository +{ + public function __construct( + private EntityManagerInterface $entityManager, + private PaginatorInterface $paginator + ) {} + + /** + * Find journal items with filters and pagination. + * + * @return PaginationInterface + */ + public function findWithFilters( + ?string $memberId = null, + ?Domain $domain = null, + ?LogLevel $logLevel = null, + ?string $label = null, + int $page = 1, + int $limit = 50 + ): PaginationInterface { + $queryBuilder = $this->createFilteredQueryBuilder($memberId, $domain, $logLevel, $label); + + return $this->paginator->paginate( + $queryBuilder, + $page, + $limit, + [ + 'defaultSortFieldName' => 'j.createdAt', + 'defaultSortDirection' => 'desc', + ] + ); + } + + /** + * Find journal item by ID. + */ + public function findById(Uuid $uuid): ?JournalItemInterface + { + return $this->entityManager->getRepository(JournalItem::class)->find($uuid); + } + + /** + * Get available domain URLs from journal. + * + * @return string[] + */ + public function getAvailableDomains(): array + { + // Join with ApplicationInstallation and then Bitrix24Account to get domain URLs + $queryBuilder = $this->entityManager->createQueryBuilder(); + $queryBuilder->select('DISTINCT b24.domainUrl') + ->from(JournalItem::class, 'j') + ->innerJoin(ApplicationInstallation::class, 'ai', 'WITH', 'ai.id = j.applicationInstallationId') + ->innerJoin(Bitrix24Account::class, 'b24', 'WITH', 'b24.id = ai.bitrix24AccountId') + ->orderBy('b24.domainUrl', 'ASC') + ; + + $results = $queryBuilder->getQuery()->getScalarResult(); + + return array_column($results, 'domainUrl'); + } + + /** + * Get available labels from journal. + * + * @return string[] + */ + public function getAvailableLabels(): array + { + $queryBuilder = $this->entityManager->createQueryBuilder(); + $queryBuilder->select('DISTINCT j.label') + ->from(JournalItem::class, 'j') + ->where('j.label IS NOT NULL') + ->orderBy('j.label', 'ASC') + ; + + $results = $queryBuilder->getQuery()->getScalarResult(); + + return array_filter(array_column($results, 'label')); + } + + /** + * Create query builder with filters. + */ + private function createFilteredQueryBuilder( + ?string $memberId = null, + ?Domain $domain = null, + ?LogLevel $logLevel = null, + ?string $label = null + ): QueryBuilder { + $queryBuilder = $this->entityManager->createQueryBuilder(); + $queryBuilder->select('j') + ->from(JournalItem::class, 'j') + ; + + if (null !== $memberId) { + $queryBuilder->andWhere('j.memberId = :memberId') + ->setParameter('memberId', $memberId) + ; + } + + if (null !== $domain) { + $queryBuilder->innerJoin(ApplicationInstallation::class, 'ai', 'WITH', 'ai.id = j.applicationInstallationId') + ->innerJoin(Bitrix24Account::class, 'b24', 'WITH', 'b24.id = ai.bitrix24AccountId') + ->andWhere('b24.domainUrl = :domainUrl') + ->setParameter('domainUrl', $domain->value) + ; + } + + if (null !== $logLevel) { + $queryBuilder->andWhere('j.level = :level') + ->setParameter('level', $logLevel) + ; + } + + if (null !== $label) { + $queryBuilder->andWhere('j.label = :label') + ->setParameter('label', $label) + ; + } + + $queryBuilder->orderBy('j.createdAt', 'DESC'); + + return $queryBuilder; + } +} diff --git a/src/Journal/Infrastructure/JournalItemRepositoryInterface.php b/src/Journal/Infrastructure/JournalItemRepositoryInterface.php index eb1fe4d..099c2cd 100644 --- a/src/Journal/Infrastructure/JournalItemRepositoryInterface.php +++ b/src/Journal/Infrastructure/JournalItemRepositoryInterface.php @@ -19,44 +19,51 @@ use Symfony\Component\Uid\Uuid; /** - * Journal item repository interface for SDK contract extraction + * Journal item repository interface for SDK contract extraction. */ interface JournalItemRepositoryInterface { /** - * Save journal item + * Save journal item. */ public function save(JournalItemInterface $journalItem): void; /** - * Find journal item by ID + * Find journal item by ID. */ - public function findById(Uuid $id): ?JournalItemInterface; + public function findById(Uuid $uuid): ?JournalItemInterface; /** - * Find journal items by application installation ID + * Find journal items by application installation ID. * * @return JournalItemInterface[] */ public function findByApplicationInstallationId( + string $memberId, Uuid $applicationInstallationId, - ?LogLevel $level = null, + ?LogLevel $logLevel = null, ?int $limit = null, ?int $offset = null ): array; /** - * Delete all journal items by application installation ID - */ - public function deleteByApplicationInstallationId(Uuid $applicationInstallationId): int; - - /** - * Delete journal items older than specified date + * Find journal items by member ID. + * + * @return JournalItemInterface[] */ - public function deleteOlderThan(CarbonImmutable $date): int; + public function findByMemberId( + string $memberId, + ?LogLevel $logLevel = null, + ?int $limit = null, + ?int $offset = null + ): array; /** - * Count journal items by application installation ID + * Delete journal items older than specified date. */ - public function countByApplicationInstallationId(Uuid $applicationInstallationId, ?LogLevel $level = null): int; + public function deleteOlderThan( + string $memberId, + Uuid $applicationInstallationId, + CarbonImmutable $date + ): int; } diff --git a/src/Journal/ReadModel/JournalItemReadRepository.php b/src/Journal/ReadModel/JournalItemReadRepository.php deleted file mode 100644 index 6faa9be..0000000 --- a/src/Journal/ReadModel/JournalItemReadRepository.php +++ /dev/null @@ -1,140 +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\Journal\ReadModel; - -use Bitrix24\Lib\Journal\Entity\JournalItem; -use Bitrix24\Lib\Journal\Entity\JournalItemInterface; -use Bitrix24\Lib\Journal\Entity\LogLevel; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\QueryBuilder; -use Knp\Component\Pager\PaginatorInterface; -use Knp\Component\Pager\Pagination\PaginationInterface; -use Symfony\Component\Uid\Uuid; - -/** - * Read model repository for journal items with filtering and pagination - */ -readonly class JournalItemReadRepository -{ - public function __construct( - private EntityManagerInterface $entityManager, - private PaginatorInterface $paginator - ) { - } - - /** - * Find journal items with filters and pagination - * - * @return PaginationInterface - */ - public function findWithFilters( - ?string $domainUrl = null, - ?LogLevel $level = null, - ?string $label = null, - int $page = 1, - int $limit = 50 - ): PaginationInterface { - $qb = $this->createFilteredQueryBuilder($domainUrl, $level, $label); - - return $this->paginator->paginate( - $qb, - $page, - $limit, - [ - 'defaultSortFieldName' => 'j.createdAt', - 'defaultSortDirection' => 'desc', - ] - ); - } - - /** - * Find journal item by ID - */ - public function findById(Uuid $id): ?JournalItemInterface - { - return $this->entityManager->getRepository(JournalItem::class)->find($id); - } - - /** - * Get available domain URLs from journal - * - * @return string[] - */ - public function getAvailableDomains(): array - { - // Join with ApplicationInstallation and then Bitrix24Account to get domain URLs - $qb = $this->entityManager->createQueryBuilder(); - $qb->select('DISTINCT b24.domainUrl') - ->from(JournalItem::class, 'j') - ->innerJoin('Bitrix24\Lib\ApplicationInstallations\Entity\ApplicationInstallation', 'ai', 'WITH', 'ai.id = j.applicationInstallationId') - ->innerJoin('Bitrix24\Lib\Bitrix24Accounts\Entity\Bitrix24Account', 'b24', 'WITH', 'b24.id = ai.bitrix24AccountId') - ->orderBy('b24.domainUrl', 'ASC'); - - $results = $qb->getQuery()->getScalarResult(); - - return array_column($results, 'domainUrl'); - } - - /** - * Get available labels from journal - * - * @return string[] - */ - public function getAvailableLabels(): array - { - $qb = $this->entityManager->createQueryBuilder(); - $qb->select('DISTINCT j.context.label') - ->from(JournalItem::class, 'j') - ->where('j.context.label IS NOT NULL') - ->orderBy('j.context.label', 'ASC'); - - $results = $qb->getQuery()->getScalarResult(); - - return array_filter(array_column($results, 'label')); - } - - /** - * Create query builder with filters - */ - private function createFilteredQueryBuilder( - ?string $domainUrl = null, - ?LogLevel $level = null, - ?string $label = null - ): QueryBuilder { - $qb = $this->entityManager->createQueryBuilder(); - $qb->select('j') - ->from(JournalItem::class, 'j'); - - if ($domainUrl) { - $qb->innerJoin('Bitrix24\Lib\ApplicationInstallations\Entity\ApplicationInstallation', 'ai', 'WITH', 'ai.id = j.applicationInstallationId') - ->innerJoin('Bitrix24\Lib\Bitrix24Accounts\Entity\Bitrix24Account', 'b24', 'WITH', 'b24.id = ai.bitrix24AccountId') - ->andWhere('b24.domainUrl = :domainUrl') - ->setParameter('domainUrl', $domainUrl); - } - - if ($level) { - $qb->andWhere('j.level = :level') - ->setParameter('level', $level); - } - - if ($label) { - $qb->andWhere('j.context.label = :label') - ->setParameter('label', $label); - } - - $qb->orderBy('j.createdAt', 'DESC'); - - return $qb; - } -} diff --git a/src/Journal/Services/JournalLogger.php b/src/Journal/Services/JournalLogger.php index 1af06ac..3a6c024 100644 --- a/src/Journal/Services/JournalLogger.php +++ b/src/Journal/Services/JournalLogger.php @@ -15,8 +15,8 @@ use Bitrix24\Lib\Journal\Entity\JournalItem; use Bitrix24\Lib\Journal\Entity\LogLevel; +use Bitrix24\Lib\Journal\Entity\ValueObjects\Context; use Bitrix24\Lib\Journal\Infrastructure\JournalItemRepositoryInterface; -use Bitrix24\Lib\Journal\ValueObjects\JournalContext; use Darsyn\IP\Version\Multi as IP; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; @@ -25,36 +25,40 @@ /** * PSR-3 compatible journal logger - * Writes log entries to the journal repository + * Writes log entries to the journal repository. */ class JournalLogger implements LoggerInterface { use LoggerTrait; public function __construct( + private readonly string $memberId, private readonly Uuid $applicationInstallationId, private readonly JournalItemRepositoryInterface $repository, private readonly EntityManagerInterface $entityManager - ) { - } + ) {} /** - * Logs with an arbitrary level + * Logs with an arbitrary level. * - * @param mixed $level - * @param string|\Stringable $message + * @param mixed $level * @param array $context */ #[\Override] public function log($level, string|\Stringable $message, array $context = []): void { $logLevel = $this->convertLevel($level); + $label = $context['label'] ?? 'application.log'; + $userId = $context['userId'] ?? null; $journalContext = $this->createContext($context); $journalItem = JournalItem::create( + memberId: $this->memberId, applicationInstallationId: $this->applicationInstallationId, level: $logLevel, message: (string) $message, + label: (string) $label, + userId: $userId, context: $journalContext ); @@ -63,7 +67,7 @@ public function log($level, string|\Stringable $message, array $context = []): v } /** - * Convert PSR-3 log level to LogLevel enum + * Convert PSR-3 log level to LogLevel enum. */ private function convertLevel(mixed $level): LogLevel { @@ -81,12 +85,10 @@ private function convertLevel(mixed $level): LogLevel } /** - * Create JournalContext from PSR-3 context array + * Create Context from PSR-3 context array. */ - private function createContext(array $context): JournalContext + private function createContext(array $context): Context { - $label = $context['label'] ?? 'application.log'; - $ipAddress = null; if (isset($context['ipAddress']) && is_string($context['ipAddress'])) { try { @@ -96,8 +98,7 @@ private function createContext(array $context): JournalContext } } - return new JournalContext( - label: $label, + return new Context( payload: $context['payload'] ?? null, bitrix24UserId: isset($context['bitrix24UserId']) ? (int) $context['bitrix24UserId'] : null, ipAddress: $ipAddress diff --git a/src/Journal/Services/JournalLoggerFactory.php b/src/Journal/Services/JournalLoggerFactory.php index 3b09d5e..eb4f1e8 100644 --- a/src/Journal/Services/JournalLoggerFactory.php +++ b/src/Journal/Services/JournalLoggerFactory.php @@ -19,22 +19,22 @@ use Symfony\Component\Uid\Uuid; /** - * Factory for creating JournalLogger instances + * Factory for creating JournalLogger instances. */ readonly class JournalLoggerFactory { public function __construct( private JournalItemRepositoryInterface $repository, private EntityManagerInterface $entityManager - ) { - } + ) {} /** - * Create logger for specific application installation + * Create logger for specific application installation. */ - public function createLogger(Uuid $applicationInstallationId): LoggerInterface + public function createLogger(string $memberId, Uuid $applicationInstallationId): LoggerInterface { return new JournalLogger( + memberId: $memberId, applicationInstallationId: $applicationInstallationId, repository: $this->repository, entityManager: $this->entityManager diff --git a/src/Bitrix24Accounts/ValueObjects/Domain.php b/src/Kernel/ValueObjects/Domain.php similarity index 97% rename from src/Bitrix24Accounts/ValueObjects/Domain.php rename to src/Kernel/ValueObjects/Domain.php index e172b22..d53fcf3 100644 --- a/src/Bitrix24Accounts/ValueObjects/Domain.php +++ b/src/Kernel/ValueObjects/Domain.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Bitrix24\Lib\Bitrix24Accounts\ValueObjects; +namespace Bitrix24\Lib\Kernel\ValueObjects; readonly class Domain { diff --git a/tests/EntityManagerFactory.php b/tests/EntityManagerFactory.php index e3935cf..621244e 100644 --- a/tests/EntityManagerFactory.php +++ b/tests/EntityManagerFactory.php @@ -7,6 +7,7 @@ use Bitrix24\SDK\Core\Exceptions\WrongConfigurationException; use Carbon\Doctrine\CarbonImmutableType; use Doctrine\DBAL\DriverManager; +use Darsyn\IP\Doctrine\MultiType; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\EntityManager; @@ -66,6 +67,10 @@ public static function get(): EntityManagerInterface Type::addType('carbon_immutable', CarbonImmutableType::class); } + if (!Type::hasType('ip_address')) { + Type::addType('ip_address', MultiType::class); + } + $configuration = ORMSetup::createXMLMetadataConfiguration($paths, $isDevMode); $connection = DriverManager::getConnection($connectionParams, $configuration); diff --git a/tests/Functional/ApplicationInstallations/UseCase/Install/HandlerTest.php b/tests/Functional/ApplicationInstallations/UseCase/Install/HandlerTest.php index 45846d3..d1a8876 100644 --- a/tests/Functional/ApplicationInstallations/UseCase/Install/HandlerTest.php +++ b/tests/Functional/ApplicationInstallations/UseCase/Install/HandlerTest.php @@ -16,7 +16,7 @@ use Bitrix24\Lib\Bitrix24Accounts; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; use Bitrix24\Lib\Services\Flusher; use Bitrix24\Lib\ApplicationInstallations; use Bitrix24\Lib\Tests\EntityManagerFactory; diff --git a/tests/Functional/ApplicationInstallations/UseCase/OnAppInstall/HandlerTest.php b/tests/Functional/ApplicationInstallations/UseCase/OnAppInstall/HandlerTest.php index ea179a5..9ed7384 100644 --- a/tests/Functional/ApplicationInstallations/UseCase/OnAppInstall/HandlerTest.php +++ b/tests/Functional/ApplicationInstallations/UseCase/OnAppInstall/HandlerTest.php @@ -16,7 +16,7 @@ use Bitrix24\Lib\Bitrix24Accounts; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; use Bitrix24\Lib\Services\Flusher; use Bitrix24\Lib\ApplicationInstallations; use Bitrix24\Lib\Tests\EntityManagerFactory; diff --git a/tests/Functional/ApplicationInstallations/UseCase/Uninstall/HandlerTest.php b/tests/Functional/ApplicationInstallations/UseCase/Uninstall/HandlerTest.php index 2728053..c3ecbfa 100644 --- a/tests/Functional/ApplicationInstallations/UseCase/Uninstall/HandlerTest.php +++ b/tests/Functional/ApplicationInstallations/UseCase/Uninstall/HandlerTest.php @@ -16,7 +16,7 @@ use Bitrix24\Lib\Bitrix24Accounts; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; use Bitrix24\Lib\Services\Flusher; use Bitrix24\Lib\ApplicationInstallations; use Bitrix24\Lib\Tests\EntityManagerFactory; diff --git a/tests/Functional/Bitrix24Accounts/UseCase/ChangeDomainUrl/HandlerTest.php b/tests/Functional/Bitrix24Accounts/UseCase/ChangeDomainUrl/HandlerTest.php index 01e2d5a..e9c70e5 100644 --- a/tests/Functional/Bitrix24Accounts/UseCase/ChangeDomainUrl/HandlerTest.php +++ b/tests/Functional/Bitrix24Accounts/UseCase/ChangeDomainUrl/HandlerTest.php @@ -29,7 +29,7 @@ use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Uid\Uuid; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; /** * @internal diff --git a/tests/Functional/Bitrix24Accounts/UseCase/InstallFinish/HandlerTest.php b/tests/Functional/Bitrix24Accounts/UseCase/InstallFinish/HandlerTest.php index 5d84d7e..66e22a8 100644 --- a/tests/Functional/Bitrix24Accounts/UseCase/InstallFinish/HandlerTest.php +++ b/tests/Functional/Bitrix24Accounts/UseCase/InstallFinish/HandlerTest.php @@ -30,7 +30,7 @@ use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Uid\Uuid; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; /** * @internal diff --git a/tests/Functional/Bitrix24Accounts/UseCase/InstallStart/HandlerTest.php b/tests/Functional/Bitrix24Accounts/UseCase/InstallStart/HandlerTest.php index d179e46..578c497 100644 --- a/tests/Functional/Bitrix24Accounts/UseCase/InstallStart/HandlerTest.php +++ b/tests/Functional/Bitrix24Accounts/UseCase/InstallStart/HandlerTest.php @@ -33,7 +33,7 @@ use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Stopwatch\Stopwatch; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; /** * @internal diff --git a/tests/Unit/ApplicationInstallations/UseCase/Install/CommandTest.php b/tests/Unit/ApplicationInstallations/UseCase/Install/CommandTest.php index 0afee06..03c3182 100644 --- a/tests/Unit/ApplicationInstallations/UseCase/Install/CommandTest.php +++ b/tests/Unit/ApplicationInstallations/UseCase/Install/CommandTest.php @@ -5,7 +5,7 @@ namespace Bitrix24\Lib\Tests\Unit\ApplicationInstallations\UseCase\Install; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; use Bitrix24\Lib\Tests\Functional\Bitrix24Accounts\Builders\Bitrix24AccountBuilder; use Bitrix24\SDK\Application\ApplicationStatus; use Bitrix24\SDK\Application\PortalLicenseFamily; diff --git a/tests/Unit/ApplicationInstallations/UseCase/OnAppInstall/CommandTest.php b/tests/Unit/ApplicationInstallations/UseCase/OnAppInstall/CommandTest.php index d530274..c9b3d26 100644 --- a/tests/Unit/ApplicationInstallations/UseCase/OnAppInstall/CommandTest.php +++ b/tests/Unit/ApplicationInstallations/UseCase/OnAppInstall/CommandTest.php @@ -5,7 +5,7 @@ namespace Bitrix24\Lib\Tests\Unit\ApplicationInstallations\UseCase\OnAppInstall; use Bitrix24\Lib\ApplicationInstallations\UseCase\OnAppInstall\Command; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; use Bitrix24\Lib\Tests\Functional\ApplicationInstallations\Builders\ApplicationInstallationBuilder; use Bitrix24\Lib\Tests\Functional\Bitrix24Accounts\Builders\Bitrix24AccountBuilder; use Bitrix24\SDK\Application\ApplicationStatus; diff --git a/tests/Unit/ApplicationInstallations/UseCase/Uninstall/CommandTest.php b/tests/Unit/ApplicationInstallations/UseCase/Uninstall/CommandTest.php index 75cc13f..51b7d6a 100644 --- a/tests/Unit/ApplicationInstallations/UseCase/Uninstall/CommandTest.php +++ b/tests/Unit/ApplicationInstallations/UseCase/Uninstall/CommandTest.php @@ -5,7 +5,7 @@ namespace Bitrix24\Lib\Tests\Unit\ApplicationInstallations\UseCase\Uninstall; use Bitrix24\Lib\ApplicationInstallations\UseCase\Uninstall\Command; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; use Bitrix24\Lib\Tests\Functional\Bitrix24Accounts\Builders\Bitrix24AccountBuilder; use Bitrix24\SDK\Application\Contracts\Bitrix24Accounts\Entity\Bitrix24AccountStatus; use Bitrix24\SDK\Core\Credentials\Scope; diff --git a/tests/Unit/Bitrix24Accounts/UseCase/ChangeDomainUrl/CommandTest.php b/tests/Unit/Bitrix24Accounts/UseCase/ChangeDomainUrl/CommandTest.php index f8deb49..8dc5c47 100644 --- a/tests/Unit/Bitrix24Accounts/UseCase/ChangeDomainUrl/CommandTest.php +++ b/tests/Unit/Bitrix24Accounts/UseCase/ChangeDomainUrl/CommandTest.php @@ -5,7 +5,7 @@ namespace Bitrix24\Lib\Tests\Unit\Bitrix24Accounts\UseCase\ChangeDomainUrl; use Bitrix24\Lib\Bitrix24Accounts\UseCase\ChangeDomainUrl\Command; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; diff --git a/tests/Unit/Bitrix24Accounts/UseCase/InstallFinish/CommandTest.php b/tests/Unit/Bitrix24Accounts/UseCase/InstallFinish/CommandTest.php index 44559ef..dfc1200 100644 --- a/tests/Unit/Bitrix24Accounts/UseCase/InstallFinish/CommandTest.php +++ b/tests/Unit/Bitrix24Accounts/UseCase/InstallFinish/CommandTest.php @@ -12,7 +12,7 @@ use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Symfony\Component\Uid\Uuid; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; /** diff --git a/tests/Unit/Bitrix24Accounts/UseCase/InstallStart/CommandTest.php b/tests/Unit/Bitrix24Accounts/UseCase/InstallStart/CommandTest.php index 27a39a4..bf4de15 100644 --- a/tests/Unit/Bitrix24Accounts/UseCase/InstallStart/CommandTest.php +++ b/tests/Unit/Bitrix24Accounts/UseCase/InstallStart/CommandTest.php @@ -14,7 +14,7 @@ use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Symfony\Component\Uid\Uuid; -use Bitrix24\Lib\Bitrix24Accounts\ValueObjects\Domain; +use Bitrix24\Lib\Kernel\ValueObjects\Domain; /** diff --git a/tests/Unit/Journal/Entity/JournalItemTest.php b/tests/Unit/Journal/Entity/JournalItemTest.php index 0aeef62..352caac 100644 --- a/tests/Unit/Journal/Entity/JournalItemTest.php +++ b/tests/Unit/Journal/Entity/JournalItemTest.php @@ -15,7 +15,7 @@ use Bitrix24\Lib\Journal\Entity\JournalItem; use Bitrix24\Lib\Journal\Entity\LogLevel; -use Bitrix24\Lib\Journal\ValueObjects\JournalContext; +use Bitrix24\Lib\Journal\Entity\ValueObjects\Context; use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; use PHPUnit\Framework\TestCase; use Symfony\Component\Uid\Uuid; @@ -24,104 +24,111 @@ class JournalItemTest extends TestCase { private Uuid $applicationInstallationId; + private string $memberId; + + #[\Override] protected function setUp(): void { $this->applicationInstallationId = Uuid::v7(); + $this->memberId = 'test-member-id'; } public function testCreateJournalItemWithInfoLevel(): void { $message = 'Test info message'; - $context = new JournalContext( - label: 'test.label', + $label = 'test.label'; + $userName = 'test-user'; + $journalContext = new Context( payload: ['key' => 'value'], bitrix24UserId: 123 ); - $item = JournalItem::info($this->applicationInstallationId, $message, $context); - - $this->assertInstanceOf(JournalItem::class, $item); - $this->assertSame(LogLevel::info, $item->getLevel()); - $this->assertSame($message, $item->getMessage()); - $this->assertTrue($item->getApplicationInstallationId()->equals($this->applicationInstallationId)); - $this->assertSame('test.label', $item->getContext()->getLabel()); - $this->assertSame(['key' => 'value'], $item->getContext()->getPayload()); - $this->assertSame(123, $item->getContext()->getBitrix24UserId()); + $journalItem = JournalItem::info($this->memberId, $this->applicationInstallationId, $message, $label, $userName, $journalContext); + + $this->assertInstanceOf(JournalItem::class, $journalItem); + $this->assertSame(LogLevel::info, $journalItem->getLevel()); + $this->assertSame($this->memberId, $journalItem->getMemberId()); + $this->assertSame($message, $journalItem->getMessage()); + $this->assertSame($label, $journalItem->getLabel()); + $this->assertSame($userName, $journalItem->getUserId()); + $this->assertTrue($journalItem->getApplicationInstallationId()->equals($this->applicationInstallationId)); + $this->assertSame(['key' => 'value'], $journalItem->getContext()->getPayload()); + $this->assertSame(123, $journalItem->getContext()->getBitrix24UserId()); } public function testCreateJournalItemWithEmergencyLevel(): void { - $context = new JournalContext('emergency.label'); - $item = JournalItem::emergency($this->applicationInstallationId, 'Emergency message', $context); + $journalContext = new Context(); + $journalItem = JournalItem::emergency($this->memberId, $this->applicationInstallationId, 'Emergency message', 'emergency.label', null, $journalContext); - $this->assertSame(LogLevel::emergency, $item->getLevel()); - $this->assertSame('Emergency message', $item->getMessage()); + $this->assertSame(LogLevel::emergency, $journalItem->getLevel()); + $this->assertSame('Emergency message', $journalItem->getMessage()); } public function testCreateJournalItemWithAlertLevel(): void { - $context = new JournalContext('alert.label'); - $item = JournalItem::alert($this->applicationInstallationId, 'Alert message', $context); + $journalContext = new Context(); + $journalItem = JournalItem::alert($this->memberId, $this->applicationInstallationId, 'Alert message', 'alert.label', null, $journalContext); - $this->assertSame(LogLevel::alert, $item->getLevel()); + $this->assertSame(LogLevel::alert, $journalItem->getLevel()); } public function testCreateJournalItemWithCriticalLevel(): void { - $context = new JournalContext('critical.label'); - $item = JournalItem::critical($this->applicationInstallationId, 'Critical message', $context); + $journalContext = new Context(); + $journalItem = JournalItem::critical($this->memberId, $this->applicationInstallationId, 'Critical message', 'critical.label', null, $journalContext); - $this->assertSame(LogLevel::critical, $item->getLevel()); + $this->assertSame(LogLevel::critical, $journalItem->getLevel()); } public function testCreateJournalItemWithErrorLevel(): void { - $context = new JournalContext('error.label'); - $item = JournalItem::error($this->applicationInstallationId, 'Error message', $context); + $journalContext = new Context(); + $journalItem = JournalItem::error($this->memberId, $this->applicationInstallationId, 'Error message', 'error.label', null, $journalContext); - $this->assertSame(LogLevel::error, $item->getLevel()); + $this->assertSame(LogLevel::error, $journalItem->getLevel()); } public function testCreateJournalItemWithWarningLevel(): void { - $context = new JournalContext('warning.label'); - $item = JournalItem::warning($this->applicationInstallationId, 'Warning message', $context); + $journalContext = new Context(); + $journalItem = JournalItem::warning($this->memberId, $this->applicationInstallationId, 'Warning message', 'warning.label', null, $journalContext); - $this->assertSame(LogLevel::warning, $item->getLevel()); + $this->assertSame(LogLevel::warning, $journalItem->getLevel()); } public function testCreateJournalItemWithNoticeLevel(): void { - $context = new JournalContext('notice.label'); - $item = JournalItem::notice($this->applicationInstallationId, 'Notice message', $context); + $journalContext = new Context(); + $journalItem = JournalItem::notice($this->memberId, $this->applicationInstallationId, 'Notice message', 'notice.label', null, $journalContext); - $this->assertSame(LogLevel::notice, $item->getLevel()); + $this->assertSame(LogLevel::notice, $journalItem->getLevel()); } public function testCreateJournalItemWithDebugLevel(): void { - $context = new JournalContext('debug.label'); - $item = JournalItem::debug($this->applicationInstallationId, 'Debug message', $context); + $journalContext = new Context(); + $journalItem = JournalItem::debug($this->memberId, $this->applicationInstallationId, 'Debug message', 'debug.label', null, $journalContext); - $this->assertSame(LogLevel::debug, $item->getLevel()); + $this->assertSame(LogLevel::debug, $journalItem->getLevel()); } public function testJournalItemHasUniqueId(): void { - $context = new JournalContext('test.label'); - $item1 = JournalItem::info($this->applicationInstallationId, 'Message 1', $context); - $item2 = JournalItem::info($this->applicationInstallationId, 'Message 2', $context); + $journalContext = new Context(); + $journalItem = JournalItem::info($this->memberId, $this->applicationInstallationId, 'Message 1', 'test.label', null, $journalContext); + $item2 = JournalItem::info($this->memberId, $this->applicationInstallationId, 'Message 2', 'test.label', null, $journalContext); - $this->assertNotEquals($item1->getId()->toRfc4122(), $item2->getId()->toRfc4122()); + $this->assertNotEquals($journalItem->getId()->toRfc4122(), $item2->getId()->toRfc4122()); } public function testJournalItemHasCreatedAt(): void { - $context = new JournalContext('test.label'); - $item = JournalItem::info($this->applicationInstallationId, 'Test message', $context); + $journalContext = new Context(); + $journalItem = JournalItem::info($this->memberId, $this->applicationInstallationId, 'Test message', 'test.label', null, $journalContext); - $this->assertNotNull($item->getCreatedAt()); - $this->assertInstanceOf(\Carbon\CarbonImmutable::class, $item->getCreatedAt()); + $this->assertNotNull($journalItem->getCreatedAt()); + $this->assertInstanceOf(\Carbon\CarbonImmutable::class, $journalItem->getCreatedAt()); } public function testCreateJournalItemWithEmptyMessageThrowsException(): void @@ -129,8 +136,8 @@ public function testCreateJournalItemWithEmptyMessageThrowsException(): void $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Journal message cannot be empty'); - $context = new JournalContext('test.label'); - JournalItem::info($this->applicationInstallationId, '', $context); + $journalContext = new Context(); + JournalItem::info($this->memberId, $this->applicationInstallationId, '', 'test.label', null, $journalContext); } public function testCreateJournalItemWithWhitespaceMessageThrowsException(): void @@ -138,19 +145,28 @@ public function testCreateJournalItemWithWhitespaceMessageThrowsException(): voi $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Journal message cannot be empty'); - $context = new JournalContext('test.label'); - JournalItem::info($this->applicationInstallationId, ' ', $context); + $journalContext = new Context(); + JournalItem::info($this->memberId, $this->applicationInstallationId, ' ', 'test.label', null, $journalContext); + } + + public function testCreateJournalItemWithEmptyMemberIdThrowsException(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('memberId cannot be empty'); + + $journalContext = new Context(); + JournalItem::info('', $this->applicationInstallationId, 'Message', 'test.label', null, $journalContext); } - public function testJournalItemContextWithOnlyLabel(): void + public function testJournalItemContextWithoutLabel(): void { - $context = new JournalContext('test.label'); - $item = JournalItem::info($this->applicationInstallationId, 'Test message', $context); + $journalContext = new Context(); + $journalItem = JournalItem::info($this->memberId, $this->applicationInstallationId, 'Test message', 'test.label', null, $journalContext); - $this->assertSame('test.label', $item->getContext()->getLabel()); - $this->assertNull($item->getContext()->getPayload()); - $this->assertNull($item->getContext()->getBitrix24UserId()); - $this->assertNull($item->getContext()->getIpAddress()); + $this->assertSame('test.label', $journalItem->getLabel()); + $this->assertNull($journalItem->getContext()->getPayload()); + $this->assertNull($journalItem->getContext()->getBitrix24UserId()); + $this->assertNull($journalItem->getContext()->getIpAddress()); } public function testJournalItemWithComplexPayload(): void @@ -164,13 +180,16 @@ public function testJournalItemWithComplexPayload(): void ], ]; - $context = new JournalContext('sync.label', $payload); - $item = JournalItem::info( + $journalContext = new Context(payload: $payload); + $journalItem = JournalItem::info( + $this->memberId, $this->applicationInstallationId, 'Sync completed', - $context + 'sync.label', + null, + $journalContext ); - $this->assertSame($payload, $item->getContext()->getPayload()); + $this->assertSame($payload, $journalItem->getContext()->getPayload()); } } diff --git a/tests/Unit/Journal/Entity/LogLevelTest.php b/tests/Unit/Journal/Entity/LogLevelTest.php index 0b20b42..76e7715 100644 --- a/tests/Unit/Journal/Entity/LogLevelTest.php +++ b/tests/Unit/Journal/Entity/LogLevelTest.php @@ -20,50 +20,50 @@ class LogLevelTest extends TestCase { public function testFromPsr3LevelEmergency(): void { - $level = LogLevel::fromPsr3Level('emergency'); - $this->assertSame(LogLevel::emergency, $level); + $logLevel = LogLevel::fromPsr3Level('emergency'); + $this->assertSame(LogLevel::emergency, $logLevel); } public function testFromPsr3LevelAlert(): void { - $level = LogLevel::fromPsr3Level('alert'); - $this->assertSame(LogLevel::alert, $level); + $logLevel = LogLevel::fromPsr3Level('alert'); + $this->assertSame(LogLevel::alert, $logLevel); } public function testFromPsr3LevelCritical(): void { - $level = LogLevel::fromPsr3Level('critical'); - $this->assertSame(LogLevel::critical, $level); + $logLevel = LogLevel::fromPsr3Level('critical'); + $this->assertSame(LogLevel::critical, $logLevel); } public function testFromPsr3LevelError(): void { - $level = LogLevel::fromPsr3Level('error'); - $this->assertSame(LogLevel::error, $level); + $logLevel = LogLevel::fromPsr3Level('error'); + $this->assertSame(LogLevel::error, $logLevel); } public function testFromPsr3LevelWarning(): void { - $level = LogLevel::fromPsr3Level('warning'); - $this->assertSame(LogLevel::warning, $level); + $logLevel = LogLevel::fromPsr3Level('warning'); + $this->assertSame(LogLevel::warning, $logLevel); } public function testFromPsr3LevelNotice(): void { - $level = LogLevel::fromPsr3Level('notice'); - $this->assertSame(LogLevel::notice, $level); + $logLevel = LogLevel::fromPsr3Level('notice'); + $this->assertSame(LogLevel::notice, $logLevel); } public function testFromPsr3LevelInfo(): void { - $level = LogLevel::fromPsr3Level('info'); - $this->assertSame(LogLevel::info, $level); + $logLevel = LogLevel::fromPsr3Level('info'); + $this->assertSame(LogLevel::info, $logLevel); } public function testFromPsr3LevelDebug(): void { - $level = LogLevel::fromPsr3Level('debug'); - $this->assertSame(LogLevel::debug, $level); + $logLevel = LogLevel::fromPsr3Level('debug'); + $this->assertSame(LogLevel::debug, $logLevel); } public function testFromPsr3LevelCaseInsensitive(): void @@ -98,7 +98,7 @@ public function testAllLogLevelsExist(): void $cases = LogLevel::cases(); $this->assertCount(8, $cases); - $values = array_map(static fn (LogLevel $level): string => $level->value, $cases); + $values = array_map(static fn (LogLevel $logLevel): string => $logLevel->value, $cases); $this->assertContains('emergency', $values); $this->assertContains('alert', $values); diff --git a/tests/Unit/Journal/Infrastructure/InMemory/InMemoryJournalItemRepository.php b/tests/Unit/Journal/Infrastructure/InMemory/InMemoryJournalItemRepository.php index 5b8789b..cdaf183 100644 --- a/tests/Unit/Journal/Infrastructure/InMemory/InMemoryJournalItemRepository.php +++ b/tests/Unit/Journal/Infrastructure/InMemory/InMemoryJournalItemRepository.php @@ -20,7 +20,7 @@ use Symfony\Component\Uid\Uuid; /** - * In-memory implementation of JournalItemRepository for testing + * In-memory implementation of JournalItemRepository for testing. */ class InMemoryJournalItemRepository implements JournalItemRepositoryInterface { @@ -36,9 +36,9 @@ public function save(JournalItemInterface $journalItem): void } #[\Override] - public function findById(Uuid $id): ?JournalItemInterface + public function findById(Uuid $uuid): ?JournalItemInterface { - return $this->items[$id->toRfc4122()] ?? null; + return $this->items[$uuid->toRfc4122()] ?? null; } /** @@ -46,19 +46,24 @@ public function findById(Uuid $id): ?JournalItemInterface */ #[\Override] public function findByApplicationInstallationId( + string $memberId, Uuid $applicationInstallationId, - ?LogLevel $level = null, + ?LogLevel $logLevel = null, ?int $limit = null, ?int $offset = null ): array { $filtered = array_filter( $this->items, - static function (JournalItemInterface $item) use ($applicationInstallationId, $level): bool { - if (!$item->getApplicationInstallationId()->equals($applicationInstallationId)) { + static function (JournalItemInterface $journalItem) use ($applicationInstallationId, $memberId, $logLevel): bool { + if ($journalItem->getMemberId() !== $memberId) { return false; } - if ($level !== null && $item->getLevel() !== $level) { + if (!$journalItem->getApplicationInstallationId()->equals($applicationInstallationId)) { + return false; + } + + if (null !== $logLevel && $journalItem->getLevel() !== $logLevel) { return false; } @@ -67,41 +72,70 @@ static function (JournalItemInterface $item) use ($applicationInstallationId, $l ); // Sort by created date descending - usort($filtered, static function (JournalItemInterface $a, JournalItemInterface $b): int { - return $b->getCreatedAt()->getTimestamp() <=> $a->getCreatedAt()->getTimestamp(); - }); + usort($filtered, static fn (JournalItemInterface $a, JournalItemInterface $b): int => $b->getCreatedAt()->getTimestamp() <=> $a->getCreatedAt()->getTimestamp()); - if ($offset !== null) { + if (null !== $offset) { $filtered = array_slice($filtered, $offset); } - if ($limit !== null) { - $filtered = array_slice($filtered, 0, $limit); + if (null !== $limit) { + return array_slice($filtered, 0, $limit); } return $filtered; } + /** + * @return JournalItemInterface[] + */ #[\Override] - public function deleteByApplicationInstallationId(Uuid $applicationInstallationId): int - { - $count = 0; - foreach ($this->items as $key => $item) { - if ($item->getApplicationInstallationId()->equals($applicationInstallationId)) { - unset($this->items[$key]); - ++$count; + public function findByMemberId( + string $memberId, + ?LogLevel $logLevel = null, + ?int $limit = null, + ?int $offset = null + ): array { + $filtered = array_filter( + $this->items, + static function (JournalItemInterface $journalItem) use ($memberId, $logLevel): bool { + if ($journalItem->getMemberId() !== $memberId) { + return false; + } + + if (null !== $logLevel && $journalItem->getLevel() !== $logLevel) { + return false; + } + + return true; } + ); + + // Sort by created date descending + usort($filtered, static fn (JournalItemInterface $a, JournalItemInterface $b): int => $b->getCreatedAt()->getTimestamp() <=> $a->getCreatedAt()->getTimestamp()); + + if (null !== $offset) { + $filtered = array_slice($filtered, $offset); } - return $count; + if (null !== $limit) { + return array_slice($filtered, 0, $limit); + } + + return $filtered; } #[\Override] - public function deleteOlderThan(CarbonImmutable $date): int - { + public function deleteOlderThan( + string $memberId, + Uuid $applicationInstallationId, + CarbonImmutable $date + ): int { $count = 0; foreach ($this->items as $key => $item) { - if ($item->getCreatedAt()->isBefore($date)) { + if ($item->getMemberId() === $memberId + && $item->getApplicationInstallationId()->equals($applicationInstallationId) + && $item->getCreatedAt()->isBefore($date) + ) { unset($this->items[$key]); ++$count; } @@ -110,14 +144,8 @@ public function deleteOlderThan(CarbonImmutable $date): int return $count; } - #[\Override] - public function countByApplicationInstallationId(Uuid $applicationInstallationId, ?LogLevel $level = null): int - { - return count($this->findByApplicationInstallationId($applicationInstallationId, $level)); - } - /** - * Get all items (for testing purposes) + * Get all items (for testing purposes). * * @return JournalItemInterface[] */ @@ -127,7 +155,7 @@ public function findAll(): array } /** - * Clear all items (for testing purposes) + * Clear all items (for testing purposes). */ public function clear(): void { diff --git a/tests/Unit/Journal/Infrastructure/InMemoryJournalItemRepositoryTest.php b/tests/Unit/Journal/Infrastructure/InMemoryJournalItemRepositoryTest.php index 19c3d52..86c3b21 100644 --- a/tests/Unit/Journal/Infrastructure/InMemoryJournalItemRepositoryTest.php +++ b/tests/Unit/Journal/Infrastructure/InMemoryJournalItemRepositoryTest.php @@ -15,36 +15,45 @@ use Bitrix24\Lib\Journal\Entity\JournalItem; use Bitrix24\Lib\Journal\Entity\LogLevel; -use Bitrix24\Lib\Journal\ValueObjects\JournalContext; +use Bitrix24\Lib\Journal\Entity\ValueObjects\Context; use Bitrix24\Lib\Tests\Unit\Journal\Infrastructure\InMemory\InMemoryJournalItemRepository; use Carbon\CarbonImmutable; use PHPUnit\Framework\TestCase; use Symfony\Component\Uid\Uuid; +/** + * @internal + * + * @coversNothing + */ class InMemoryJournalItemRepositoryTest extends TestCase { private InMemoryJournalItemRepository $repository; private Uuid $applicationInstallationId; + private string $memberId; + + #[\Override] protected function setUp(): void { $this->repository = new InMemoryJournalItemRepository(); $this->applicationInstallationId = Uuid::v7(); + $this->memberId = 'test-member-id'; } public function testSaveAndFindById(): void { - $context = new JournalContext('test.label'); - $item = JournalItem::info($this->applicationInstallationId, 'Test message', $context); + $journalContext = new Context(['key' => 'value']); + $journalItem = JournalItem::info($this->memberId, $this->applicationInstallationId, 'Test message', 'test.label', null, $journalContext); - $this->repository->save($item); + $this->repository->save($journalItem); - $found = $this->repository->findById($item->getId()); + $found = $this->repository->findById($journalItem->getId()); $this->assertNotNull($found); - $this->assertSame($item->getId()->toRfc4122(), $found->getId()->toRfc4122()); - $this->assertSame($item->getMessage(), $found->getMessage()); + $this->assertSame($journalItem->getId()->toRfc4122(), $found->getId()->toRfc4122()); + $this->assertSame($journalItem->getMessage(), $found->getMessage()); } public function testFindByIdReturnsNullForNonexistent(): void @@ -56,34 +65,35 @@ public function testFindByIdReturnsNullForNonexistent(): void public function testFindByApplicationInstallationId(): void { - $context = new JournalContext('test.label'); - $item1 = JournalItem::info($this->applicationInstallationId, 'Message 1', $context); - $item2 = JournalItem::error($this->applicationInstallationId, 'Message 2', $context); - $item3 = JournalItem::info(Uuid::v7(), 'Message 3', $context); // Different installation + $journalContext = new Context(['key' => 'value']); + $journalItem = JournalItem::info($this->memberId, $this->applicationInstallationId, 'Message 1', 'test.label', null, $journalContext); + $item2 = JournalItem::error($this->memberId, $this->applicationInstallationId, 'Message 2', 'test.label', null, $journalContext); + $item3 = JournalItem::info('other-member', Uuid::v7(), 'Message 3', 'test.label', null, $journalContext); // Different installation - $this->repository->save($item1); + $this->repository->save($journalItem); $this->repository->save($item2); $this->repository->save($item3); - $items = $this->repository->findByApplicationInstallationId($this->applicationInstallationId); + $items = $this->repository->findByApplicationInstallationId($this->memberId, $this->applicationInstallationId); $this->assertCount(2, $items); } public function testFindByApplicationInstallationIdWithLevelFilter(): void { - $context = new JournalContext('test.label'); - $item1 = JournalItem::info($this->applicationInstallationId, 'Message 1', $context); - $item2 = JournalItem::error($this->applicationInstallationId, 'Message 2', $context); - $item3 = JournalItem::info($this->applicationInstallationId, 'Message 3', $context); + $journalContext = new Context(['key' => 'value']); + $journalItem = JournalItem::info($this->memberId, $this->applicationInstallationId, 'Message 1', 'test.label', null, $journalContext); + $item2 = JournalItem::error($this->memberId, $this->applicationInstallationId, 'Message 2', 'test.label', null, $journalContext); + $item3 = JournalItem::info($this->memberId, $this->applicationInstallationId, 'Message 3', 'test.label', null, $journalContext); - $this->repository->save($item1); + $this->repository->save($journalItem); $this->repository->save($item2); $this->repository->save($item3); $items = $this->repository->findByApplicationInstallationId( + $this->memberId, $this->applicationInstallationId, - LogLevel::info + logLevel: LogLevel::info ); $this->assertCount(2, $items); @@ -92,15 +102,35 @@ public function testFindByApplicationInstallationIdWithLevelFilter(): void } } + public function testFindByMemberId(): void + { + $journalContext = new Context(['key' => 'value']); + $journalItem = JournalItem::info($this->memberId, $this->applicationInstallationId, 'Message 1', 'test.label', null, $journalContext); + $item2 = JournalItem::error($this->memberId, $this->applicationInstallationId, 'Message 2', 'test.label', null, $journalContext); + $item3 = JournalItem::info('other-member', Uuid::v7(), 'Message 3', 'test.label', null, $journalContext); + + $this->repository->save($journalItem); + $this->repository->save($item2); + $this->repository->save($item3); + + $items = $this->repository->findByMemberId($this->memberId); + + $this->assertCount(2, $items); + foreach ($items as $item) { + $this->assertSame($this->memberId, $item->getMemberId()); + } + } + public function testFindByApplicationInstallationIdWithLimit(): void { - $context = new JournalContext('test.label'); + $journalContext = new Context(['key' => 'value']); for ($i = 1; $i <= 5; ++$i) { - $item = JournalItem::info($this->applicationInstallationId, "Message {$i}", $context); + $item = JournalItem::info($this->memberId, $this->applicationInstallationId, 'Message '.$i, 'test.label', null, $journalContext); $this->repository->save($item); } $items = $this->repository->findByApplicationInstallationId( + $this->memberId, $this->applicationInstallationId, limit: 3 ); @@ -110,13 +140,14 @@ public function testFindByApplicationInstallationIdWithLimit(): void public function testFindByApplicationInstallationIdWithOffset(): void { - $context = new JournalContext('test.label'); + $journalContext = new Context(['key' => 'value']); for ($i = 1; $i <= 5; ++$i) { - $item = JournalItem::info($this->applicationInstallationId, "Message {$i}", $context); + $item = JournalItem::info($this->memberId, $this->applicationInstallationId, 'Message '.$i, 'test.label', null, $journalContext); $this->repository->save($item); } $items = $this->repository->findByApplicationInstallationId( + $this->memberId, $this->applicationInstallationId, offset: 2 ); @@ -126,13 +157,14 @@ public function testFindByApplicationInstallationIdWithOffset(): void public function testFindByApplicationInstallationIdWithLimitAndOffset(): void { - $context = new JournalContext('test.label'); + $journalContext = new Context(['key' => 'value']); for ($i = 1; $i <= 10; ++$i) { - $item = JournalItem::info($this->applicationInstallationId, "Message {$i}", $context); + $item = JournalItem::info($this->memberId, $this->applicationInstallationId, 'Message '.$i, 'test.label', null, $journalContext); $this->repository->save($item); } $items = $this->repository->findByApplicationInstallationId( + $this->memberId, $this->applicationInstallationId, limit: 3, offset: 2 @@ -141,71 +173,24 @@ public function testFindByApplicationInstallationIdWithLimitAndOffset(): void $this->assertCount(3, $items); } - public function testDeleteByApplicationInstallationId(): void - { - $context = new JournalContext('test.label'); - $item1 = JournalItem::info($this->applicationInstallationId, 'Message 1', $context); - $item2 = JournalItem::info($this->applicationInstallationId, 'Message 2', $context); - $otherInstallationId = Uuid::v7(); - $item3 = JournalItem::info($otherInstallationId, 'Message 3', $context); - - $this->repository->save($item1); - $this->repository->save($item2); - $this->repository->save($item3); - - $deleted = $this->repository->deleteByApplicationInstallationId($this->applicationInstallationId); - - $this->assertSame(2, $deleted); - $this->assertEmpty($this->repository->findByApplicationInstallationId($this->applicationInstallationId)); - $this->assertCount(1, $this->repository->findByApplicationInstallationId($otherInstallationId)); - } - public function testDeleteOlderThan(): void { - $context = new JournalContext('test.label'); - $item = JournalItem::info($this->applicationInstallationId, 'Message', $context); - $this->repository->save($item); + $journalContext = new Context(['key' => 'value']); + $journalItem = JournalItem::info($this->memberId, $this->applicationInstallationId, 'Message', 'test.label', null, $journalContext); + $this->repository->save($journalItem); $futureDate = new CarbonImmutable('+1 day'); - $deleted = $this->repository->deleteOlderThan($futureDate); + $deleted = $this->repository->deleteOlderThan($this->memberId, $this->applicationInstallationId, $futureDate); // Item should be deleted as it's older than future date $this->assertSame(1, $deleted); } - public function testCountByApplicationInstallationId(): void - { - $context = new JournalContext('test.label'); - for ($i = 1; $i <= 5; ++$i) { - $item = JournalItem::info($this->applicationInstallationId, "Message {$i}", $context); - $this->repository->save($item); - } - - $count = $this->repository->countByApplicationInstallationId($this->applicationInstallationId); - - $this->assertSame(5, $count); - } - - public function testCountByApplicationInstallationIdWithLevelFilter(): void - { - $context = new JournalContext('test.label'); - $this->repository->save(JournalItem::info($this->applicationInstallationId, 'Info 1', $context)); - $this->repository->save(JournalItem::info($this->applicationInstallationId, 'Info 2', $context)); - $this->repository->save(JournalItem::error($this->applicationInstallationId, 'Error 1', $context)); - - $count = $this->repository->countByApplicationInstallationId( - $this->applicationInstallationId, - LogLevel::info - ); - - $this->assertSame(2, $count); - } - public function testClear(): void { - $context = new JournalContext('test.label'); - $item = JournalItem::info($this->applicationInstallationId, 'Message', $context); - $this->repository->save($item); + $journalContext = new Context(['key' => 'value']); + $journalItem = JournalItem::info($this->memberId, $this->applicationInstallationId, 'Message', 'test.label', null, $journalContext); + $this->repository->save($journalItem); $this->assertNotEmpty($this->repository->findAll()); @@ -216,11 +201,11 @@ public function testClear(): void public function testFindAll(): void { - $context = new JournalContext('test.label'); - $item1 = JournalItem::info($this->applicationInstallationId, 'Message 1', $context); - $item2 = JournalItem::error(Uuid::v7(), 'Message 2', $context); + $journalContext = new Context(['key' => 'value']); + $journalItem = JournalItem::info($this->memberId, $this->applicationInstallationId, 'Message 1', 'test.label', null, $journalContext); + $item2 = JournalItem::error('other-member', Uuid::v7(), 'Message 2', 'test.label', null, $journalContext); - $this->repository->save($item1); + $this->repository->save($journalItem); $this->repository->save($item2); $all = $this->repository->findAll(); diff --git a/tests/Unit/Journal/Services/JournalLoggerTest.php b/tests/Unit/Journal/Services/JournalLoggerTest.php index 66ad4ea..98b207e 100644 --- a/tests/Unit/Journal/Services/JournalLoggerTest.php +++ b/tests/Unit/Journal/Services/JournalLoggerTest.php @@ -28,15 +28,20 @@ class JournalLoggerTest extends TestCase private Uuid $applicationInstallationId; + private string $memberId; + private JournalLogger $logger; + #[\Override] protected function setUp(): void { $this->repository = new InMemoryJournalItemRepository(); $this->entityManager = $this->createMock(EntityManagerInterface::class); $this->applicationInstallationId = Uuid::v7(); + $this->memberId = 'test-member-id'; $this->logger = new JournalLogger( + $this->memberId, $this->applicationInstallationId, $this->repository, $this->entityManager @@ -47,13 +52,15 @@ public function testLogInfoMessage(): void { $this->entityManager->expects($this->once())->method('flush'); - $this->logger->info('Test info message', ['label' => 'test.label']); + $this->logger->info('Test info message', ['label' => 'test.label', 'userId' => 'test-user']); $items = $this->repository->findAll(); $this->assertCount(1, $items); $this->assertSame(LogLevel::info, $items[0]->getLevel()); + $this->assertSame($this->memberId, $items[0]->getMemberId()); $this->assertSame('Test info message', $items[0]->getMessage()); - $this->assertSame('test.label', $items[0]->getContext()->getLabel()); + $this->assertSame('test.label', $items[0]->getLabel()); + $this->assertSame('test-user', $items[0]->getUserId()); } public function testLogErrorMessage(): void @@ -65,7 +72,7 @@ public function testLogErrorMessage(): void $items = $this->repository->findAll(); $this->assertCount(1, $items); $this->assertSame(LogLevel::error, $items[0]->getLevel()); - $this->assertSame('error.label', $items[0]->getContext()->getLabel()); + $this->assertSame('error.label', $items[0]->getLabel()); } public function testLogWarningMessage(): void @@ -144,7 +151,7 @@ public function testLogWithContext(): void $items = $this->repository->findAll(); $item = $items[0]; - $this->assertSame('test.label', $item->getContext()->getLabel()); + $this->assertSame('test.label', $item->getLabel()); $this->assertSame(['key' => 'value'], $item->getContext()->getPayload()); $this->assertSame(123, $item->getContext()->getBitrix24UserId()); $this->assertNotNull($item->getContext()->getIpAddress()); @@ -157,7 +164,7 @@ public function testLogWithoutLabelUsesDefault(): void $this->logger->info('Test message without label'); $items = $this->repository->findAll(); - $this->assertSame('application.log', $items[0]->getContext()->getLabel()); + $this->assertSame('application.log', $items[0]->getLabel()); } public function testLogMultipleMessages(): void