diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f141291..170f1206 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,37 @@ ### Added +- Added service `Services\Biconnector\Dataset` with support methods, + see [biconnector.dataset.* methods](https://apidocs.bitrix24.com/api-reference/biconnector/dataset/index.html) ([#469](https://github.com/bitrix24/b24phpsdk/issues/469)): + - `add` adds a new dataset, with batch calls support + - `update` updates an existing dataset description, with batch calls support + - `get` gets information about the dataset by its identifier + - `list` gets the list of datasets, with batch calls support + - `delete` deletes a dataset, with batch calls support + - `fields` returns the fields description + - `updateFields` adds, updates visibility of, or deletes individual dataset columns (`biconnector.dataset.fields.update`) + - `count` counts datasets +- Added `dataset()` accessor to `BiconnectorServiceBuilder` ([#469](https://github.com/bitrix24/b24phpsdk/issues/469)) +- Added service `Services\Biconnector\Source` with support methods, + see [biconnector.source.* methods](https://apidocs.bitrix24.com/api-reference/biconnector/source/index.html) ([#469](https://github.com/bitrix24/b24phpsdk/issues/469)): + - `add` adds a new data source, with batch calls support + - `update` updates an existing data source, with batch calls support + - `get` gets information about the data source by its identifier + - `list` gets the list of data sources, with batch calls support + - `delete` deletes a data source, with batch calls support + - `fields` returns the fields description + - `count` counts data sources +- Added `source()` accessor to `BiconnectorServiceBuilder` ([#469](https://github.com/bitrix24/b24phpsdk/issues/469)) +- Added service `Services\Biconnector\Connector` with support methods, + see [biconnector.connector.* methods](https://github.com/bitrix24/b24phpsdk/issues/469): + - `add` adds a new connector, with batch calls support + - `update` updates an existing connector, with batch calls support + - `get` gets information about the connector by its identifier + - `list` gets the list of connectors, with batch calls support + - `delete` deletes a connector, with batch calls support + - `fields` returns the fields description + - `count` counts connectors + - Added support for events: - `onCrmDocumentGeneratorDocumentAdd` — fires when a document is created, see [event documentation](https://apidocs.bitrix24.com/api-reference/crm/document-generator/documents/events/on-crm-document-generator-document-add.html) diff --git a/Makefile b/Makefile index d3555c4f..616e36ef 100644 --- a/Makefile +++ b/Makefile @@ -445,6 +445,22 @@ integration_tests_crm_documentgenerator_document: integration_tests_crm_documentgenerator_template: docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_crm_documentgenerator_template +.PHONY: test-integration-scope-biconnector +test-integration-scope-biconnector: + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_scope_biconnector + +.PHONY: test-integration-biconnector-connector +test-integration-biconnector-connector: + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_biconnector_connector + +.PHONY: test-integration-biconnector-source +test-integration-biconnector-source: + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_biconnector_source + +.PHONY: test-integration-biconnector-dataset +test-integration-biconnector-dataset: + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_biconnector_dataset + # work dev environment .PHONY: php-dev-server-up php-dev-server-up: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index e9c12ee3..37d354cb 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -232,6 +232,21 @@ ./tests/Integration/Services/SonetGroup/ + + ./tests/Integration/Services/Biconnector/ + + + ./tests/Integration/Services/Biconnector/Connector/Service/ + ./tests/Integration/Services/Biconnector/Connector/Result/ + + + ./tests/Integration/Services/Biconnector/Source/Service/ + ./tests/Integration/Services/Biconnector/Source/Result/ + + + ./tests/Integration/Services/Biconnector/Dataset/Service/ + ./tests/Integration/Services/Biconnector/Dataset/Result/ + diff --git a/src/Services/Biconnector/BiconnectorServiceBuilder.php b/src/Services/Biconnector/BiconnectorServiceBuilder.php new file mode 100644 index 00000000..925f9da9 --- /dev/null +++ b/src/Services/Biconnector/BiconnectorServiceBuilder.php @@ -0,0 +1,103 @@ + + * + * 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\SDK\Services\Biconnector; + +use Bitrix24\SDK\Attributes\ApiServiceBuilderMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Services\AbstractServiceBuilder; +use Bitrix24\SDK\Services\Biconnector\Connector\Batch as ConnectorBatch; +use Bitrix24\SDK\Services\Biconnector\Connector\Service\Batch; +use Bitrix24\SDK\Services\Biconnector\Connector\Service\Connector; +use Bitrix24\SDK\Services\Biconnector\Dataset\Batch as DatasetBatch; +use Bitrix24\SDK\Services\Biconnector\Dataset\Service\Batch as DatasetServiceBatch; +use Bitrix24\SDK\Services\Biconnector\Dataset\Service\Dataset; +use Bitrix24\SDK\Services\Biconnector\Source\Batch as SourceBatch; +use Bitrix24\SDK\Services\Biconnector\Source\Service\Batch as SourceServiceBatch; +use Bitrix24\SDK\Services\Biconnector\Source\Service\Source; + +#[ApiServiceBuilderMetadata(new Scope(['biconnector']))] +class BiconnectorServiceBuilder extends AbstractServiceBuilder +{ + /** + * Get the Connector service + * + * Uses a specialized ConnectorBatch to handle biconnector.connector.* REST API differences: + * - list uses 'page' parameter (page number) instead of standard 'start' (offset) + * - delete uses lowercase 'id' instead of 'ID' + */ + public function connector(): Connector + { + if (!isset($this->serviceCache[__METHOD__])) { + // Use specialized Batch for Connector to ensure correct REST parameter mapping + $connectorBatch = new ConnectorBatch( + $this->core, + $this->log + ); + $this->serviceCache[__METHOD__] = new Connector( + new Batch($connectorBatch, $this->log), + $this->core, + $this->log + ); + } + + return $this->serviceCache[__METHOD__]; + } + + /** + * Get the Dataset service + * + * Uses a specialized DatasetBatch to handle biconnector.dataset.* REST API differences: + * - list uses 'page' parameter (page number) instead of standard 'start' (offset) + * - delete uses lowercase 'id' instead of 'ID' + */ + public function dataset(): Dataset + { + if (!isset($this->serviceCache[__METHOD__])) { + $datasetBatch = new DatasetBatch( + $this->core, + $this->log + ); + $this->serviceCache[__METHOD__] = new Dataset( + new DatasetServiceBatch($datasetBatch, $this->log), + $this->core, + $this->log + ); + } + + return $this->serviceCache[__METHOD__]; + } + + /** + * Get the Source service + * + * Uses a specialized SourceBatch to handle biconnector.source.* REST API differences: + * - delete uses lowercase 'id' instead of 'ID' + */ + public function source(): Source + { + if (!isset($this->serviceCache[__METHOD__])) { + $sourceBatch = new SourceBatch( + $this->core, + $this->log + ); + $this->serviceCache[__METHOD__] = new Source( + new SourceServiceBatch($sourceBatch, $this->log), + $this->core, + $this->log + ); + } + + return $this->serviceCache[__METHOD__]; + } +} diff --git a/src/Services/Biconnector/Connector/Batch.php b/src/Services/Biconnector/Connector/Batch.php new file mode 100644 index 00000000..b1323e80 --- /dev/null +++ b/src/Services/Biconnector/Connector/Batch.php @@ -0,0 +1,266 @@ + + * + * 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\SDK\Services\Biconnector\Connector; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\SDK\Core\Response\DTO\ResponseData; +use Generator; + +/** + * Class Batch + * + * Overrides base Batch to handle parameter naming differences in biconnector.connector.* REST methods: + * - list uses 'page' (page number, 50 records per page) instead of 'start' (offset) for pagination + * - delete uses lowercase 'id' instead of 'ID' + * + * @see https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-list.html + * @see https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-delete.html + */ +class Batch extends \Bitrix24\SDK\Core\Batch +{ + /** + * Determines the ID key — lowercase 'id' for biconnector connector + */ + #[\Override] + protected function determineKeyId(string $apiMethod, ?array $additionalParameters): string + { + return 'id'; + } + + /** + * Delete entity items with batch call using lowercase 'id' parameter + * + * @param int[] $entityItemId + * @param array|null $additionalParameters + * + * @return Generator|ResponseData[] + * @throws BaseException + */ + #[\Override] + public function deleteEntityItems( + string $apiMethod, + array $entityItemId, + ?array $additionalParameters = null + ): Generator { + $this->logger->debug( + 'deleteEntityItems.start', + [ + 'apiMethod' => $apiMethod, + 'entityItems' => $entityItemId, + 'additionalParameters' => $additionalParameters, + ] + ); + + try { + $this->clearCommands(); + foreach ($entityItemId as $cnt => $itemId) { + if (!is_int($itemId)) { + throw new InvalidArgumentException( + sprintf( + 'invalid type «%s» of connector id «%s» at position %s, connector id must be integer type', + gettype($itemId), + $itemId, + $cnt + ) + ); + } + + $this->registerCommand($apiMethod, ['id' => $itemId]); + } + + foreach ($this->getTraversable(true) as $cnt => $deletedItemResult) { + yield $cnt => $deletedItemResult; + } + } catch (InvalidArgumentException $exception) { + $errorMessage = sprintf('batch delete connector items: %s', $exception->getMessage()); + $this->logger->error( + $errorMessage, + [ + 'trace' => $exception->getTrace(), + ] + ); + throw $exception; + } catch (\Throwable $exception) { + $errorMessage = sprintf('batch delete connector items: %s', $exception->getMessage()); + $this->logger->error( + $errorMessage, + [ + 'trace' => $exception->getTrace(), + ] + ); + + throw new BaseException($errorMessage, $exception->getCode(), $exception); + } + + $this->logger->debug('deleteEntityItems.finish'); + } + + /** + * Get traversable list using page-based pagination. + * + * The biconnector.connector.list method uses 'page' parameter (page number, 50 records per page) + * instead of the standard 'start' (offset) parameter used by most other REST methods. + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-list.html + * + * @param array $order + * @param array $filter + * @param array $select + * + * @return Generator + * @throws BaseException + * @throws \Bitrix24\SDK\Core\Exceptions\TransportException + */ + #[\Override] + public function getTraversableList( + string $apiMethod, + ?array $order = [], + ?array $filter = [], + ?array $select = [], + ?int $limit = null, + ?array $additionalParameters = null + ): Generator { + yield from $this->getTraversableListWithCount( + $apiMethod, + $order ?? [], + $filter ?? [], + $select ?? [], + $limit, + $additionalParameters + ); + } + + /** + * Get traversable list using page-based pagination (page number, 50 records per page). + * + * The biconnector.connector.list method accepts 'page' parameter instead of 'start'. + * Page 1 returns items 1–50, page 2 returns items 51–100, etc. + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-list.html + * + * @param array $order + * @param array $filter + * @param array $select + * + * @return Generator + * @throws BaseException + * @throws \Bitrix24\SDK\Core\Exceptions\TransportException + */ + #[\Override] + public function getTraversableListWithCount( + string $apiMethod, + array $order, + array $filter, + array $select, + ?int $limit = null, + ?array $additionalParameters = null + ): Generator { + $this->logger->debug( + 'getTraversableListWithCount.start', + [ + 'apiMethod' => $apiMethod, + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'limit' => $limit, + 'additionalParameters' => $additionalParameters, + ] + ); + + $this->clearCommands(); + + // Fetch first page to determine total count + $params = [ + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'page' => 1, + ]; + + if ($additionalParameters !== null) { + $params = array_merge($params, $additionalParameters); + } + + $response = $this->core->call($apiMethod, $params); + $total = $response->getResponseData()->getPagination()->getTotal(); + + $this->logger->debug( + 'getTraversableListWithCount.totalElementsCount', + [ + 'totalElementsCount' => $total, + ] + ); + + if ($total <= self::MAX_ELEMENTS_IN_PAGE) { + $elementsCounter = 0; + foreach ($response->getResponseData()->getResult() as $item) { + $elementsCounter++; + if ($limit !== null && $elementsCounter > $limit) { + return; + } + + yield $item; + } + + return; + } + + // Register batch commands for all pages + $totalPages = (int)ceil($total / self::MAX_ELEMENTS_IN_PAGE); + for ($page = 1; $page <= $totalPages; $page++) { + $pageParams = [ + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'page' => $page, + ]; + + if ($additionalParameters !== null) { + $pageParams = array_merge($pageParams, $additionalParameters); + } + + $this->registerCommand($apiMethod, $pageParams); + + if ($limit !== null && $limit < $page * self::MAX_ELEMENTS_IN_PAGE) { + break; + } + } + + $this->logger->debug( + 'getTraversableListWithCount.commandsRegistered', + [ + 'commandsCount' => $this->commands->count(), + 'totalItemsToSelect' => $total, + ] + ); + + $elementsCounter = 0; + foreach ($this->getTraversable(true) as $queryResultData) { + $resultElements = $this->extractElementsFromBatchResult($queryResultData, false); + foreach ($resultElements as $resultElement) { + ++$elementsCounter; + if ($limit !== null && $elementsCounter > $limit) { + return; + } + + yield $resultElement; + } + } + + $this->logger->debug('getTraversableListWithCount.finish'); + } +} + + diff --git a/src/Services/Biconnector/Connector/Result/AddedConnectorBatchResult.php b/src/Services/Biconnector/Connector/Result/AddedConnectorBatchResult.php new file mode 100644 index 00000000..6a3c7f13 --- /dev/null +++ b/src/Services/Biconnector/Connector/Result/AddedConnectorBatchResult.php @@ -0,0 +1,38 @@ + + * + * 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\SDK\Services\Biconnector\Connector\Result; + +use Bitrix24\SDK\Core\Result\AddedItemBatchResult; + +/** + * Class AddedConnectorBatchResult + */ +class AddedConnectorBatchResult extends AddedItemBatchResult +{ + #[\Override] + public function getId(): int + { + $result = $this->getResponseData()->getResult(); + + if (!empty($result['connector']['id'])) { + return (int)$result['connector']['id']; + } + + if (!empty($result['id'])) { + return (int)$result['id']; + } + + return (int)$result; + } +} diff --git a/src/Services/Biconnector/Connector/Result/AddedConnectorResult.php b/src/Services/Biconnector/Connector/Result/AddedConnectorResult.php new file mode 100644 index 00000000..6f16fc0e --- /dev/null +++ b/src/Services/Biconnector/Connector/Result/AddedConnectorResult.php @@ -0,0 +1,42 @@ + + * + * 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\SDK\Services\Biconnector\Connector\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AddedItemResult; + +/** + * Class AddedConnectorResult + */ +class AddedConnectorResult extends AddedItemResult +{ + /** + * @throws BaseException + */ + #[\Override] + public function getId(): int + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + if (!empty($result['connector']['id'])) { + return (int)$result['connector']['id']; + } + + if (!empty($result['id'])) { + return (int)$result['id']; + } + + return (int)$result; + } +} diff --git a/src/Services/Biconnector/Connector/Result/ConnectorItemResult.php b/src/Services/Biconnector/Connector/Result/ConnectorItemResult.php new file mode 100644 index 00000000..479b16b6 --- /dev/null +++ b/src/Services/Biconnector/Connector/Result/ConnectorItemResult.php @@ -0,0 +1,53 @@ + + * + * 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\SDK\Services\Biconnector\Connector\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; +use Carbon\CarbonImmutable; + +/** + * Class ConnectorItemResult + * + * Field names correspond to the actual API response returned by biconnector.connector.get / biconnector.connector.list. + * + * @see https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-fields.html + * + * @property-read int $id + * @property-read string $title + * @property-read string $logo + * @property-read string|null $description + * @property-read int|null $sort + * @property-read string|null $urlCheck + * @property-read string|null $urlData + * @property-read string|null $urlTableList + * @property-read string|null $urlTableDescription + * @property-read array|null $settings + * @property-read bool|null $supportMapping + * @property-read CarbonImmutable $dateCreate + */ +class ConnectorItemResult extends AbstractItem +{ + #[\Override] + public function __get($offset): mixed + { + return match ($offset) { + 'id', 'sort' => isset($this->data[$offset]) ? (int)$this->data[$offset] : null, + 'supportMapping' => isset($this->data[$offset]) ? (bool)$this->data[$offset] : null, + 'dateCreate' => isset($this->data[$offset]) + ? CarbonImmutable::parse($this->data[$offset]) + : null, + default => $this->data[$offset] ?? null, + }; + } +} diff --git a/src/Services/Biconnector/Connector/Result/ConnectorResult.php b/src/Services/Biconnector/Connector/Result/ConnectorResult.php new file mode 100644 index 00000000..ae162bce --- /dev/null +++ b/src/Services/Biconnector/Connector/Result/ConnectorResult.php @@ -0,0 +1,39 @@ + + * + * 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\SDK\Services\Biconnector\Connector\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Class ConnectorResult + */ +class ConnectorResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function connector(): ConnectorItemResult + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + // biconnector.connector.get returns the item under the 'item' key + if (!empty($result['item']) && is_array($result['item'])) { + return new ConnectorItemResult($result['item']); + } + + // Fallback: flat object at result level {"id": ..., "title": ..., ...} + return new ConnectorItemResult($result); + } +} diff --git a/src/Services/Biconnector/Connector/Result/ConnectorsResult.php b/src/Services/Biconnector/Connector/Result/ConnectorsResult.php new file mode 100644 index 00000000..96530ccf --- /dev/null +++ b/src/Services/Biconnector/Connector/Result/ConnectorsResult.php @@ -0,0 +1,49 @@ + + * + * 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\SDK\Services\Biconnector\Connector\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Class ConnectorsResult + */ +class ConnectorsResult extends AbstractResult +{ + /** + * @return ConnectorItemResult[] + * @throws BaseException + */ + public function getConnectors(): array + { + $items = []; + $source = []; + + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + if (!empty($result['connectors']) && is_array($result['connectors'])) { + $source = $result['connectors']; + } elseif (!empty($result['items']) && is_array($result['items'])) { + $source = $result['items']; + } elseif (is_array($result) && array_is_list($result)) { + $source = $result; + } + + foreach ($source as $item) { + $items[] = new ConnectorItemResult($item); + } + + return $items; + } +} diff --git a/src/Services/Biconnector/Connector/Result/DeletedConnectorBatchResult.php b/src/Services/Biconnector/Connector/Result/DeletedConnectorBatchResult.php new file mode 100644 index 00000000..a3790231 --- /dev/null +++ b/src/Services/Biconnector/Connector/Result/DeletedConnectorBatchResult.php @@ -0,0 +1,28 @@ + + * + * 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\SDK\Services\Biconnector\Connector\Result; + +use Bitrix24\SDK\Core\Result\DeletedItemBatchResult; + +/** + * Class DeletedConnectorBatchResult + */ +class DeletedConnectorBatchResult extends DeletedItemBatchResult +{ + #[\Override] + public function isSuccess(): bool + { + return (bool)$this->getResponseData()->getResult(); + } +} diff --git a/src/Services/Biconnector/Connector/Result/DeletedConnectorResult.php b/src/Services/Biconnector/Connector/Result/DeletedConnectorResult.php new file mode 100644 index 00000000..b06bba3a --- /dev/null +++ b/src/Services/Biconnector/Connector/Result/DeletedConnectorResult.php @@ -0,0 +1,32 @@ + + * + * 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\SDK\Services\Biconnector\Connector\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\DeletedItemResult; + +/** + * Class DeletedConnectorResult + */ +class DeletedConnectorResult extends DeletedItemResult +{ + /** + * @throws BaseException + */ + #[\Override] + public function isSuccess(): bool + { + return (bool)$this->getCoreResponse()->getResponseData()->getResult(); + } +} diff --git a/src/Services/Biconnector/Connector/Result/UpdatedConnectorBatchResult.php b/src/Services/Biconnector/Connector/Result/UpdatedConnectorBatchResult.php new file mode 100644 index 00000000..b0cbe9c4 --- /dev/null +++ b/src/Services/Biconnector/Connector/Result/UpdatedConnectorBatchResult.php @@ -0,0 +1,28 @@ + + * + * 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\SDK\Services\Biconnector\Connector\Result; + +use Bitrix24\SDK\Core\Result\UpdatedItemBatchResult; + +/** + * Class UpdatedConnectorBatchResult + */ +class UpdatedConnectorBatchResult extends UpdatedItemBatchResult +{ + #[\Override] + public function isSuccess(): bool + { + return (bool)$this->getResponseData()->getResult(); + } +} diff --git a/src/Services/Biconnector/Connector/Result/UpdatedConnectorResult.php b/src/Services/Biconnector/Connector/Result/UpdatedConnectorResult.php new file mode 100644 index 00000000..5687dfc4 --- /dev/null +++ b/src/Services/Biconnector/Connector/Result/UpdatedConnectorResult.php @@ -0,0 +1,32 @@ + + * + * 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\SDK\Services\Biconnector\Connector\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; + +/** + * Class UpdatedConnectorResult + */ +class UpdatedConnectorResult extends UpdatedItemResult +{ + /** + * @throws BaseException + */ + #[\Override] + public function isSuccess(): bool + { + return (bool)$this->getCoreResponse()->getResponseData()->getResult(); + } +} diff --git a/src/Services/Biconnector/Connector/Service/Batch.php b/src/Services/Biconnector/Connector/Service/Batch.php new file mode 100644 index 00000000..bdfbcf25 --- /dev/null +++ b/src/Services/Biconnector/Connector/Service/Batch.php @@ -0,0 +1,169 @@ + + * + * 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\SDK\Services\Biconnector\Connector\Service; + +use Bitrix24\SDK\Attributes\ApiBatchMethodMetadata; +use Bitrix24\SDK\Attributes\ApiBatchServiceMetadata; +use Bitrix24\SDK\Core\Contracts\BatchOperationsInterface; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Services\Biconnector\Connector\Result\AddedConnectorBatchResult; +use Bitrix24\SDK\Services\Biconnector\Connector\Result\ConnectorItemResult; +use Bitrix24\SDK\Services\Biconnector\Connector\Result\DeletedConnectorBatchResult; +use Bitrix24\SDK\Services\Biconnector\Connector\Result\UpdatedConnectorBatchResult; +use Generator; +use Psr\Log\LoggerInterface; + +#[ApiBatchServiceMetadata(new Scope(['biconnector']))] +class Batch +{ + /** + * Batch constructor + */ + public function __construct(protected BatchOperationsInterface $batch, protected LoggerInterface $log) + { + } + + /** + * Batch list connectors + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-list.html + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'biconnector.connector.list', + 'https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-list.html', + 'Batch list connectors' + )] + public function list( + array $order = [], + array $filter = [], + array $select = [], + ?int $limit = null + ): Generator { + $this->log->debug( + 'batchList', + [ + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'limit' => $limit, + ] + ); + + $connectorListGenerator = $this->batch->getTraversableListWithCount( + 'biconnector.connector.list', + $order, + $filter, + $select, + $limit + ); + foreach ($connectorListGenerator as $key => $value) { + yield $key => new ConnectorItemResult($value); + } + } + + /** + * Batch add connectors + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-add.html + * + * @param array $connectors + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'biconnector.connector.add', + 'https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-add.html', + 'Batch add connectors' + )] + public function add(array $connectors): Generator + { + $items = []; + foreach ($connectors as $item) { + $items[] = [ + 'fields' => $item, + ]; + } + + foreach ($this->batch->addEntityItems('biconnector.connector.add', $items) as $key => $item) { + yield $key => new AddedConnectorBatchResult($item); + } + } + + /** + * Batch update connectors + * + * Update elements in array with structure: + * id => [ // Connector id + * 'fields' => [] // Connector fields to update + * ] + * + * @param array $entityItems + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'biconnector.connector.update', + 'https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-update.html', + 'Batch update connectors' + )] + public function update(array $entityItems): Generator + { + foreach ( + $this->batch->updateEntityItems( + 'biconnector.connector.update', + $entityItems + ) as $key => $item + ) { + yield $key => new UpdatedConnectorBatchResult($item); + } + } + + /** + * Batch delete connectors + * + * @param int[] $connectorIds + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'biconnector.connector.delete', + 'https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-delete.html', + 'Batch delete connectors' + )] + public function delete(array $connectorIds): Generator + { + foreach ( + $this->batch->deleteEntityItems( + 'biconnector.connector.delete', + $connectorIds + ) as $key => $item + ) { + yield $key => new DeletedConnectorBatchResult($item); + } + } +} diff --git a/src/Services/Biconnector/Connector/Service/Connector.php b/src/Services/Biconnector/Connector/Service/Connector.php new file mode 100644 index 00000000..1ef76aeb --- /dev/null +++ b/src/Services/Biconnector/Connector/Service/Connector.php @@ -0,0 +1,237 @@ + + * + * 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\SDK\Services\Biconnector\Connector\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Contracts\CoreInterface; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\FieldsResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\Biconnector\Connector\Result\AddedConnectorResult; +use Bitrix24\SDK\Services\Biconnector\Connector\Result\ConnectorResult; +use Bitrix24\SDK\Services\Biconnector\Connector\Result\ConnectorsResult; +use Bitrix24\SDK\Services\Biconnector\Connector\Result\DeletedConnectorResult; +use Bitrix24\SDK\Services\Biconnector\Connector\Result\UpdatedConnectorResult; +use Psr\Log\LoggerInterface; + +#[ApiServiceMetadata(new Scope(['biconnector']))] +class Connector extends AbstractService +{ + /** + * Connector constructor + */ + public function __construct(public Batch $batch, CoreInterface $core, LoggerInterface $logger) + { + parent::__construct($core, $logger); + } + + /** + * Add a new connector + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-add.html + * + * @param array{ + * title: string, + * logo: string, + * urlCheck: string, + * urlData: string, + * urlTableList: string, + * urlTableDescription: string, + * settings: array, + * description?: string, + * sort?: int, + * } $fields + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.connector.add', + 'https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-add.html', + 'Add a new connector' + )] + public function add(array $fields): AddedConnectorResult + { + return new AddedConnectorResult( + $this->core->call( + 'biconnector.connector.add', + [ + 'fields' => $fields, + ] + ) + ); + } + + /** + * Update an existing connector + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-update.html + * + * @param array{ + * title?: string, + * logo?: string, + * description?: string, + * sort?: int, + * urlCheck?: string, + * urlData?: string, + * urlTableList?: string, + * urlTableDescription?: string, + * settings?: array, + * supportMapping?: bool, + * } $fields + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.connector.update', + 'https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-update.html', + 'Update an existing connector' + )] + public function update(int $id, array $fields): UpdatedConnectorResult + { + return new UpdatedConnectorResult( + $this->core->call( + 'biconnector.connector.update', + [ + 'id' => $id, + 'fields' => $fields, + ] + ) + ); + } + + /** + * Get a connector by its ID + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-get.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.connector.get', + 'https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-get.html', + 'Get a connector by its ID' + )] + public function get(int $id): ConnectorResult + { + return new ConnectorResult( + $this->core->call( + 'biconnector.connector.get', + [ + 'id' => $id, + ] + ) + ); + } + + /** + * Get a list of connectors + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-list.html + * + * @param array $order - sort fields, e.g. ['id' => 'ASC'] + * @param array $filter - filter fields + * @param array $select - fields to include in the result + * @param int $page - page number for pagination (page size is 50 records per page) + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.connector.list', + 'https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-list.html', + 'Get a list of connectors' + )] + public function list(array $order = [], array $filter = [], array $select = [], int $page = 1): ConnectorsResult + { + return new ConnectorsResult( + $this->core->call( + 'biconnector.connector.list', + [ + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'page' => $page, + ] + ) + ); + } + + /** + * Delete a connector by its ID + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-delete.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.connector.delete', + 'https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-delete.html', + 'Delete a connector by its ID' + )] + public function delete(int $id): DeletedConnectorResult + { + return new DeletedConnectorResult( + $this->core->call( + 'biconnector.connector.delete', + [ + 'id' => $id, + ] + ) + ); + } + + /** + * Get the fields description for connectors + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-fields.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.connector.fields', + 'https://apidocs.bitrix24.com/api-reference/biconnector/connector/biconnector-connector-fields.html', + 'Get the fields description for connectors' + )] + public function fields(): FieldsResult + { + return new FieldsResult($this->core->call('biconnector.connector.fields')); + } + + /** + * Count connectors + * + * Note: biconnector.connector.list does not return a total count in pagination, + * so we iterate all available items via batch to count them. + * + * @throws BaseException + * @throws TransportException + */ + public function count(): int + { + $count = 0; + foreach ($this->batch->list() as $item) { + $count++; + } + + return $count; + } +} diff --git a/src/Services/Biconnector/Dataset/Batch.php b/src/Services/Biconnector/Dataset/Batch.php new file mode 100644 index 00000000..fea884f6 --- /dev/null +++ b/src/Services/Biconnector/Dataset/Batch.php @@ -0,0 +1,266 @@ + + * + * 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\SDK\Services\Biconnector\Dataset; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Response\DTO\ResponseData; +use Generator; + +/** + * Class Batch + * + * Overrides base Batch to handle parameter naming differences in biconnector.dataset.* REST methods: + * - list uses 'page' (page number, 50 records per page) instead of 'start' (offset) for pagination + * - delete uses lowercase 'id' instead of 'ID' + * + * @see https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-list.html + * @see https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-delete.html + */ +class Batch extends \Bitrix24\SDK\Core\Batch +{ + /** + * Determines the ID key — lowercase 'id' for biconnector dataset + */ + #[\Override] + protected function determineKeyId(string $apiMethod, ?array $additionalParameters): string + { + return 'id'; + } + + /** + * Delete entity items with batch call using lowercase 'id' parameter + * + * @param int[] $entityItemId + * @param array|null $additionalParameters + * + * @return Generator|ResponseData[] + * @throws BaseException + */ + #[\Override] + public function deleteEntityItems( + string $apiMethod, + array $entityItemId, + ?array $additionalParameters = null + ): Generator { + $this->logger->debug( + 'deleteEntityItems.start', + [ + 'apiMethod' => $apiMethod, + 'entityItems' => $entityItemId, + 'additionalParameters' => $additionalParameters, + ] + ); + + try { + $this->clearCommands(); + foreach ($entityItemId as $cnt => $itemId) { + if (!is_int($itemId)) { + throw new InvalidArgumentException( + sprintf( + 'invalid type «%s» of dataset id «%s» at position %s, dataset id must be integer type', + gettype($itemId), + $itemId, + $cnt + ) + ); + } + + $this->registerCommand($apiMethod, ['id' => $itemId]); + } + + foreach ($this->getTraversable(true) as $cnt => $deletedItemResult) { + yield $cnt => $deletedItemResult; + } + } catch (InvalidArgumentException $exception) { + $errorMessage = sprintf('batch delete dataset items: %s', $exception->getMessage()); + $this->logger->error( + $errorMessage, + [ + 'trace' => $exception->getTrace(), + ] + ); + throw $exception; + } catch (\Throwable $exception) { + $errorMessage = sprintf('batch delete dataset items: %s', $exception->getMessage()); + $this->logger->error( + $errorMessage, + [ + 'trace' => $exception->getTrace(), + ] + ); + + throw new BaseException($errorMessage, $exception->getCode(), $exception); + } + + $this->logger->debug('deleteEntityItems.finish'); + } + + /** + * Get traversable list using page-based pagination. + * + * The biconnector.dataset.list method uses 'page' parameter (page number, 50 records per page) + * instead of the standard 'start' (offset) parameter used by most other REST methods. + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-list.html + * + * @param array $order + * @param array $filter + * @param array $select + * + * @return Generator + * @throws BaseException + * @throws TransportException + */ + #[\Override] + public function getTraversableList( + string $apiMethod, + ?array $order = [], + ?array $filter = [], + ?array $select = [], + ?int $limit = null, + ?array $additionalParameters = null + ): Generator { + yield from $this->getTraversableListWithCount( + $apiMethod, + $order ?? [], + $filter ?? [], + $select ?? [], + $limit, + $additionalParameters + ); + } + + /** + * Get traversable list using page-based pagination (page number, 50 records per page). + * + * The biconnector.dataset.list method accepts 'page' parameter instead of 'start'. + * Page 1 returns items 1–50, page 2 returns items 51–100, etc. + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-list.html + * + * @param array $order + * @param array $filter + * @param array $select + * + * @return Generator + * @throws BaseException + * @throws TransportException + */ + #[\Override] + public function getTraversableListWithCount( + string $apiMethod, + array $order, + array $filter, + array $select, + ?int $limit = null, + ?array $additionalParameters = null + ): Generator { + $this->logger->debug( + 'getTraversableListWithCount.start', + [ + 'apiMethod' => $apiMethod, + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'limit' => $limit, + 'additionalParameters' => $additionalParameters, + ] + ); + + $this->clearCommands(); + + // Fetch first page to determine total count + $params = [ + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'page' => 1, + ]; + + if ($additionalParameters !== null) { + $params = array_merge($params, $additionalParameters); + } + + $response = $this->core->call($apiMethod, $params); + $total = $response->getResponseData()->getPagination()->getTotal(); + + $this->logger->debug( + 'getTraversableListWithCount.totalElementsCount', + [ + 'totalElementsCount' => $total, + ] + ); + + if ($total <= self::MAX_ELEMENTS_IN_PAGE) { + $elementsCounter = 0; + foreach ($response->getResponseData()->getResult() as $item) { + $elementsCounter++; + if ($limit !== null && $elementsCounter > $limit) { + return; + } + + yield $item; + } + + return; + } + + // Register batch commands for all pages + $totalPages = (int)ceil($total / self::MAX_ELEMENTS_IN_PAGE); + for ($page = 1; $page <= $totalPages; $page++) { + $pageParams = [ + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'page' => $page, + ]; + + if ($additionalParameters !== null) { + $pageParams = array_merge($pageParams, $additionalParameters); + } + + $this->registerCommand($apiMethod, $pageParams); + + if ($limit !== null && $limit < $page * self::MAX_ELEMENTS_IN_PAGE) { + break; + } + } + + $this->logger->debug( + 'getTraversableListWithCount.commandsRegistered', + [ + 'commandsCount' => $this->commands->count(), + 'totalItemsToSelect' => $total, + ] + ); + + $elementsCounter = 0; + foreach ($this->getTraversable(true) as $queryResultData) { + $resultElements = $this->extractElementsFromBatchResult($queryResultData, false); + foreach ($resultElements as $resultElement) { + ++$elementsCounter; + if ($limit !== null && $elementsCounter > $limit) { + return; + } + + yield $resultElement; + } + } + + $this->logger->debug('getTraversableListWithCount.finish'); + } +} + diff --git a/src/Services/Biconnector/Dataset/Result/AddedDatasetBatchResult.php b/src/Services/Biconnector/Dataset/Result/AddedDatasetBatchResult.php new file mode 100644 index 00000000..0d87d7f0 --- /dev/null +++ b/src/Services/Biconnector/Dataset/Result/AddedDatasetBatchResult.php @@ -0,0 +1,35 @@ + + * + * 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\SDK\Services\Biconnector\Dataset\Result; + +use Bitrix24\SDK\Core\Result\AddedItemBatchResult; + +/** + * Class AddedDatasetBatchResult + */ +class AddedDatasetBatchResult extends AddedItemBatchResult +{ + #[\Override] + public function getId(): int + { + $result = $this->getResponseData()->getResult(); + + if (!empty($result['id'])) { + return (int)$result['id']; + } + + return (int)$result; + } +} + diff --git a/src/Services/Biconnector/Dataset/Result/AddedDatasetResult.php b/src/Services/Biconnector/Dataset/Result/AddedDatasetResult.php new file mode 100644 index 00000000..1ae6707e --- /dev/null +++ b/src/Services/Biconnector/Dataset/Result/AddedDatasetResult.php @@ -0,0 +1,42 @@ + + * + * 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\SDK\Services\Biconnector\Dataset\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AddedItemResult; + +/** + * Class AddedDatasetResult + * + * Wraps the response from biconnector.dataset.add. + * The API returns: result.id (integer) + */ +class AddedDatasetResult extends AddedItemResult +{ + /** + * @throws BaseException + */ + #[\Override] + public function getId(): int + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + if (!empty($result['id'])) { + return (int)$result['id']; + } + + return (int)$result; + } +} + diff --git a/src/Services/Biconnector/Dataset/Result/DatasetItemResult.php b/src/Services/Biconnector/Dataset/Result/DatasetItemResult.php new file mode 100644 index 00000000..c807c4ba --- /dev/null +++ b/src/Services/Biconnector/Dataset/Result/DatasetItemResult.php @@ -0,0 +1,55 @@ + + * + * 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\SDK\Services\Biconnector\Dataset\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; +use Carbon\CarbonImmutable; + +/** + * Class DatasetItemResult + * + * Field names correspond to the actual API response returned by biconnector.dataset.get / biconnector.dataset.list. + * + * @see https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-fields.html + * + * @property-read int $id + * @property-read int|null $sourceId + * @property-read string|null $name + * @property-read string|null $type + * @property-read string|null $description + * @property-read string|null $externalName + * @property-read string|null $externalCode + * @property-read int|null $externalId + * @property-read CarbonImmutable $dateCreate + * @property-read CarbonImmutable $dateUpdate + * @property-read int $createdById + * @property-read int $updatedById + * @property-read array|null $fields + */ +class DatasetItemResult extends AbstractItem +{ + #[\Override] + public function __get($offset): mixed + { + return match ($offset) { + 'id', 'createdById', 'updatedById' => isset($this->data[$offset]) ? (int)$this->data[$offset] : null, + 'sourceId', 'externalId' => isset($this->data[$offset]) ? (int)$this->data[$offset] : null, + 'dateCreate', 'dateUpdate' => isset($this->data[$offset]) + ? CarbonImmutable::parse($this->data[$offset]) + : null, + default => $this->data[$offset] ?? null, + }; + } +} + diff --git a/src/Services/Biconnector/Dataset/Result/DatasetResult.php b/src/Services/Biconnector/Dataset/Result/DatasetResult.php new file mode 100644 index 00000000..09352c67 --- /dev/null +++ b/src/Services/Biconnector/Dataset/Result/DatasetResult.php @@ -0,0 +1,41 @@ + + * + * 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\SDK\Services\Biconnector\Dataset\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Class DatasetResult + * + * Wraps the response from biconnector.dataset.get. + * The API returns: result.item (object) + */ +class DatasetResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function dataset(): DatasetItemResult + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + if (!empty($result['item']) && is_array($result['item'])) { + return new DatasetItemResult($result['item']); + } + + return new DatasetItemResult($result); + } +} + diff --git a/src/Services/Biconnector/Dataset/Result/DatasetsResult.php b/src/Services/Biconnector/Dataset/Result/DatasetsResult.php new file mode 100644 index 00000000..bf17fcf1 --- /dev/null +++ b/src/Services/Biconnector/Dataset/Result/DatasetsResult.php @@ -0,0 +1,45 @@ + + * + * 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\SDK\Services\Biconnector\Dataset\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Class DatasetsResult + * + * Wraps the response from biconnector.dataset.list. + * The API returns a flat array of dataset items. + */ +class DatasetsResult extends AbstractResult +{ + /** + * @return DatasetItemResult[] + * @throws BaseException + */ + public function getDatasets(): array + { + $items = []; + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + if (array_is_list($result)) { + foreach ($result as $item) { + $items[] = new DatasetItemResult($item); + } + } + + return $items; + } +} + diff --git a/src/Services/Biconnector/Dataset/Result/DeletedDatasetBatchResult.php b/src/Services/Biconnector/Dataset/Result/DeletedDatasetBatchResult.php new file mode 100644 index 00000000..74d79e70 --- /dev/null +++ b/src/Services/Biconnector/Dataset/Result/DeletedDatasetBatchResult.php @@ -0,0 +1,29 @@ + + * + * 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\SDK\Services\Biconnector\Dataset\Result; + +use Bitrix24\SDK\Core\Result\DeletedItemBatchResult; + +/** + * Class DeletedDatasetBatchResult + */ +class DeletedDatasetBatchResult extends DeletedItemBatchResult +{ + #[\Override] + public function isSuccess(): bool + { + return (bool)$this->getResponseData()->getResult(); + } +} + diff --git a/src/Services/Biconnector/Dataset/Result/DeletedDatasetResult.php b/src/Services/Biconnector/Dataset/Result/DeletedDatasetResult.php new file mode 100644 index 00000000..08afd5f7 --- /dev/null +++ b/src/Services/Biconnector/Dataset/Result/DeletedDatasetResult.php @@ -0,0 +1,33 @@ + + * + * 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\SDK\Services\Biconnector\Dataset\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\DeletedItemResult; + +/** + * Class DeletedDatasetResult + */ +class DeletedDatasetResult extends DeletedItemResult +{ + /** + * @throws BaseException + */ + #[\Override] + public function isSuccess(): bool + { + return (bool)$this->getCoreResponse()->getResponseData()->getResult(); + } +} + diff --git a/src/Services/Biconnector/Dataset/Result/UpdatedDatasetBatchResult.php b/src/Services/Biconnector/Dataset/Result/UpdatedDatasetBatchResult.php new file mode 100644 index 00000000..f9818607 --- /dev/null +++ b/src/Services/Biconnector/Dataset/Result/UpdatedDatasetBatchResult.php @@ -0,0 +1,29 @@ + + * + * 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\SDK\Services\Biconnector\Dataset\Result; + +use Bitrix24\SDK\Core\Result\UpdatedItemBatchResult; + +/** + * Class UpdatedDatasetBatchResult + */ +class UpdatedDatasetBatchResult extends UpdatedItemBatchResult +{ + #[\Override] + public function isSuccess(): bool + { + return (bool)$this->getResponseData()->getResult(); + } +} + diff --git a/src/Services/Biconnector/Dataset/Result/UpdatedDatasetResult.php b/src/Services/Biconnector/Dataset/Result/UpdatedDatasetResult.php new file mode 100644 index 00000000..9a209f8c --- /dev/null +++ b/src/Services/Biconnector/Dataset/Result/UpdatedDatasetResult.php @@ -0,0 +1,33 @@ + + * + * 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\SDK\Services\Biconnector\Dataset\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; + +/** + * Class UpdatedDatasetResult + */ +class UpdatedDatasetResult extends UpdatedItemResult +{ + /** + * @throws BaseException + */ + #[\Override] + public function isSuccess(): bool + { + return (bool)$this->getCoreResponse()->getResponseData()->getResult(); + } +} + diff --git a/src/Services/Biconnector/Dataset/Service/Batch.php b/src/Services/Biconnector/Dataset/Service/Batch.php new file mode 100644 index 00000000..ad51a3ec --- /dev/null +++ b/src/Services/Biconnector/Dataset/Service/Batch.php @@ -0,0 +1,171 @@ + + * + * 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\SDK\Services\Biconnector\Dataset\Service; + +use Bitrix24\SDK\Attributes\ApiBatchMethodMetadata; +use Bitrix24\SDK\Attributes\ApiBatchServiceMetadata; +use Bitrix24\SDK\Core\Contracts\BatchOperationsInterface; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Services\Biconnector\Dataset\Result\AddedDatasetBatchResult; +use Bitrix24\SDK\Services\Biconnector\Dataset\Result\DatasetItemResult; +use Bitrix24\SDK\Services\Biconnector\Dataset\Result\DeletedDatasetBatchResult; +use Bitrix24\SDK\Services\Biconnector\Dataset\Result\UpdatedDatasetBatchResult; +use Generator; +use Psr\Log\LoggerInterface; + +#[ApiBatchServiceMetadata(new Scope(['biconnector']))] +class Batch +{ + /** + * Batch constructor + */ + public function __construct(protected BatchOperationsInterface $batch, protected LoggerInterface $log) + { + } + + /** + * Batch list datasets + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-list.html + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'biconnector.dataset.list', + 'https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-list.html', + 'Batch list datasets' + )] + public function list( + array $order = [], + array $filter = [], + array $select = [], + ?int $limit = null + ): Generator { + $this->log->debug( + 'batchList', + [ + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'limit' => $limit, + ] + ); + + foreach ( + $this->batch->getTraversableList( + 'biconnector.dataset.list', + $order, + $filter, + $select, + $limit + ) as $key => $value + ) { + yield $key => new DatasetItemResult($value); + } + } + + /** + * Batch add datasets + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-add.html + * + * @param array $datasets + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'biconnector.dataset.add', + 'https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-add.html', + 'Batch add datasets' + )] + public function add(array $datasets): Generator + { + $items = []; + foreach ($datasets as $item) { + $items[] = [ + 'fields' => $item, + ]; + } + + foreach ($this->batch->addEntityItems('biconnector.dataset.add', $items) as $key => $item) { + yield $key => new AddedDatasetBatchResult($item); + } + } + + /** + * Batch update datasets + * + * Update elements in array with structure: + * id => [ // Dataset id + * 'fields' => [] // Dataset fields to update + * ] + * + * @param array $entityItems + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'biconnector.dataset.update', + 'https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-update.html', + 'Batch update datasets' + )] + public function update(array $entityItems): Generator + { + foreach ( + $this->batch->updateEntityItems( + 'biconnector.dataset.update', + $entityItems + ) as $key => $item + ) { + yield $key => new UpdatedDatasetBatchResult($item); + } + } + + /** + * Batch delete datasets + * + * @param int[] $datasetIds + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'biconnector.dataset.delete', + 'https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-delete.html', + 'Batch delete datasets' + )] + public function delete(array $datasetIds): Generator + { + foreach ( + $this->batch->deleteEntityItems( + 'biconnector.dataset.delete', + $datasetIds + ) as $key => $item + ) { + yield $key => new DeletedDatasetBatchResult($item); + } + } +} + diff --git a/src/Services/Biconnector/Dataset/Service/Dataset.php b/src/Services/Biconnector/Dataset/Service/Dataset.php new file mode 100644 index 00000000..3767c851 --- /dev/null +++ b/src/Services/Biconnector/Dataset/Service/Dataset.php @@ -0,0 +1,261 @@ + + * + * 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\SDK\Services\Biconnector\Dataset\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Contracts\CoreInterface; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\FieldsResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\Biconnector\Dataset\Result\AddedDatasetResult; +use Bitrix24\SDK\Services\Biconnector\Dataset\Result\DatasetResult; +use Bitrix24\SDK\Services\Biconnector\Dataset\Result\DatasetsResult; +use Bitrix24\SDK\Services\Biconnector\Dataset\Result\DeletedDatasetResult; +use Bitrix24\SDK\Services\Biconnector\Dataset\Result\UpdatedDatasetResult; +use Psr\Log\LoggerInterface; + +#[ApiServiceMetadata(new Scope(['biconnector']))] +class Dataset extends AbstractService +{ + /** + * Dataset constructor + */ + public function __construct(public Batch $batch, CoreInterface $core, LoggerInterface $logger) + { + parent::__construct($core, $logger); + } + + /** + * Add a new dataset + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-add.html + * + * @param array{ + * name: string, + * externalName: string, + * externalCode: string, + * sourceId: int, + * description?: string, + * fields?: array, + * } $fields + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.dataset.add', + 'https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-add.html', + 'Add a new dataset' + )] + public function add(array $fields): AddedDatasetResult + { + return new AddedDatasetResult( + $this->core->call( + 'biconnector.dataset.add', + [ + 'fields' => $fields, + ] + ) + ); + } + + /** + * Update an existing dataset + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-update.html + * + * @param array{ + * description?: string, + * } $fields + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.dataset.update', + 'https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-update.html', + 'Update an existing dataset' + )] + public function update(int $id, array $fields): UpdatedDatasetResult + { + return new UpdatedDatasetResult( + $this->core->call( + 'biconnector.dataset.update', + [ + 'id' => $id, + 'fields' => $fields, + ] + ) + ); + } + + /** + * Get a dataset by its ID + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-get.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.dataset.get', + 'https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-get.html', + 'Get a dataset by its ID' + )] + public function get(int $id): DatasetResult + { + return new DatasetResult( + $this->core->call( + 'biconnector.dataset.get', + [ + 'id' => $id, + ] + ) + ); + } + + /** + * Get a list of datasets + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-list.html + * + * @param array $order - sort fields, e.g. ['dateCreate' => 'DESC'] + * @param array $filter - filter fields + * @param array $select - fields to include in the result + * @param int $page - page number for pagination (page size is 50 records per page) + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.dataset.list', + 'https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-list.html', + 'Get a list of datasets' + )] + public function list(array $order = [], array $filter = [], array $select = [], int $page = 1): DatasetsResult + { + return new DatasetsResult( + $this->core->call( + 'biconnector.dataset.list', + [ + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'page' => $page, + ] + ) + ); + } + + /** + * Delete a dataset by its ID + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-delete.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.dataset.delete', + 'https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-delete.html', + 'Delete a dataset by its ID' + )] + public function delete(int $id): DeletedDatasetResult + { + return new DeletedDatasetResult( + $this->core->call( + 'biconnector.dataset.delete', + [ + 'id' => $id, + ] + ) + ); + } + + /** + * Get the fields description for datasets + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-fields.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.dataset.fields', + 'https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-fields.html', + 'Get the fields description for datasets' + )] + public function fields(): FieldsResult + { + return new FieldsResult($this->core->call('biconnector.dataset.fields')); + } + + /** + * Update fields of an existing dataset (add, update visibility, or delete dataset columns) + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-fields-update.html + * + * @param array $add - fields to add + * @param array $update - fields to update (visibility) + * @param int[] $delete - field IDs to delete + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.dataset.fields.update', + 'https://apidocs.bitrix24.com/api-reference/biconnector/dataset/biconnector-dataset-fields-update.html', + 'Update fields of an existing dataset' + )] + public function updateFields(int $id, array $add = [], array $update = [], array $delete = []): UpdatedDatasetResult + { + $params = ['id' => $id]; + + if ($add !== []) { + $params['add'] = $add; + } + + if ($update !== []) { + $params['update'] = $update; + } + + if ($delete !== []) { + $params['delete'] = $delete; + } + + return new UpdatedDatasetResult( + $this->core->call('biconnector.dataset.fields.update', $params) + ); + } + + /** + * Count datasets + * + * @throws BaseException + * @throws TransportException + */ + public function count(): int + { + $count = 0; + foreach ($this->batch->list() as $item) { + $count++; + } + + return $count; + } +} + diff --git a/src/Services/Biconnector/Source/Batch.php b/src/Services/Biconnector/Source/Batch.php new file mode 100644 index 00000000..9cddffd0 --- /dev/null +++ b/src/Services/Biconnector/Source/Batch.php @@ -0,0 +1,265 @@ + + * + * 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\SDK\Services\Biconnector\Source; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Response\DTO\ResponseData; +use Generator; + +/** + * Class Batch + * + * Overrides base Batch to handle parameter naming differences in biconnector.source.* REST methods: + * - list uses 'page' (page number, 50 records per page) instead of 'start' (offset) for pagination + * - delete uses lowercase 'id' instead of 'ID' + * + * @see https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-list.html + * @see https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-delete.html + */ +class Batch extends \Bitrix24\SDK\Core\Batch +{ + /** + * Determines the ID key — lowercase 'id' for biconnector source + */ + #[\Override] + protected function determineKeyId(string $apiMethod, ?array $additionalParameters): string + { + return 'id'; + } + + /** + * Delete entity items with batch call using lowercase 'id' parameter + * + * @param int[] $entityItemId + * @param array|null $additionalParameters + * + * @return Generator|ResponseData[] + * @throws BaseException + */ + #[\Override] + public function deleteEntityItems( + string $apiMethod, + array $entityItemId, + ?array $additionalParameters = null + ): Generator { + $this->logger->debug( + 'deleteEntityItems.start', + [ + 'apiMethod' => $apiMethod, + 'entityItems' => $entityItemId, + 'additionalParameters' => $additionalParameters, + ] + ); + + try { + $this->clearCommands(); + foreach ($entityItemId as $cnt => $itemId) { + if (!is_int($itemId)) { + throw new InvalidArgumentException( + sprintf( + 'invalid type «%s» of source id «%s» at position %s, source id must be integer type', + gettype($itemId), + $itemId, + $cnt + ) + ); + } + + $this->registerCommand($apiMethod, ['id' => $itemId]); + } + + foreach ($this->getTraversable(true) as $cnt => $deletedItemResult) { + yield $cnt => $deletedItemResult; + } + } catch (InvalidArgumentException $exception) { + $errorMessage = sprintf('batch delete source items: %s', $exception->getMessage()); + $this->logger->error( + $errorMessage, + [ + 'trace' => $exception->getTrace(), + ] + ); + throw $exception; + } catch (\Throwable $exception) { + $errorMessage = sprintf('batch delete source items: %s', $exception->getMessage()); + $this->logger->error( + $errorMessage, + [ + 'trace' => $exception->getTrace(), + ] + ); + + throw new BaseException($errorMessage, $exception->getCode(), $exception); + } + + $this->logger->debug('deleteEntityItems.finish'); + } + + /** + * Get traversable list using page-based pagination. + * + * The biconnector.source.list method uses 'page' parameter (page number, 50 records per page) + * instead of the standard 'start' (offset) parameter used by most other REST methods. + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-list.html + * + * @param array $order + * @param array $filter + * @param array $select + * + * @return Generator + * @throws BaseException + * @throws TransportException + */ + #[\Override] + public function getTraversableList( + string $apiMethod, + ?array $order = [], + ?array $filter = [], + ?array $select = [], + ?int $limit = null, + ?array $additionalParameters = null + ): Generator { + yield from $this->getTraversableListWithCount( + $apiMethod, + $order ?? [], + $filter ?? [], + $select ?? [], + $limit, + $additionalParameters + ); + } + + /** + * Get traversable list using page-based pagination (page number, 50 records per page). + * + * The biconnector.source.list method accepts 'page' parameter instead of 'start'. + * Page 1 returns items 1–50, page 2 returns items 51–100, etc. + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-list.html + * + * @param array $order + * @param array $filter + * @param array $select + * + * @return Generator + * @throws BaseException + * @throws TransportException + */ + #[\Override] + public function getTraversableListWithCount( + string $apiMethod, + array $order, + array $filter, + array $select, + ?int $limit = null, + ?array $additionalParameters = null + ): Generator { + $this->logger->debug( + 'getTraversableListWithCount.start', + [ + 'apiMethod' => $apiMethod, + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'limit' => $limit, + 'additionalParameters' => $additionalParameters, + ] + ); + + $this->clearCommands(); + + // Fetch first page to determine total count + $params = [ + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'page' => 1, + ]; + + if ($additionalParameters !== null) { + $params = array_merge($params, $additionalParameters); + } + + $response = $this->core->call($apiMethod, $params); + $total = $response->getResponseData()->getPagination()->getTotal(); + + $this->logger->debug( + 'getTraversableListWithCount.totalElementsCount', + [ + 'totalElementsCount' => $total, + ] + ); + + if ($total <= self::MAX_ELEMENTS_IN_PAGE) { + $elementsCounter = 0; + foreach ($response->getResponseData()->getResult() as $item) { + $elementsCounter++; + if ($limit !== null && $elementsCounter > $limit) { + return; + } + + yield $item; + } + + return; + } + + // Register batch commands for all pages + $totalPages = (int)ceil($total / self::MAX_ELEMENTS_IN_PAGE); + for ($page = 1; $page <= $totalPages; $page++) { + $pageParams = [ + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'page' => $page, + ]; + + if ($additionalParameters !== null) { + $pageParams = array_merge($pageParams, $additionalParameters); + } + + $this->registerCommand($apiMethod, $pageParams); + + if ($limit !== null && $limit < $page * self::MAX_ELEMENTS_IN_PAGE) { + break; + } + } + + $this->logger->debug( + 'getTraversableListWithCount.commandsRegistered', + [ + 'commandsCount' => $this->commands->count(), + 'totalItemsToSelect' => $total, + ] + ); + + $elementsCounter = 0; + foreach ($this->getTraversable(true) as $queryResultData) { + $resultElements = $this->extractElementsFromBatchResult($queryResultData, false); + foreach ($resultElements as $resultElement) { + ++$elementsCounter; + if ($limit !== null && $elementsCounter > $limit) { + return; + } + + yield $resultElement; + } + } + + $this->logger->debug('getTraversableListWithCount.finish'); + } +} diff --git a/src/Services/Biconnector/Source/Result/AddedSourceBatchResult.php b/src/Services/Biconnector/Source/Result/AddedSourceBatchResult.php new file mode 100644 index 00000000..f9107407 --- /dev/null +++ b/src/Services/Biconnector/Source/Result/AddedSourceBatchResult.php @@ -0,0 +1,34 @@ + + * + * 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\SDK\Services\Biconnector\Source\Result; + +use Bitrix24\SDK\Core\Result\AddedItemBatchResult; + +/** + * Class AddedSourceBatchResult + */ +class AddedSourceBatchResult extends AddedItemBatchResult +{ + #[\Override] + public function getId(): int + { + $result = $this->getResponseData()->getResult(); + + if (!empty($result['id'])) { + return (int)$result['id']; + } + + return (int)$result; + } +} diff --git a/src/Services/Biconnector/Source/Result/AddedSourceResult.php b/src/Services/Biconnector/Source/Result/AddedSourceResult.php new file mode 100644 index 00000000..a4c73f89 --- /dev/null +++ b/src/Services/Biconnector/Source/Result/AddedSourceResult.php @@ -0,0 +1,41 @@ + + * + * 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\SDK\Services\Biconnector\Source\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AddedItemResult; + +/** + * Class AddedSourceResult + * + * Wraps the response from biconnector.source.add. + * The API returns: result.id (integer) + */ +class AddedSourceResult extends AddedItemResult +{ + /** + * @throws BaseException + */ + #[\Override] + public function getId(): int + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + if (!empty($result['id'])) { + return (int)$result['id']; + } + + return (int)$result; + } +} diff --git a/src/Services/Biconnector/Source/Result/DeletedSourceBatchResult.php b/src/Services/Biconnector/Source/Result/DeletedSourceBatchResult.php new file mode 100644 index 00000000..f3e927f9 --- /dev/null +++ b/src/Services/Biconnector/Source/Result/DeletedSourceBatchResult.php @@ -0,0 +1,28 @@ + + * + * 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\SDK\Services\Biconnector\Source\Result; + +use Bitrix24\SDK\Core\Result\DeletedItemBatchResult; + +/** + * Class DeletedSourceBatchResult + */ +class DeletedSourceBatchResult extends DeletedItemBatchResult +{ + #[\Override] + public function isSuccess(): bool + { + return (bool)$this->getResponseData()->getResult(); + } +} diff --git a/src/Services/Biconnector/Source/Result/DeletedSourceResult.php b/src/Services/Biconnector/Source/Result/DeletedSourceResult.php new file mode 100644 index 00000000..3808c934 --- /dev/null +++ b/src/Services/Biconnector/Source/Result/DeletedSourceResult.php @@ -0,0 +1,32 @@ + + * + * 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\SDK\Services\Biconnector\Source\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\DeletedItemResult; + +/** + * Class DeletedSourceResult + */ +class DeletedSourceResult extends DeletedItemResult +{ + /** + * @throws BaseException + */ + #[\Override] + public function isSuccess(): bool + { + return (bool)$this->getCoreResponse()->getResponseData()->getResult(); + } +} diff --git a/src/Services/Biconnector/Source/Result/SourceItemResult.php b/src/Services/Biconnector/Source/Result/SourceItemResult.php new file mode 100644 index 00000000..2cde005d --- /dev/null +++ b/src/Services/Biconnector/Source/Result/SourceItemResult.php @@ -0,0 +1,53 @@ + + * + * 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\SDK\Services\Biconnector\Source\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; +use Carbon\CarbonImmutable; + +/** + * Class SourceItemResult + * + * Field names correspond to the actual API response returned by biconnector.source.get / biconnector.source.list. + * + * @see https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-fields.html + * + * @property-read int $id + * @property-read string $title + * @property-read string|null $type + * @property-read string|null $code + * @property-read string|null $description + * @property-read bool|null $active + * @property-read CarbonImmutable $dateCreate + * @property-read CarbonImmutable $dateUpdate + * @property-read int $createdById + * @property-read int $updatedById + * @property-read int $connectorId + * @property-read array|null $settings + */ +class SourceItemResult extends AbstractItem +{ + #[\Override] + public function __get($offset): mixed + { + return match ($offset) { + 'id', 'createdById', 'updatedById', 'connectorId' => isset($this->data[$offset]) ? (int)$this->data[$offset] : null, + 'active' => isset($this->data[$offset]) ? (bool)$this->data[$offset] : null, + 'dateCreate', 'dateUpdate' => isset($this->data[$offset]) + ? CarbonImmutable::parse($this->data[$offset]) + : null, + default => $this->data[$offset] ?? null, + }; + } +} diff --git a/src/Services/Biconnector/Source/Result/SourceResult.php b/src/Services/Biconnector/Source/Result/SourceResult.php new file mode 100644 index 00000000..3f8534fa --- /dev/null +++ b/src/Services/Biconnector/Source/Result/SourceResult.php @@ -0,0 +1,55 @@ + + * + * 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\SDK\Services\Biconnector\Source\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Class SourceResult + * + * Wraps the response from biconnector.source.get. + * + * The API returns: + * result.item.connection.{id, type, code, title, description, active, dateCreate, dateUpdate, createdById, updatedById} + * result.item.connectorId + * result.item.settings + * + * We flatten connection fields to the root level so SourceItemResult has a consistent flat structure. + */ +class SourceResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function source(): SourceItemResult + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + if (!empty($result['item']) && is_array($result['item'])) { + $item = $result['item']; + + // Flatten nested 'connection' fields to root level + if (!empty($item['connection']) && is_array($item['connection'])) { + $connection = $item['connection']; + unset($item['connection']); + $item = array_merge($connection, $item); + } + + return new SourceItemResult($item); + } + + return new SourceItemResult($result); + } +} diff --git a/src/Services/Biconnector/Source/Result/SourcesResult.php b/src/Services/Biconnector/Source/Result/SourcesResult.php new file mode 100644 index 00000000..eddc14b3 --- /dev/null +++ b/src/Services/Biconnector/Source/Result/SourcesResult.php @@ -0,0 +1,44 @@ + + * + * 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\SDK\Services\Biconnector\Source\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Class SourcesResult + * + * Wraps the response from biconnector.source.list. + * The API returns a flat array of source items. + */ +class SourcesResult extends AbstractResult +{ + /** + * @return SourceItemResult[] + * @throws BaseException + */ + public function getSources(): array + { + $items = []; + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + if (array_is_list($result)) { + foreach ($result as $item) { + $items[] = new SourceItemResult($item); + } + } + + return $items; + } +} diff --git a/src/Services/Biconnector/Source/Result/UpdatedSourceBatchResult.php b/src/Services/Biconnector/Source/Result/UpdatedSourceBatchResult.php new file mode 100644 index 00000000..99525485 --- /dev/null +++ b/src/Services/Biconnector/Source/Result/UpdatedSourceBatchResult.php @@ -0,0 +1,28 @@ + + * + * 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\SDK\Services\Biconnector\Source\Result; + +use Bitrix24\SDK\Core\Result\UpdatedItemBatchResult; + +/** + * Class UpdatedSourceBatchResult + */ +class UpdatedSourceBatchResult extends UpdatedItemBatchResult +{ + #[\Override] + public function isSuccess(): bool + { + return (bool)$this->getResponseData()->getResult(); + } +} diff --git a/src/Services/Biconnector/Source/Result/UpdatedSourceResult.php b/src/Services/Biconnector/Source/Result/UpdatedSourceResult.php new file mode 100644 index 00000000..aa01634f --- /dev/null +++ b/src/Services/Biconnector/Source/Result/UpdatedSourceResult.php @@ -0,0 +1,32 @@ + + * + * 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\SDK\Services\Biconnector\Source\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; + +/** + * Class UpdatedSourceResult + */ +class UpdatedSourceResult extends UpdatedItemResult +{ + /** + * @throws BaseException + */ + #[\Override] + public function isSuccess(): bool + { + return (bool)$this->getCoreResponse()->getResponseData()->getResult(); + } +} diff --git a/src/Services/Biconnector/Source/Service/Batch.php b/src/Services/Biconnector/Source/Service/Batch.php new file mode 100644 index 00000000..5a988272 --- /dev/null +++ b/src/Services/Biconnector/Source/Service/Batch.php @@ -0,0 +1,169 @@ + + * + * 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\SDK\Services\Biconnector\Source\Service; + +use Bitrix24\SDK\Attributes\ApiBatchMethodMetadata; +use Bitrix24\SDK\Attributes\ApiBatchServiceMetadata; +use Bitrix24\SDK\Core\Contracts\BatchOperationsInterface; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Services\Biconnector\Source\Result\AddedSourceBatchResult; +use Bitrix24\SDK\Services\Biconnector\Source\Result\DeletedSourceBatchResult; +use Bitrix24\SDK\Services\Biconnector\Source\Result\SourceItemResult; +use Bitrix24\SDK\Services\Biconnector\Source\Result\UpdatedSourceBatchResult; +use Generator; +use Psr\Log\LoggerInterface; + +#[ApiBatchServiceMetadata(new Scope(['biconnector']))] +class Batch +{ + /** + * Batch constructor + */ + public function __construct(protected BatchOperationsInterface $batch, protected LoggerInterface $log) + { + } + + /** + * Batch list sources + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-list.html + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'biconnector.source.list', + 'https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-list.html', + 'Batch list sources' + )] + public function list( + array $order = [], + array $filter = [], + array $select = [], + ?int $limit = null + ): Generator { + $this->log->debug( + 'batchList', + [ + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'limit' => $limit, + ] + ); + + foreach ( + $this->batch->getTraversableList( + 'biconnector.source.list', + $order, + $filter, + $select, + $limit + ) as $key => $value + ) { + yield $key => new SourceItemResult($value); + } + } + + /** + * Batch add sources + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-add.html + * + * @param array $sources + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'biconnector.source.add', + 'https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-add.html', + 'Batch add sources' + )] + public function add(array $sources): Generator + { + $items = []; + foreach ($sources as $item) { + $items[] = [ + 'fields' => $item, + ]; + } + + foreach ($this->batch->addEntityItems('biconnector.source.add', $items) as $key => $item) { + yield $key => new AddedSourceBatchResult($item); + } + } + + /** + * Batch update sources + * + * Update elements in array with structure: + * id => [ // Source id + * 'fields' => [] // Source fields to update + * ] + * + * @param array $entityItems + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'biconnector.source.update', + 'https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-update.html', + 'Batch update sources' + )] + public function update(array $entityItems): Generator + { + foreach ( + $this->batch->updateEntityItems( + 'biconnector.source.update', + $entityItems + ) as $key => $item + ) { + yield $key => new UpdatedSourceBatchResult($item); + } + } + + /** + * Batch delete sources + * + * @param int[] $sourceIds + * + * @return Generator + * @throws BaseException + */ + #[ApiBatchMethodMetadata( + 'biconnector.source.delete', + 'https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-delete.html', + 'Batch delete sources' + )] + public function delete(array $sourceIds): Generator + { + foreach ( + $this->batch->deleteEntityItems( + 'biconnector.source.delete', + $sourceIds + ) as $key => $item + ) { + yield $key => new DeletedSourceBatchResult($item); + } + } +} diff --git a/src/Services/Biconnector/Source/Service/Source.php b/src/Services/Biconnector/Source/Service/Source.php new file mode 100644 index 00000000..3c4d7e4c --- /dev/null +++ b/src/Services/Biconnector/Source/Service/Source.php @@ -0,0 +1,224 @@ + + * + * 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\SDK\Services\Biconnector\Source\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Contracts\CoreInterface; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\FieldsResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\Biconnector\Source\Result\AddedSourceResult; +use Bitrix24\SDK\Services\Biconnector\Source\Result\DeletedSourceResult; +use Bitrix24\SDK\Services\Biconnector\Source\Result\SourceResult; +use Bitrix24\SDK\Services\Biconnector\Source\Result\SourcesResult; +use Bitrix24\SDK\Services\Biconnector\Source\Result\UpdatedSourceResult; +use Psr\Log\LoggerInterface; + +#[ApiServiceMetadata(new Scope(['biconnector']))] +class Source extends AbstractService +{ + /** + * Source constructor + */ + public function __construct(public Batch $batch, CoreInterface $core, LoggerInterface $logger) + { + parent::__construct($core, $logger); + } + + /** + * Add a new data source + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-add.html + * + * @param array{ + * title: string, + * connectorId: int, + * description?: string, + * active?: bool, + * settings?: array, + * } $fields + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.source.add', + 'https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-add.html', + 'Add a new data source' + )] + public function add(array $fields): AddedSourceResult + { + return new AddedSourceResult( + $this->core->call( + 'biconnector.source.add', + [ + 'fields' => $fields, + ] + ) + ); + } + + /** + * Update an existing data source + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-update.html + * + * @param array{ + * title?: string, + * description?: string, + * active?: bool, + * settings?: array, + * } $fields + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.source.update', + 'https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-update.html', + 'Update an existing data source' + )] + public function update(int $id, array $fields): UpdatedSourceResult + { + return new UpdatedSourceResult( + $this->core->call( + 'biconnector.source.update', + [ + 'id' => $id, + 'fields' => $fields, + ] + ) + ); + } + + /** + * Get a data source by its ID + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-get.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.source.get', + 'https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-get.html', + 'Get a data source by its ID' + )] + public function get(int $id): SourceResult + { + return new SourceResult( + $this->core->call( + 'biconnector.source.get', + [ + 'id' => $id, + ] + ) + ); + } + + /** + * Get a list of data sources + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-list.html + * + * @param array $order - sort fields, e.g. ['id' => 'ASC'] + * @param array $filter - filter fields + * @param array $select - fields to include in the result + * @param int $page - page number for pagination (page size is 50 records per page) + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.source.list', + 'https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-list.html', + 'Get a list of data sources' + )] + public function list(array $order = [], array $filter = [], array $select = [], int $page = 1): SourcesResult + { + return new SourcesResult( + $this->core->call( + 'biconnector.source.list', + [ + 'order' => $order, + 'filter' => $filter, + 'select' => $select, + 'page' => $page, + ] + ) + ); + } + + /** + * Delete a data source by its ID + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-delete.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.source.delete', + 'https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-delete.html', + 'Delete a data source by its ID' + )] + public function delete(int $id): DeletedSourceResult + { + return new DeletedSourceResult( + $this->core->call( + 'biconnector.source.delete', + [ + 'id' => $id, + ] + ) + ); + } + + /** + * Get the fields description for data sources + * + * @link https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-fields.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'biconnector.source.fields', + 'https://apidocs.bitrix24.com/api-reference/biconnector/source/biconnector-source-fields.html', + 'Get the fields description for data sources' + )] + public function fields(): FieldsResult + { + return new FieldsResult($this->core->call('biconnector.source.fields')); + } + + /** + * Count data sources + * + * @throws BaseException + * @throws TransportException + */ + public function count(): int + { + $count = 0; + foreach ($this->batch->list() as $item) { + $count++; + } + + return $count; + } +} diff --git a/src/Services/ServiceBuilder.php b/src/Services/ServiceBuilder.php index e76b5e06..992f1587 100644 --- a/src/Services/ServiceBuilder.php +++ b/src/Services/ServiceBuilder.php @@ -17,6 +17,7 @@ use Bitrix24\SDK\Core\Contracts\BulkItemsReaderInterface; use Bitrix24\SDK\Core\Contracts\CoreInterface; use Bitrix24\SDK\Services\AI\AIServiceBuilder; +use Bitrix24\SDK\Services\Biconnector\BiconnectorServiceBuilder; use Bitrix24\SDK\Services\Catalog\CatalogServiceBuilder; use Bitrix24\SDK\Services\CRM\CRMServiceBuilder; use Bitrix24\SDK\Services\Disk\DiskServiceBuilder; @@ -49,6 +50,20 @@ public function __construct( parent::__construct($core, $batch, $bulkItemsReader, $log); } + public function getBiconnectorScope(): BiconnectorServiceBuilder + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new BiconnectorServiceBuilder( + $this->core, + $this->batch, + $this->bulkItemsReader, + $this->log + ); + } + + return $this->serviceCache[__METHOD__]; + } + public function getSaleScope(): SaleServiceBuilder { if (!isset($this->serviceCache[__METHOD__])) { diff --git a/tests/Integration/Services/Biconnector/Connector/Result/ConnectorItemResultAnnotationsTest.php b/tests/Integration/Services/Biconnector/Connector/Result/ConnectorItemResultAnnotationsTest.php new file mode 100644 index 00000000..cb342962 --- /dev/null +++ b/tests/Integration/Services/Biconnector/Connector/Result/ConnectorItemResultAnnotationsTest.php @@ -0,0 +1,104 @@ + + * + * 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\SDK\Tests\Integration\Services\Biconnector\Connector\Result; + +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\SDK\Services\Biconnector\Connector\Result\ConnectorItemResult; +use Bitrix24\SDK\Services\Biconnector\Connector\Service\Connector; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(ConnectorItemResult::class)] +class ConnectorItemResultAnnotationsTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Connector $connectorService; + + /** + * @throws InvalidArgumentException + */ + #[\Override] + protected function setUp(): void + { + $this->connectorService = Fabric::getServiceBuilder(true)->getBiconnectorScope()->connector(); + } + + #[Test] + #[TestDox('all fields in ConnectorItemResult are annotated and match live API fields schema')] + public function testAllSystemFieldsAnnotated(): void + { + $fieldCodes = $this->getConnectorFieldCodes(); + + $this->assertBitrix24AllResultItemFieldsAnnotated( + $fieldCodes, + ConnectorItemResult::class + ); + } + + #[Test] + #[TestDox('all fields in ConnectorItemResult have valid type casting matching API fields schema')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $fieldTypesMap = $this->getConnectorFieldTypesMap(); + + $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation( + $fieldTypesMap, + ConnectorItemResult::class + ); + } + + /** + * Returns list of field codes from biconnector.connector.fields API. + * + * @return array + */ + private function getConnectorFieldCodes(): array + { + $raw = $this->connectorService->fields()->getFieldsDescription(); + $fields = $raw['fields'] ?? []; + + return array_column($fields, 'title'); + } + + /** + * Returns field type map compatible with assertBitrix24AllResultItemFieldsHasValidTypeAnnotation. + * Normalises biconnector-specific types to types known by the shared assertion. + * + * @return array + */ + private function getConnectorFieldTypesMap(): array + { + $raw = $this->connectorService->fields()->getFieldsDescription(); + $fields = $raw['fields'] ?? []; + + $result = []; + foreach ($fields as $field) { + $apiType = $field['type']; + + // biconnector uses 'boolean' — map to 'char' which the shared assertion handles as bool + if ($apiType === 'boolean') { + $apiType = 'char'; + } + + $result[$field['title']] = ['type' => $apiType]; + } + + return $result; + } +} diff --git a/tests/Integration/Services/Biconnector/Connector/Service/BatchTest.php b/tests/Integration/Services/Biconnector/Connector/Service/BatchTest.php new file mode 100644 index 00000000..5314b6fc --- /dev/null +++ b/tests/Integration/Services/Biconnector/Connector/Service/BatchTest.php @@ -0,0 +1,160 @@ + + * + * 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\SDK\Tests\Integration\Services\Biconnector\Connector\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Biconnector\Connector\Result\ConnectorItemResult; +use Bitrix24\SDK\Services\Biconnector\Connector\Service\Batch; +use Bitrix24\SDK\Services\Biconnector\Connector\Service\Connector; +use Bitrix24\SDK\Tests\Integration\Fabric; +use Faker\Generator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use Faker; + +#[CoversClass(Batch::class)] +class BatchTest extends TestCase +{ + private Connector $connectorService; + + private Generator $faker; + + /** + * @throws InvalidArgumentException + */ + #[\Override] + protected function setUp(): void + { + $this->connectorService = Fabric::getServiceBuilder(true)->getBiconnectorScope()->connector(); + $this->faker = Faker\Factory::create(); + } + + /** + * Returns the minimum set of required fields to create a connector. + * + * @return array{ + * title: string, + * logo: string, + * urlCheck: string, + * urlData: string, + * urlTableList: string, + * urlTableDescription: string, + * settings: array, + * } + */ + private function makeConnectorFields(string $title): array + { + return [ + 'title' => $title, + 'logo' => 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIiIGhlaWdodD0iMjIiIHZpZXdCb3g9IjAgMCAyMiAyMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KCTxjaXJjbGUgY3g9IjExIiBjeT0iMTEiIHI9IjEwIiBmaWxsPSIjRkYzQjNCIiAvPgoJPHRleHQgeD0iMTEiIHk9IjEzIiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iNiIgZmlsbD0iI0ZGRkZGRiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9ImJvbGQiPlJFU1Q8L3RleHQ+Cjwvc3ZnPg==', + 'urlCheck' => 'https://example.com/api/check', + 'urlTableList' => 'https://example.com/api/table_list', + 'urlTableDescription' => 'https://example.com/api/table_description', + 'urlData' => 'https://example.com/api/data', + 'settings' => [ + [ + 'name' => 'Host', + 'type' => 'STRING', + 'code' => 'host', + ], + [ + 'name' => 'Port', + 'type' => 'STRING', + 'code' => 'port', + ], + [ + 'name' => 'Database', + 'type' => 'STRING', + 'code' => 'database', + ], + [ + 'name' => 'Username', + 'type' => 'STRING', + 'code' => 'username', + ], + [ + 'name' => 'Password', + 'type' => 'STRING', + 'code' => 'password', + ], + ], + ]; + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testBatchList(): void + { + $title = 'connector-' . $this->faker->uuid(); + $id = $this->connectorService->add($this->makeConnectorFields($title))->getId(); + + $count = 0; + foreach ($this->connectorService->batch->list([], [], [], 10) as $item) { + self::assertInstanceOf(ConnectorItemResult::class, $item); + $count++; + } + + self::assertGreaterThanOrEqual(1, $count); + + // Cleanup + $this->connectorService->delete($id); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testBatchAdd(): void + { + $connectors = []; + for ($i = 0; $i < 3; $i++) { + $connectors[] = $this->makeConnectorFields('connector-batch-' . $this->faker->uuid()); + } + + $addedIds = []; + foreach ($this->connectorService->batch->add($connectors) as $result) { + $addedIds[] = $result->getId(); + self::assertGreaterThanOrEqual(1, $result->getId()); + } + + self::assertCount(3, $addedIds); + + // Cleanup + foreach ($this->connectorService->batch->delete($addedIds) as $deleteResult) { + self::assertTrue($deleteResult->isSuccess()); + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testBatchDelete(): void + { + $ids = []; + for ($i = 0; $i < 2; $i++) { + $ids[] = $this->connectorService->add( + $this->makeConnectorFields('connector-del-batch-' . $this->faker->uuid()) + )->getId(); + } + + foreach ($this->connectorService->batch->delete($ids) as $result) { + self::assertTrue($result->isSuccess()); + } + } +} diff --git a/tests/Integration/Services/Biconnector/Connector/Service/ConnectorTest.php b/tests/Integration/Services/Biconnector/Connector/Service/ConnectorTest.php new file mode 100644 index 00000000..86e51ac5 --- /dev/null +++ b/tests/Integration/Services/Biconnector/Connector/Service/ConnectorTest.php @@ -0,0 +1,211 @@ + + * + * 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\SDK\Tests\Integration\Services\Biconnector\Connector\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Biconnector\Connector\Result\ConnectorItemResult; +use Bitrix24\SDK\Services\Biconnector\Connector\Service\Connector; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use Faker\Generator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use Faker; + +#[CoversClass(Connector::class)] +class ConnectorTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Connector $connectorService; + + private Generator $faker; + + /** + * @throws InvalidArgumentException + */ + #[\Override] + protected function setUp(): void + { + $this->connectorService = Fabric::getServiceBuilder(true)->getBiconnectorScope()->connector(); + $this->faker = Faker\Factory::create(); + } + + /** + * Returns the minimum set of required fields to create a connector. + * + * @return array{ + * title: string, + * logo: string, + * urlCheck: string, + * urlData: string, + * urlTableList: string, + * urlTableDescription: string, + * settings: array, + * } + */ + private function makeConnectorFields(string $title): array + { + return [ + 'title' => $title, + 'logo' => 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIiIGhlaWdodD0iMjIiIHZpZXdCb3g9IjAgMCAyMiAyMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KCTxjaXJjbGUgY3g9IjExIiBjeT0iMTEiIHI9IjEwIiBmaWxsPSIjRkYzQjNCIiAvPgoJPHRleHQgeD0iMTEiIHk9IjEzIiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iNiIgZmlsbD0iI0ZGRkZGRiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9ImJvbGQiPlJFU1Q8L3RleHQ+Cjwvc3ZnPg==', + 'urlCheck' => 'https://example.com/api/check', + 'urlTableList' => 'https://example.com/api/table_list', + 'urlTableDescription' => 'https://example.com/api/table_description', + 'urlData' => 'https://example.com/api/data', + 'settings' => [ + [ + 'name' => 'Host', + 'type' => 'STRING', + 'code' => 'host', + ], + [ + 'name' => 'Port', + 'type' => 'STRING', + 'code' => 'port', + ], + [ + 'name' => 'Database', + 'type' => 'STRING', + 'code' => 'database', + ], + [ + 'name' => 'Username', + 'type' => 'STRING', + 'code' => 'username', + ], + [ + 'name' => 'Password', + 'type' => 'STRING', + 'code' => 'password', + ], + ], + ]; + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testAdd(): void + { + $title = 'connector-' . $this->faker->uuid(); + $id = $this->connectorService->add($this->makeConnectorFields($title))->getId(); + + self::assertGreaterThanOrEqual(1, $id); + + // Cleanup + $this->connectorService->delete($id); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testGet(): void + { + $title = 'connector-' . $this->faker->uuid(); + $id = $this->connectorService->add($this->makeConnectorFields($title))->getId(); + + $connectorItemResult = $this->connectorService->get($id)->connector(); + self::assertInstanceOf(ConnectorItemResult::class, $connectorItemResult); + self::assertEquals($id, $connectorItemResult->id); + self::assertEquals($title, $connectorItemResult->title); + + // Cleanup + $this->connectorService->delete($id); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testList(): void + { + $title = 'connector-' . $this->faker->uuid(); + $id = $this->connectorService->add($this->makeConnectorFields($title))->getId(); + + $list = $this->connectorService->list()->getConnectors(); + self::assertIsArray($list); + self::assertGreaterThanOrEqual(1, count($list)); + + // Cleanup + $this->connectorService->delete($id); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testUpdate(): void + { + $title = 'connector-' . $this->faker->uuid(); + $id = $this->connectorService->add($this->makeConnectorFields($title))->getId(); + + $newTitle = $title . '-updated'; + self::assertTrue( + $this->connectorService->update($id, [ + 'title' => $newTitle, + ])->isSuccess() + ); + + self::assertEquals($newTitle, $this->connectorService->get($id)->connector()->title); + + // Cleanup + $this->connectorService->delete($id); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testDelete(): void + { + $title = 'connector-' . $this->faker->uuid(); + $id = $this->connectorService->add($this->makeConnectorFields($title))->getId(); + + self::assertTrue($this->connectorService->delete($id)->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testFields(): void + { + $fields = $this->connectorService->fields()->getFieldsDescription(); + self::assertIsArray($fields); + self::assertNotEmpty($fields); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testCount(): void + { + $countBefore = $this->connectorService->count(); + + $title = 'connector-' . $this->faker->uuid(); + $id = $this->connectorService->add($this->makeConnectorFields($title))->getId(); + + $countAfter = $this->connectorService->count(); + self::assertEquals($countBefore + 1, $countAfter); + + // Cleanup + $this->connectorService->delete($id); + } +} diff --git a/tests/Integration/Services/Biconnector/Dataset/Result/DatasetItemResultAnnotationsTest.php b/tests/Integration/Services/Biconnector/Dataset/Result/DatasetItemResultAnnotationsTest.php new file mode 100644 index 00000000..5e69101b --- /dev/null +++ b/tests/Integration/Services/Biconnector/Dataset/Result/DatasetItemResultAnnotationsTest.php @@ -0,0 +1,207 @@ + + * + * 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\SDK\Tests\Integration\Services\Biconnector\Dataset\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Biconnector\Connector\Service\Connector; +use Bitrix24\SDK\Services\Biconnector\Dataset\Result\DatasetItemResult; +use Bitrix24\SDK\Services\Biconnector\Dataset\Service\Dataset; +use Bitrix24\SDK\Services\Biconnector\Source\Service\Source; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use Faker\Generator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; +use Faker; + +#[CoversClass(DatasetItemResult::class)] +class DatasetItemResultAnnotationsTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Dataset $datasetService; + + private Source $sourceService; + + private Connector $connectorService; + + private Generator $faker; + + private int $connectorId; + + private int $sourceId; + + private int $datasetId; + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function setUp(): void + { + // setUp body is commented out: this test class requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $biconnectorServiceBuilder = Fabric::getServiceBuilder(true)->getBiconnectorScope(); + $this->datasetService = $biconnectorServiceBuilder->dataset(); + $this->sourceService = $biconnectorServiceBuilder->source(); + $this->connectorService = $biconnectorServiceBuilder->connector(); + $this->faker = Faker\Factory::create(); + + // Create connector, source, and dataset for annotation tests + $this->connectorId = $this->connectorService->add($this->makeConnectorFields( + 'connector-annotations-' . $this->faker->uuid() + ))->getId(); + + $this->sourceId = $this->sourceService->add([ + 'title' => 'source-annotations-' . $this->faker->uuid(), + 'connectorId' => $this->connectorId, + 'settings' => [ + 'host' => '172.18.0.2', + 'port' => '3306', + 'database' => 'customer_db', + 'username' => 'testuser', + 'password' => 'testpass123', + ], + ])->getId(); + + $name = 'ds' . substr(str_replace('-', '', $this->faker->uuid()), 0, 20); + $this->datasetId = $this->datasetService->add([ + 'sourceId' => $this->sourceId, + 'name' => $name, + 'externalName' => 'order_items', + 'externalCode' => 'order_items', + 'fields' => [ + ['type' => 'int', 'name' => 'ID', 'externalCode' => 'ID'], + ], + ])->getId(); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function tearDown(): void + { + // tearDown body is commented out: this test class requires an additional external service + // (a real database accessible via the Biconnector connector). + /* + try { + $this->datasetService->delete($this->datasetId); + } catch (\Throwable) { + } + + try { + $this->sourceService->delete($this->sourceId); + } catch (\Throwable) { + } + + try { + $this->connectorService->delete($this->connectorId); + } catch (\Throwable) { + } + */ + } + + private function makeConnectorFields(string $title): array + { + return [ + 'title' => $title, + 'logo' => 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIiIGhlaWdodD0iMjIiIHZpZXdCb3g9IjAgMCAyMiAyMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KCTxjaXJjbGUgY3g9IjExIiBjeT0iMTEiIHI9IjEwIiBmaWxsPSIjRkYzQjNCIiAvPgoJPHRleHQgeD0iMTEiIHk9IjEzIiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iNiIgZmlsbD0iI0ZGRkZGRiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9ImJvbGQiPlJFU1Q8L3RleHQ+Cjwvc3ZnPg==', + 'urlCheck' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=check', + 'urlTableList' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=table_list', + 'urlTableDescription' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=table_description', + 'urlData' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=data', + 'settings' => [ + ['name' => 'Host', 'type' => 'STRING', 'code' => 'host'], + ['name' => 'Port', 'type' => 'STRING', 'code' => 'port'], + ['name' => 'Database', 'type' => 'STRING', 'code' => 'database'], + ['name' => 'Username', 'type' => 'STRING', 'code' => 'username'], + ['name' => 'Password', 'type' => 'STRING', 'code' => 'password'], + ], + ]; + } + + #[Test] + #[TestDox('all fields in DatasetItemResult are annotated and match live API fields schema')] + public function testAllSystemFieldsAnnotated(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $fieldCodes = $this->getDatasetFieldCodes(); + + $this->assertBitrix24AllResultItemFieldsAnnotated( + $fieldCodes, + DatasetItemResult::class + ); + */ + } + + #[Test] + #[TestDox('all fields in DatasetItemResult have valid type casting matching API fields schema')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $fieldTypesMap = $this->getDatasetFieldTypesMap(); + + $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation( + $fieldTypesMap, + DatasetItemResult::class + ); + */ + } + + /** + * Returns list of field codes from biconnector.dataset.fields API. + * + * @return array + */ + private function getDatasetFieldCodes(): array + { + $raw = $this->datasetService->fields()->getFieldsDescription(); + $fields = $raw['fields'] ?? []; + + return array_column($fields, 'title'); + } + + /** + * Returns field type map compatible with assertBitrix24AllResultItemFieldsHasValidTypeAnnotation. + * + * @return array + */ + private function getDatasetFieldTypesMap(): array + { + $raw = $this->datasetService->fields()->getFieldsDescription(); + $fields = $raw['fields'] ?? []; + + $result = []; + foreach ($fields as $field) { + $result[$field['title']] = ['type' => $field['type']]; + } + + return $result; + } +} diff --git a/tests/Integration/Services/Biconnector/Dataset/Service/BatchTest.php b/tests/Integration/Services/Biconnector/Dataset/Service/BatchTest.php new file mode 100644 index 00000000..a79969c9 --- /dev/null +++ b/tests/Integration/Services/Biconnector/Dataset/Service/BatchTest.php @@ -0,0 +1,212 @@ + + * + * 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\SDK\Tests\Integration\Services\Biconnector\Dataset\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Biconnector\Connector\Service\Connector; +use Bitrix24\SDK\Services\Biconnector\Dataset\Result\DatasetItemResult; +use Bitrix24\SDK\Services\Biconnector\Dataset\Service\Batch; +use Bitrix24\SDK\Services\Biconnector\Dataset\Service\Dataset; +use Bitrix24\SDK\Services\Biconnector\Source\Service\Source; +use Bitrix24\SDK\Tests\Integration\Fabric; +use Faker\Generator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use Faker; + +#[CoversClass(Batch::class)] +class BatchTest extends TestCase +{ + private Dataset $datasetService; + + private Source $sourceService; + + private Connector $connectorService; + + private Generator $faker; + + private int $connectorId; + + private int $sourceId; + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function setUp(): void + { + // setUp body is commented out: this test class requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $biconnectorServiceBuilder = Fabric::getServiceBuilder(true)->getBiconnectorScope(); + $this->datasetService = $biconnectorServiceBuilder->dataset(); + $this->sourceService = $biconnectorServiceBuilder->source(); + $this->connectorService = $biconnectorServiceBuilder->connector(); + $this->faker = Faker\Factory::create(); + + // Create a connector and source to use for dataset batch tests + $this->connectorId = $this->connectorService->add($this->makeConnectorFields( + 'connector-for-dataset-batch-' . $this->faker->uuid() + ))->getId(); + + $this->sourceId = $this->sourceService->add([ + 'title' => 'source-for-dataset-batch-' . $this->faker->uuid(), + 'connectorId' => $this->connectorId, + 'settings' => [ + 'host' => '172.18.0.2', + 'port' => '3306', + 'database' => 'customer_db', + 'username' => 'testuser', + 'password' => 'testpass123', + ], + ])->getId(); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function tearDown(): void + { + // tearDown body is commented out: this test class requires an additional external service + // (a real database accessible via the Biconnector connector). + /* + try { + $this->sourceService->delete($this->sourceId); + } catch (\Throwable) { + } + + try { + $this->connectorService->delete($this->connectorId); + } catch (\Throwable) { + } + */ + } + + private function makeConnectorFields(string $title): array + { + return [ + 'title' => $title, + 'logo' => 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIiIGhlaWdodD0iMjIiIHZpZXdCb3g9IjAgMCAyMiAyMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KCTxjaXJjbGUgY3g9IjExIiBjeT0iMTEiIHI9IjEwIiBmaWxsPSIjRkYzQjNCIiAvPgoJPHRleHQgeD0iMTEiIHk9IjEzIiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iNiIgZmlsbD0iI0ZGRkZGRiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9ImJvbGQiPlJFU1Q8L3RleHQ+Cjwvc3ZnPg==', + 'urlCheck' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=check', + 'urlTableList' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=table_list', + 'urlTableDescription' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=table_description', + 'urlData' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=data', + 'settings' => [ + ['name' => 'Host', 'type' => 'STRING', 'code' => 'host'], + ['name' => 'Port', 'type' => 'STRING', 'code' => 'port'], + ['name' => 'Database', 'type' => 'STRING', 'code' => 'database'], + ['name' => 'Username', 'type' => 'STRING', 'code' => 'username'], + ['name' => 'Password', 'type' => 'STRING', 'code' => 'password'], + ], + ]; + } + + private function makeDatasetFields(string $name): array + { + return [ + 'sourceId' => $this->sourceId, + 'name' => $name, + 'externalName' => 'order_items', + 'externalCode' => 'order_items', + 'fields' => [ + ['type' => 'int', 'name' => 'ID', 'externalCode' => 'ID'], + ], + ]; + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testBatchList(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $name = 'ds' . substr(str_replace('-', '', $this->faker->uuid()), 0, 20); + $id = $this->datasetService->add($this->makeDatasetFields($name))->getId(); + + $count = 0; + foreach ($this->datasetService->batch->list([], [], [], 10) as $item) { + self::assertInstanceOf(DatasetItemResult::class, $item); + $count++; + } + + self::assertGreaterThanOrEqual(1, $count); + + // Cleanup + $this->datasetService->delete($id); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testBatchAdd(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $datasets = []; + for ($i = 0; $i < 3; $i++) { + $name = 'ds' . substr(str_replace('-', '', $this->faker->uuid()), 0, 20); + $datasets[] = $this->makeDatasetFields($name); + } + + $addedIds = []; + foreach ($this->datasetService->batch->add($datasets) as $result) { + $addedIds[] = $result->getId(); + self::assertGreaterThanOrEqual(1, $result->getId()); + } + + self::assertCount(3, $addedIds); + + // Cleanup + foreach ($this->datasetService->batch->delete($addedIds) as $deleteResult) { + self::assertTrue($deleteResult->isSuccess()); + } + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testBatchDelete(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $ids = []; + for ($i = 0; $i < 2; $i++) { + $name = 'ds' . substr(str_replace('-', '', $this->faker->uuid()), 0, 20); + $ids[] = $this->datasetService->add($this->makeDatasetFields($name))->getId(); + } + + foreach ($this->datasetService->batch->delete($ids) as $result) { + self::assertTrue($result->isSuccess()); + } + */ + } +} diff --git a/tests/Integration/Services/Biconnector/Dataset/Service/DatasetTest.php b/tests/Integration/Services/Biconnector/Dataset/Service/DatasetTest.php new file mode 100644 index 00000000..92951817 --- /dev/null +++ b/tests/Integration/Services/Biconnector/Dataset/Service/DatasetTest.php @@ -0,0 +1,317 @@ + + * + * 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\SDK\Tests\Integration\Services\Biconnector\Dataset\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Biconnector\Connector\Service\Connector; +use Bitrix24\SDK\Services\Biconnector\Dataset\Result\DatasetItemResult; +use Bitrix24\SDK\Services\Biconnector\Dataset\Service\Dataset; +use Bitrix24\SDK\Services\Biconnector\Source\Service\Source; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use Faker\Generator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use Faker; + +#[CoversClass(Dataset::class)] +class DatasetTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Dataset $datasetService; + + private Source $sourceService; + + private Connector $connectorService; + + private Generator $faker; + + private int $connectorId; + + private int $sourceId; + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function setUp(): void + { + // setUp body is commented out: this test class requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $biconnectorServiceBuilder = Fabric::getServiceBuilder(true)->getBiconnectorScope(); + $this->datasetService = $biconnectorServiceBuilder->dataset(); + $this->sourceService = $biconnectorServiceBuilder->source(); + $this->connectorService = $biconnectorServiceBuilder->connector(); + $this->faker = Faker\Factory::create(); + + // Create a connector and source to use for dataset tests + $this->connectorId = $this->connectorService->add($this->makeConnectorFields( + 'connector-for-dataset-' . $this->faker->uuid() + ))->getId(); + + $this->sourceId = $this->sourceService->add([ + 'title' => 'source-for-dataset-' . $this->faker->uuid(), + 'connectorId' => $this->connectorId, + 'settings' => [ + 'host' => '172.18.0.2', + 'port' => '3306', + 'database' => 'customer_db', + 'username' => 'testuser', + 'password' => 'testpass123', + ], + ])->getId(); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function tearDown(): void + { + // tearDown body is commented out: this test class requires an additional external service + // (a real database accessible via the Biconnector connector). + /* + try { + $this->sourceService->delete($this->sourceId); + } catch (\Throwable) { + } + + try { + $this->connectorService->delete($this->connectorId); + } catch (\Throwable) { + } + */ + } + + private function makeConnectorFields(string $title): array + { + return [ + 'title' => $title, + 'logo' => 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIiIGhlaWdodD0iMjIiIHZpZXdCb3g9IjAgMCAyMiAyMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KCTxjaXJjbGUgY3g9IjExIiBjeT0iMTEiIHI9IjEwIiBmaWxsPSIjRkYzQjNCIiAvPgoJPHRleHQgeD0iMTEiIHk9IjEzIiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iNiIgZmlsbD0iI0ZGRkZGRiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9ImJvbGQiPlJFU1Q8L3RleHQ+Cjwvc3ZnPg==', + 'urlCheck' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=check', + 'urlTableList' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=table_list', + 'urlTableDescription' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=table_description', + 'urlData' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=data', + 'settings' => [ + ['name' => 'Host', 'type' => 'STRING', 'code' => 'host'], + ['name' => 'Port', 'type' => 'STRING', 'code' => 'port'], + ['name' => 'Database', 'type' => 'STRING', 'code' => 'database'], + ['name' => 'Username', 'type' => 'STRING', 'code' => 'username'], + ['name' => 'Password', 'type' => 'STRING', 'code' => 'password'], + ], + ]; + } + + /** + * Returns the minimum set of required fields to create a dataset. + */ + private function makeDatasetFields(string $name): array + { + return [ + 'sourceId' => $this->sourceId, + 'name' => $name, + 'externalName' => 'order_items', + 'externalCode' => 'order_items', + 'fields' => [ + ['type' => 'int', 'name' => 'ID', 'externalCode' => 'ID'], + ], + ]; + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testAdd(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $name = 'ds' . substr(str_replace('-', '', $this->faker->uuid()), 0, 20); + $id = $this->datasetService->add($this->makeDatasetFields($name))->getId(); + + self::assertGreaterThanOrEqual(1, $id); + + // Cleanup + $this->datasetService->delete($id); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testGet(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $name = 'ds' . substr(str_replace('-', '', $this->faker->uuid()), 0, 20); + $id = $this->datasetService->add($this->makeDatasetFields($name))->getId(); + + $datasetItemResult = $this->datasetService->get($id)->dataset(); + self::assertInstanceOf(DatasetItemResult::class, $datasetItemResult); + self::assertEquals($id, $datasetItemResult->id); + self::assertEquals($name, $datasetItemResult->name); + + // Cleanup + $this->datasetService->delete($id); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testList(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $name = 'ds' . substr(str_replace('-', '', $this->faker->uuid()), 0, 20); + $id = $this->datasetService->add($this->makeDatasetFields($name))->getId(); + + $list = $this->datasetService->list()->getDatasets(); + self::assertIsArray($list); + self::assertGreaterThanOrEqual(1, count($list)); + + // Cleanup + $this->datasetService->delete($id); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testUpdate(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $name = 'ds' . substr(str_replace('-', '', $this->faker->uuid()), 0, 20); + $id = $this->datasetService->add($this->makeDatasetFields($name))->getId(); + + $newDescription = 'Updated description ' . $this->faker->sentence(); + self::assertTrue( + $this->datasetService->update($id, ['description' => $newDescription])->isSuccess() + ); + + self::assertEquals($newDescription, $this->datasetService->get($id)->dataset()->description); + + // Cleanup + $this->datasetService->delete($id); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testDelete(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $name = 'ds' . substr(str_replace('-', '', $this->faker->uuid()), 0, 20); + $id = $this->datasetService->add($this->makeDatasetFields($name))->getId(); + + self::assertTrue($this->datasetService->delete($id)->isSuccess()); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testFields(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $fields = $this->datasetService->fields()->getFieldsDescription(); + self::assertIsArray($fields); + self::assertNotEmpty($fields); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testUpdateFields(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $name = 'ds' . substr(str_replace('-', '', $this->faker->uuid()), 0, 20); + $id = $this->datasetService->add($this->makeDatasetFields($name))->getId(); + + // Get current field IDs + $datasetItemResult = $this->datasetService->get($id)->dataset(); + $existingFields = $datasetItemResult->fields ?? []; + $fieldId = $existingFields[0]['id'] ?? null; + + // Add a new field + $updatedDatasetResult = $this->datasetService->updateFields( + $id, + [['type' => 'string', 'name' => 'EXTRA', 'externalCode' => 'EXTRA']], + $fieldId !== null ? [['id' => $fieldId, 'visible' => false]] : [], + [] + ); + + self::assertTrue($updatedDatasetResult->isSuccess()); + + // Cleanup + $this->datasetService->delete($id); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testCount(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $countBefore = $this->datasetService->count(); + + $name = 'ds' . substr(str_replace('-', '', $this->faker->uuid()), 0, 20); + $id = $this->datasetService->add($this->makeDatasetFields($name))->getId(); + + $countAfter = $this->datasetService->count(); + self::assertEquals($countBefore + 1, $countAfter); + + // Cleanup + $this->datasetService->delete($id); + */ + } +} diff --git a/tests/Integration/Services/Biconnector/Source/Result/SourceItemResultAnnotationsTest.php b/tests/Integration/Services/Biconnector/Source/Result/SourceItemResultAnnotationsTest.php new file mode 100644 index 00000000..a66ebd7b --- /dev/null +++ b/tests/Integration/Services/Biconnector/Source/Result/SourceItemResultAnnotationsTest.php @@ -0,0 +1,215 @@ + + * + * 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\SDK\Tests\Integration\Services\Biconnector\Source\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Biconnector\Connector\Service\Connector; +use Bitrix24\SDK\Services\Biconnector\Source\Result\SourceItemResult; +use Bitrix24\SDK\Services\Biconnector\Source\Service\Source; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use Faker\Generator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; +use Faker; + +#[CoversClass(SourceItemResult::class)] +class SourceItemResultAnnotationsTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Source $sourceService; + + private Connector $connectorService; + + private Generator $faker; + + private int $connectorId; + + private int $sourceId; + + /** + * @throws InvalidArgumentException + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function setUp(): void + { + // setUp body is commented out: this test class requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $builder = Fabric::getServiceBuilder(true)->getBiconnectorScope(); + $this->sourceService = $builder->source(); + $this->connectorService = $builder->connector(); + $this->faker = Faker\Factory::create(); + + // Create a connector, then a source for annotation tests + $this->connectorId = $this->connectorService->add($this->makeConnectorFields( + 'connector-annotations-' . $this->faker->uuid() + ))->getId(); + + $this->sourceId = $this->sourceService->add([ + 'title' => 'source-annotations-' . $this->faker->uuid(), + 'connectorId' => $this->connectorId, + 'settings' => [ + 'host' => '172.18.0.2', + 'port' => '3306', + 'database' => 'customer_db', + 'username' => 'testuser', + 'password' => 'testpass123', + ], + ])->getId(); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function tearDown(): void + { + // tearDown body is commented out: this test class requires an additional external service + // (a real database accessible via the Biconnector connector). + /* + try { + $this->sourceService->delete($this->sourceId); + } catch (\Throwable) { + } + + try { + $this->connectorService->delete($this->connectorId); + } catch (\Throwable) { + } + */ + } + + private function makeConnectorFields(string $title): array + { + return [ + 'title' => $title, + 'logo' => 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIiIGhlaWdodD0iMjIiIHZpZXdCb3g9IjAgMCAyMiAyMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KCTxjaXJjbGUgY3g9IjExIiBjeT0iMTEiIHI9IjEwIiBmaWxsPSIjRkYzQjNCIiAvPgoJPHRleHQgeD0iMTEiIHk9IjEzIiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iNiIgZmlsbD0iI0ZGRkZGRiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9ImJvbGQiPlJFU1Q8L3RleHQ+Cjwvc3ZnPg==', + 'urlCheck' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=check', + 'urlTableList' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=table_list', + 'urlTableDescription' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=table_description', + 'urlData' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=data', + 'settings' => [ + [ + 'name' => 'Host', + 'type' => 'STRING', + 'code' => 'host', + ], + [ + 'name' => 'Port', + 'type' => 'STRING', + 'code' => 'port', + ], + [ + 'name' => 'Database', + 'type' => 'STRING', + 'code' => 'database', + ], + [ + 'name' => 'Username', + 'type' => 'STRING', + 'code' => 'username', + ], + [ + 'name' => 'Password', + 'type' => 'STRING', + 'code' => 'password', + ], + ], + ]; + } + + #[Test] + #[TestDox('all fields in SourceItemResult are annotated and match live API fields schema')] + public function testAllSystemFieldsAnnotated(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $fieldCodes = $this->getSourceFieldCodes(); + + $this->assertBitrix24AllResultItemFieldsAnnotated( + $fieldCodes, + SourceItemResult::class + ); + */ + } + + #[Test] + #[TestDox('all fields in SourceItemResult have valid type casting matching API fields schema')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $fieldTypesMap = $this->getSourceFieldTypesMap(); + + $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation( + $fieldTypesMap, + SourceItemResult::class + ); + */ + } + + /** + * Returns list of field codes from biconnector.source.fields API. + * + * @return array + */ + private function getSourceFieldCodes(): array + { + $raw = $this->sourceService->fields()->getFieldsDescription(); + $fields = $raw['fields'] ?? []; + + return array_column($fields, 'title'); + } + + /** + * Returns field type map compatible with assertBitrix24AllResultItemFieldsHasValidTypeAnnotation. + * Normalises biconnector-specific types to types known by the shared assertion. + * + * @return array + */ + private function getSourceFieldTypesMap(): array + { + $raw = $this->sourceService->fields()->getFieldsDescription(); + $fields = $raw['fields'] ?? []; + + $result = []; + foreach ($fields as $field) { + $apiType = $field['type']; + + // biconnector uses 'boolean' — map to 'char' which the shared assertion handles as bool + if ($apiType === 'boolean') { + $apiType = 'char'; + } + + $result[$field['title']] = ['type' => $apiType]; + } + + return $result; + } +} diff --git a/tests/Integration/Services/Biconnector/Source/Service/BatchTest.php b/tests/Integration/Services/Biconnector/Source/Service/BatchTest.php new file mode 100644 index 00000000..1915c7cd --- /dev/null +++ b/tests/Integration/Services/Biconnector/Source/Service/BatchTest.php @@ -0,0 +1,205 @@ + + * + * 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\SDK\Tests\Integration\Services\Biconnector\Source\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Biconnector\Connector\Service\Connector; +use Bitrix24\SDK\Services\Biconnector\Source\Result\SourceItemResult; +use Bitrix24\SDK\Services\Biconnector\Source\Service\Batch; +use Bitrix24\SDK\Services\Biconnector\Source\Service\Source; +use Bitrix24\SDK\Tests\Integration\Fabric; +use Faker\Generator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use Faker; + +#[CoversClass(Batch::class)] +class BatchTest extends TestCase +{ + private Source $sourceService; + + private Connector $connectorService; + + private Generator $faker; + + private int $connectorId; + + /** + * @throws InvalidArgumentException + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function setUp(): void + { + $builder = Fabric::getServiceBuilder(true)->getBiconnectorScope(); + $this->sourceService = $builder->source(); + $this->connectorService = $builder->connector(); + $this->faker = Faker\Factory::create(); + + // Create a connector to use for source tests + $this->connectorId = $this->connectorService->add($this->makeConnectorFields( + 'connector-for-source-batch-' . $this->faker->uuid() + ))->getId(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function tearDown(): void + { + try { + $this->connectorService->delete($this->connectorId); + } catch (\Throwable) { + // Ignore cleanup errors + } + } + + private function makeConnectorFields(string $title): array + { + return [ + 'title' => $title, + 'logo' => 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIiIGhlaWdodD0iMjIiIHZpZXdCb3g9IjAgMCAyMiAyMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KCTxjaXJjbGUgY3g9IjExIiBjeT0iMTEiIHI9IjEwIiBmaWxsPSIjRkYzQjNCIiAvPgoJPHRleHQgeD0iMTEiIHk9IjEzIiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iNiIgZmlsbD0iI0ZGRkZGRiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9ImJvbGQiPlJFU1Q8L3RleHQ+Cjwvc3ZnPg==', + 'urlCheck' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=check', + 'urlTableList' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=table_list', + 'urlTableDescription' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=table_description', + 'urlData' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=data', + 'settings' => [ + [ + 'name' => 'Host', + 'type' => 'STRING', + 'code' => 'host', + ], + [ + 'name' => 'Port', + 'type' => 'STRING', + 'code' => 'port', + ], + [ + 'name' => 'Database', + 'type' => 'STRING', + 'code' => 'database', + ], + [ + 'name' => 'Username', + 'type' => 'STRING', + 'code' => 'username', + ], + [ + 'name' => 'Password', + 'type' => 'STRING', + 'code' => 'password', + ], + ], + ]; + } + + private function makeSourceFields(string $title): array + { + return [ + 'title' => $title, + 'connectorId' => $this->connectorId, + 'settings' => [ + 'host' => '172.18.0.2', + 'port' => '3306', + 'database' => 'customer_db', + 'username' => 'testuser', + 'password' => 'testpass123', + ], + ]; + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testBatchList(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $title = 'source-' . $this->faker->uuid(); + $id = $this->sourceService->add($this->makeSourceFields($title))->getId(); + + $count = 0; + foreach ($this->sourceService->batch->list([], [], [], 10) as $item) { + self::assertInstanceOf(SourceItemResult::class, $item); + $count++; + } + + self::assertGreaterThanOrEqual(1, $count); + + // Cleanup + $this->sourceService->delete($id); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testBatchAdd(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $sources = []; + for ($i = 0; $i < 3; $i++) { + $sources[] = $this->makeSourceFields('source-batch-' . $this->faker->uuid()); + } + + $addedIds = []; + foreach ($this->sourceService->batch->add($sources) as $result) { + $addedIds[] = $result->getId(); + self::assertGreaterThanOrEqual(1, $result->getId()); + } + + self::assertCount(3, $addedIds); + + // Cleanup + foreach ($this->sourceService->batch->delete($addedIds) as $deleteResult) { + self::assertTrue($deleteResult->isSuccess()); + } + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testBatchDelete(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $ids = []; + for ($i = 0; $i < 2; $i++) { + $ids[] = $this->sourceService->add( + $this->makeSourceFields('source-del-batch-' . $this->faker->uuid()) + )->getId(); + } + + foreach ($this->sourceService->batch->delete($ids) as $result) { + self::assertTrue($result->isSuccess()); + } + */ + } +} diff --git a/tests/Integration/Services/Biconnector/Source/Service/SourceTest.php b/tests/Integration/Services/Biconnector/Source/Service/SourceTest.php new file mode 100644 index 00000000..80a0e432 --- /dev/null +++ b/tests/Integration/Services/Biconnector/Source/Service/SourceTest.php @@ -0,0 +1,285 @@ + + * + * 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\SDK\Tests\Integration\Services\Biconnector\Source\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Biconnector\Connector\Service\Connector; +use Bitrix24\SDK\Services\Biconnector\Source\Result\SourceItemResult; +use Bitrix24\SDK\Services\Biconnector\Source\Service\Source; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use Faker\Generator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use Faker; + +#[CoversClass(Source::class)] +class SourceTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Source $sourceService; + + private Connector $connectorService; + + private Generator $faker; + + private int $connectorId; + + /** + * @throws InvalidArgumentException + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function setUp(): void + { + $builder = Fabric::getServiceBuilder(true)->getBiconnectorScope(); + $this->sourceService = $builder->source(); + $this->connectorService = $builder->connector(); + $this->faker = Faker\Factory::create(); + + // Create a connector to use for source tests + $this->connectorId = $this->connectorService->add($this->makeConnectorFields( + 'connector-for-source-' . $this->faker->uuid() + ))->getId(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function tearDown(): void + { + // Clean up the connector created for tests + try { + $this->connectorService->delete($this->connectorId); + } catch (\Throwable) { + // Ignore cleanup errors + } + } + + /** + * Returns the minimum set of required fields to create a connector. + */ + private function makeConnectorFields(string $title): array + { + return [ + 'title' => $title, + 'logo' => 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIiIGhlaWdodD0iMjIiIHZpZXdCb3g9IjAgMCAyMiAyMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KCTxjaXJjbGUgY3g9IjExIiBjeT0iMTEiIHI9IjEwIiBmaWxsPSIjRkYzQjNCIiAvPgoJPHRleHQgeD0iMTEiIHk9IjEzIiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iNiIgZmlsbD0iI0ZGRkZGRiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9ImJvbGQiPlJFU1Q8L3RleHQ+Cjwvc3ZnPg==', + 'urlCheck' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=check', + 'urlTableList' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=table_list', + 'urlTableDescription' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=table_description', + 'urlData' => 'https://digitmind8080.cloudpub.ru/?connection_type=mysql&action=data', + 'settings' => [ + [ + 'name' => 'Host', + 'type' => 'STRING', + 'code' => 'host', + ], + [ + 'name' => 'Port', + 'type' => 'STRING', + 'code' => 'port', + ], + [ + 'name' => 'Database', + 'type' => 'STRING', + 'code' => 'database', + ], + [ + 'name' => 'Username', + 'type' => 'STRING', + 'code' => 'username', + ], + [ + 'name' => 'Password', + 'type' => 'STRING', + 'code' => 'password', + ], + ], + ]; + } + + /** + * Returns the minimum set of required fields to create a source. + */ + private function makeSourceFields(string $title): array + { + return [ + 'title' => $title, + 'connectorId' => $this->connectorId, + 'settings' => [ + 'host' => '172.18.0.2', + 'port' => '3306', + 'database' => 'customer_db', + 'username' => 'testuser', + 'password' => 'testpass123', + ], + ]; + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testAdd(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $title = 'source-' . $this->faker->uuid(); + $id = $this->sourceService->add($this->makeSourceFields($title))->getId(); + + self::assertGreaterThanOrEqual(1, $id); + + // Cleanup + $this->sourceService->delete($id); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testGet(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $title = 'source-' . $this->faker->uuid(); + $id = $this->sourceService->add($this->makeSourceFields($title))->getId(); + + $sourceItemResult = $this->sourceService->get($id)->source(); + self::assertInstanceOf(SourceItemResult::class, $sourceItemResult); + self::assertEquals($id, $sourceItemResult->id); + self::assertEquals($title, $sourceItemResult->title); + + // Cleanup + $this->sourceService->delete($id); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testList(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $title = 'source-' . $this->faker->uuid(); + $id = $this->sourceService->add($this->makeSourceFields($title))->getId(); + + $list = $this->sourceService->list()->getSources(); + self::assertIsArray($list); + self::assertGreaterThanOrEqual(1, count($list)); + + // Cleanup + $this->sourceService->delete($id); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testUpdate(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $title = 'source-' . $this->faker->uuid(); + $sourceFields = $this->makeSourceFields($title); + $id = $this->sourceService->add($sourceFields)->getId(); + + $newTitle = $title . '-updated'; + self::assertTrue( + $this->sourceService->update($id, [ + 'title' => $newTitle, + 'settings' => $sourceFields['settings'], + ])->isSuccess() + ); + + self::assertEquals($newTitle, $this->sourceService->get($id)->source()->title); + + // Cleanup + $this->sourceService->delete($id); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testDelete(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $title = 'source-' . $this->faker->uuid(); + $id = $this->sourceService->add($this->makeSourceFields($title))->getId(); + + self::assertTrue($this->sourceService->delete($id)->isSuccess()); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testFields(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $fields = $this->sourceService->fields()->getFieldsDescription(); + self::assertIsArray($fields); + self::assertNotEmpty($fields); + */ + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testCount(): void + { + // Test body is commented out: this test requires an additional external service + // (a real database accessible via the Biconnector connector). + $this->markTestSkipped('This test requires an additional external service (a real database accessible via the Biconnector connector).'); + /* + $countBefore = $this->sourceService->count(); + + $title = 'source-' . $this->faker->uuid(); + $id = $this->sourceService->add($this->makeSourceFields($title))->getId(); + + $countAfter = $this->sourceService->count(); + self::assertEquals($countBefore + 1, $countAfter); + + // Cleanup + $this->sourceService->delete($id); + */ + } +} diff --git a/tests/Unit/Services/Biconnector/BiconnectorServiceBuilderTest.php b/tests/Unit/Services/Biconnector/BiconnectorServiceBuilderTest.php new file mode 100644 index 00000000..b0884ef0 --- /dev/null +++ b/tests/Unit/Services/Biconnector/BiconnectorServiceBuilderTest.php @@ -0,0 +1,49 @@ + + * + * 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\SDK\Tests\Unit\Services\Biconnector; + +use Bitrix24\SDK\Services\Biconnector\BiconnectorServiceBuilder; +use Bitrix24\SDK\Tests\Unit\Stubs\NullBatch; +use Bitrix24\SDK\Tests\Unit\Stubs\NullBulkItemsReader; +use Bitrix24\SDK\Tests\Unit\Stubs\NullCore; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; + +#[CoversClass(BiconnectorServiceBuilder::class)] +class BiconnectorServiceBuilderTest extends TestCase +{ + private BiconnectorServiceBuilder $serviceBuilder; + + #[\Override] + protected function setUp(): void + { + $this->serviceBuilder = new BiconnectorServiceBuilder( + new NullCore(), + new NullBatch(), + new NullBulkItemsReader(), + new NullLogger() + ); + } + + public function testConnectorServiceIsCached(): void + { + $this::assertSame($this->serviceBuilder->connector(), $this->serviceBuilder->connector()); + } + + public function testSourceServiceIsCached(): void + { + $this::assertSame($this->serviceBuilder->source(), $this->serviceBuilder->source()); + } +} diff --git a/tests/Unit/Services/ServiceBuilderCacheTest.php b/tests/Unit/Services/ServiceBuilderCacheTest.php index e78b8d11..d10f84cd 100644 --- a/tests/Unit/Services/ServiceBuilderCacheTest.php +++ b/tests/Unit/Services/ServiceBuilderCacheTest.php @@ -86,4 +86,8 @@ public function testGetTelephonyBuilder(): void $this::assertSame($this->serviceBuilder->getTelephonyScope(), $this->serviceBuilder->getTelephonyScope()); } + public function testGetBiconnectorScopeBuilder(): void + { + $this::assertSame($this->serviceBuilder->getBiconnectorScope(), $this->serviceBuilder->getBiconnectorScope()); + } }