diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 1fe4bb23..206a0b30 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -33,6 +33,7 @@ ->in(__DIR__ . '/src/Services/SonetGroup/') ->in(__DIR__ . '/src/Services/MailService/') ->in(__DIR__ . '/src/Services/Messageservice/') + ->in(__DIR__ . '/src/Services/Sign/') ->name('*.php') ->exclude(['vendor', 'storage', 'docker', 'docs']) // Exclude directories ->ignoreDotFiles(true) diff --git a/.tasks/504/plan.md b/.tasks/504/plan.md new file mode 100644 index 00000000..87138f28 --- /dev/null +++ b/.tasks/504/plan.md @@ -0,0 +1,94 @@ +# Plan: Add sign.b2e.* service support (issue #504) + +## Context + +Add support for Bitrix24 Electronic Document Management (КЭДО) REST API methods under the `sign.b2e.*` scope. + +**API methods:** +- `sign.b2e.document.send` — sends a document for signing (application context only) +- `sign.b2e.document.get` — returns information about a document and its signing members +- `sign.b2e.company.provider.list` — returns list of signature providers for a company +- `sign.b2e.personal.tail` — returns signed documents list for current user (application context only) +- `sign.b2e.mysafe.tail` — returns signed documents in company safe (application context only) + +**Events:** +- `OnSignB2eDocumentStatusChanged` (code `ONSIGNB2EDOCUMENTSTATUSCHANGED`) — fires when document status changes +- `OnSignB2eMemberStatusChanged` (code `ONSIGNB2EMEMBERSTATUSCHANGED`) — fires when member status changes + +**Response shapes:** +- `document.send` / `document.get` → `result` is object with `uid`, `state`, `members` +- `company.provider.list` → `result` is direct array of provider objects +- `personal.tail` → `result` is direct array of `{id, title, signed_date, file_url}` +- `mysafe.tail` → `result` is direct array of `{id, title, create_date, signed_date, creator_id, member_id, role, file_url}` + +**Scope:** `sign.b2e` (some methods also accept `crm` scope for company access) + +**Author:** © Dmitriy Ignatenko + +--- + +## Files to Create + +### Source + +1. `src/Services/Sign/SignServiceBuilder.php` — scope service builder with `#[ApiServiceBuilderMetadata(new Scope(['sign.b2e']))]` +2. `src/Services/Sign/B2e/Document/Result/DocumentItemResult.php` — `@property-read` for `uid`, `state`, `members` +3. `src/Services/Sign/B2e/Document/Result/DocumentResult.php` — wraps `DocumentItemResult` from `result` +4. `src/Services/Sign/B2e/Document/Service/Document.php` — `send()`, `get()` methods +5. `src/Services/Sign/B2e/CompanyProvider/Result/CompanyProviderItemResult.php` — `@property-read` for `code`, `uid`, `name`, `date`, `expires` +6. `src/Services/Sign/B2e/CompanyProvider/Result/CompanyProvidersResult.php` — list result +7. `src/Services/Sign/B2e/CompanyProvider/Service/CompanyProvider.php` — `list()` method +8. `src/Services/Sign/B2e/PersonalTail/Result/PersonalTailItemResult.php` — `@property-read` for `id`, `title`, `signed_date`, `file_url` +9. `src/Services/Sign/B2e/PersonalTail/Result/PersonalTailResult.php` — list result +10. `src/Services/Sign/B2e/PersonalTail/Service/PersonalTail.php` — `tail()` method +11. `src/Services/Sign/B2e/MySafeTail/Result/MySafeTailItemResult.php` — `@property-read` for `id`, `title`, `create_date`, `signed_date`, `creator_id`, `member_id`, `role`, `file_url` +12. `src/Services/Sign/B2e/MySafeTail/Result/MySafeTailResult.php` — list result +13. `src/Services/Sign/B2e/MySafeTail/Service/MySafeTail.php` — `tail()` method +14. `src/Services/Sign/Events/SignB2eEventsFactory.php` +15. `src/Services/Sign/Events/OnSignB2eDocumentStatusChanged/OnSignB2eDocumentStatusChanged.php` +16. `src/Services/Sign/Events/OnSignB2eDocumentStatusChanged/OnSignB2eDocumentStatusChangedPayload.php` +17. `src/Services/Sign/Events/OnSignB2eMemberStatusChanged/OnSignB2eMemberStatusChanged.php` +18. `src/Services/Sign/Events/OnSignB2eMemberStatusChanged/OnSignB2eMemberStatusChangedPayload.php` + +### Tests + +19. `tests/Integration/Services/Sign/B2e/Document/Service/DocumentTest.php` +20. `tests/Integration/Services/Sign/B2e/Document/Result/DocumentItemResultAnnotationsTest.php` +21. `tests/Integration/Services/Sign/B2e/CompanyProvider/Service/CompanyProviderTest.php` +22. `tests/Integration/Services/Sign/B2e/CompanyProvider/Result/CompanyProviderItemResultAnnotationsTest.php` +23. `tests/Integration/Services/Sign/B2e/PersonalTail/Service/PersonalTailTest.php` +24. `tests/Integration/Services/Sign/B2e/PersonalTail/Result/PersonalTailItemResultAnnotationsTest.php` +25. `tests/Integration/Services/Sign/B2e/MySafeTail/Service/MySafeTailTest.php` +26. `tests/Integration/Services/Sign/B2e/MySafeTail/Result/MySafeTailItemResultAnnotationsTest.php` +27. `tests/Unit/Services/Sign/SignServiceBuilderTest.php` + +--- + +## Files to Modify + +1. `src/Services/ServiceBuilder.php` — add `getSignScope(): SignServiceBuilder` +2. `src/Services/RemoteEventsFactory.php` — register `SignB2eEventsFactory` in `init()` +3. `phpunit.xml.dist` — add test suites for sign.b2e +4. `Makefile` — add `test-integration-scope-sign` make target +5. `CHANGELOG.md` — add entry under `## 3.4.0 – UNRELEASED → ### Added` + +--- + +## Deptrac compliance + +New code lives in `src/Services/Sign/` (Services layer) and only depends on `Core` and `Application` +namespaces — no new violations introduced. + +--- + +## Verification + +```bash +make lint-cs-fixer +make lint-rector +make lint-phpstan +make lint-deptrac +make test-unit +make test-integration-scope-sign +``` + diff --git a/CHANGELOG.md b/CHANGELOG.md index ac030197..823b685c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ ### Added +- Added `Services\Sign\SignServiceBuilder` with support for `sign.b2e.*` methods and events, + see [sign.b2e.* methods](https://apidocs.bitrix24.com/api-reference/sign/index.html) ([#504](https://github.com/bitrix24/b24phpsdk/issues/504)): + - `document()->send()` — sends a document for company-side signing (КЭДО), application context only + - `document()->get()` — returns information about a document and its signing members + - `companyProvider()->list()` — returns the list of signature providers for a selected company + - `personalTail()->tail()` — returns the list of signed documents for the current user (КЭДО section), application context only + - `mySafeTail()->tail()` — returns the list of signed documents in the company safe, application context only +- Added events support for `sign.b2e` scope via `SignB2eEventsFactory` ([#504](https://github.com/bitrix24/b24phpsdk/issues/504)): + - `OnSignB2eDocumentStatusChanged` — fires when document status changes + - `OnSignB2eMemberStatusChanged` — fires when member status changes - Added services `Services\Timeman\Record\Service\Record` and `Services\Timeman\RecordField\Service\RecordField` with support for v3 `timeman.record.*` methods, see [timeman REST v3](https://apidocs.bitrix24.com/api-reference/rest-v3/timeman/index.html) ([#518](https://github.com/bitrix24/b24phpsdk/issues/518)): diff --git a/Makefile b/Makefile index 53a12d42..0ec7a117 100644 --- a/Makefile +++ b/Makefile @@ -103,6 +103,11 @@ help: @echo "test-integration-lists-section - run Lists Section integration tests" @echo "test-integration-lists-element - run Lists Element integration tests" @echo "test-integration-mailservice - run MailService integration tests" + @echo "test-integration-scope-sign - run Sign B2e integration tests" + @echo "test-integration-sign-document - run Sign Document integration tests" + @echo "test-integration-sign-company-provider - run Sign CompanyProvider integration tests" + @echo "test-integration-sign-personal-tail - run Sign PersonalTail integration tests" + @echo "test-integration-sign-mysafe-tail - run Sign MySafeTail integration tests" t: docker compose run --rm php-cli sh @@ -747,6 +752,26 @@ test-integration-messageservice-sender: test-integration-messageservice-message-status: docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_messageservice_message_status +.PHONY: test-integration-scope-sign +test-integration-scope-sign: + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_scope_sign + +.PHONY: test-integration-sign-document +test-integration-sign-document: + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_sign_document + +.PHONY: test-integration-sign-company-provider +test-integration-sign-company-provider: + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_sign_company_provider + +.PHONY: test-integration-sign-personal-tail +test-integration-sign-personal-tail: + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_sign_personal_tail + +.PHONY: test-integration-sign-mysafe-tail +test-integration-sign-mysafe-tail: + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_sign_mysafe_tail + # work dev environment .PHONY: php-dev-server-up php-dev-server-up: diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 5a92c3a6..37dd9b63 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -43,6 +43,7 @@ parameters: - tests/Integration/Services/Timeman - tests/Integration/Services/MailService - tests/Integration/Services/Messageservice + - tests/Integration/Services/Sign excludePaths: # TODO: Fix type errors in RequisiteUserfieldUseCaseTest and remove this exclusion # Tracking: https://github.com/bitrix24/b24phpsdk/issues diff --git a/phpunit.xml.dist b/phpunit.xml.dist index a0fbb94a..377ff04a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -432,6 +432,25 @@ ./tests/Integration/Services/Messageservice/Message/Status/ + + ./tests/Integration/Services/Sign/ + + + ./tests/Integration/Services/Sign/B2e/Document/Service/ + ./tests/Integration/Services/Sign/B2e/Document/Result/ + + + ./tests/Integration/Services/Sign/B2e/CompanyProvider/Service/ + ./tests/Integration/Services/Sign/B2e/CompanyProvider/Result/ + + + ./tests/Integration/Services/Sign/B2e/PersonalTail/Service/ + ./tests/Integration/Services/Sign/B2e/PersonalTail/Result/ + + + ./tests/Integration/Services/Sign/B2e/MySafeTail/Service/ + ./tests/Integration/Services/Sign/B2e/MySafeTail/Result/ + diff --git a/rector.php b/rector.php index fbee556c..d2e21ef9 100644 --- a/rector.php +++ b/rector.php @@ -91,6 +91,8 @@ __DIR__ . '/tests/Unit/', __DIR__ . '/src/Services/Timeman', __DIR__ . '/tests/Integration/Services/Timeman', + __DIR__ . '/src/Services/Sign', + __DIR__ . '/tests/Integration/Services/Sign', ]) ->withCache(cacheDirectory: __DIR__ . '/var/.cache/rector') ->withSets( diff --git a/src/Services/RemoteEventsFactory.php b/src/Services/RemoteEventsFactory.php index 112159f4..c5da7302 100644 --- a/src/Services/RemoteEventsFactory.php +++ b/src/Services/RemoteEventsFactory.php @@ -29,6 +29,7 @@ use Bitrix24\SDK\Services\IMOpenLines\Events\IMOpenLinesEventsFactory; use Bitrix24\SDK\Services\SonetGroup\Events\SonetGroupEventsFactory; use Bitrix24\SDK\Services\Telephony\Events\TelephonyEventsFactory; +use Bitrix24\SDK\Services\Sign\Events\SignB2eEventsFactory; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; @@ -81,9 +82,9 @@ public function create(Request $request): EventInterface } $event = new UnsupportedRemoteEvent($request); - foreach ($this->eventsFabrics as $itemFabric) { - if ($itemFabric->isSupport($payload['event'])) { - $event = $itemFabric->create($request); + foreach ($this->eventsFabrics as $eventFabric) { + if ($eventFabric->isSupport($payload['event'])) { + $event = $eventFabric->create($request); break; } } @@ -155,6 +156,7 @@ public function createEvent(Request $request, ?string $applicationToken): EventI ]); throw new InvalidArgumentException('key «event» not found in request payload'); } + if ($applicationToken !== null && trim($applicationToken) === '') { throw new InvalidArgumentException('application token cannot be empty'); } @@ -166,9 +168,9 @@ public function createEvent(Request $request, ?string $applicationToken): EventI ]); $event = new UnsupportedRemoteEvent($request); - foreach ($this->eventsFabrics as $itemFabric) { - if ($itemFabric->isSupport($eventCode)) { - $event = $itemFabric->create($request); + foreach ($this->eventsFabrics as $eventFabric) { + if ($eventFabric->isSupport($eventCode)) { + $event = $eventFabric->create($request); break; } } @@ -225,6 +227,7 @@ public static function init(LoggerInterface $logger): self new IMOpenLinesEventsFactory(), new SonetGroupEventsFactory(), new Sale\Events\SaleEventsFactory(), + new SignB2eEventsFactory(), ], $logger ); diff --git a/src/Services/ServiceBuilder.php b/src/Services/ServiceBuilder.php index 04cced9e..371d14e9 100644 --- a/src/Services/ServiceBuilder.php +++ b/src/Services/ServiceBuilder.php @@ -45,6 +45,7 @@ use Bitrix24\SDK\Services\MailService\MailServiceServiceBuilder; use Bitrix24\SDK\Services\Messageservice\MessageserviceServiceBuilder; use Bitrix24\SDK\Services\Rest\RestServiceBuilder; +use Bitrix24\SDK\Services\Sign\SignServiceBuilder; use Bitrix24\SDK\Services\Timeman\TimemanServiceBuilder; use Psr\Log\LoggerInterface; @@ -485,4 +486,18 @@ public function getTimemanScope(): TimemanServiceBuilder return $this->serviceCache[__METHOD__]; } + + public function getSignScope(): SignServiceBuilder + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new SignServiceBuilder( + $this->core, + $this->batch, + $this->bulkItemsReader, + $this->log + ); + } + + return $this->serviceCache[__METHOD__]; + } } diff --git a/src/Services/Sign/B2e/CompanyProvider/Result/CompanyProviderItemResult.php b/src/Services/Sign/B2e/CompanyProvider/Result/CompanyProviderItemResult.php new file mode 100644 index 00000000..b40e9045 --- /dev/null +++ b/src/Services/Sign/B2e/CompanyProvider/Result/CompanyProviderItemResult.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\Sign\B2e\CompanyProvider\Result; + +use Bitrix24\SDK\Core\Result\AbstractAnnotatedItem; +use Carbon\CarbonImmutable; + +/** + * @property-read string|null $code Signature provider code + * @property-read string|null $uid Unique identifier of the provider + * @property-read string|null $name Provider display name + * @property-read CarbonImmutable|null $date Provider activation date + * @property-read CarbonImmutable|null $expires Provider expiration date + */ +class CompanyProviderItemResult extends AbstractAnnotatedItem +{ +} diff --git a/src/Services/Sign/B2e/CompanyProvider/Result/CompanyProvidersResult.php b/src/Services/Sign/B2e/CompanyProvider/Result/CompanyProvidersResult.php new file mode 100644 index 00000000..d1ca4c6f --- /dev/null +++ b/src/Services/Sign/B2e/CompanyProvider/Result/CompanyProvidersResult.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\Sign\B2e\CompanyProvider\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class CompanyProvidersResult extends AbstractResult +{ + /** + * @return CompanyProviderItemResult[] + * @throws BaseException + */ + public function getProviders(): array + { + $items = []; + foreach ($this->getCoreResponse()->getResponseData()->getResult() as $item) { + $items[] = new CompanyProviderItemResult($item); + } + + return $items; + } +} diff --git a/src/Services/Sign/B2e/CompanyProvider/Service/CompanyProvider.php b/src/Services/Sign/B2e/CompanyProvider/Service/CompanyProvider.php new file mode 100644 index 00000000..2f85cd53 --- /dev/null +++ b/src/Services/Sign/B2e/CompanyProvider/Service/CompanyProvider.php @@ -0,0 +1,78 @@ + + * + * 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\Sign\B2e\CompanyProvider\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\Services\AbstractService; +use Bitrix24\SDK\Services\Sign\B2e\CompanyProvider\Result\CompanyProvidersResult; +use Psr\Log\LoggerInterface; + +#[ApiServiceMetadata(new Scope(['sign.b2e']))] +class CompanyProvider extends AbstractService +{ + public function __construct(CoreInterface $core, LoggerInterface $logger) + { + parent::__construct($core, $logger); + } + + /** + * Returns the list of signature providers for a selected company. + * + * @link https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-company-provider-list.html + * + * @param string|null $companyUuid Company UUID in HCM Link (required if companyCrmId is not provided) + * @param int|null $companyCrmId Company CRM identifier (required if companyUuid is not provided) + * @param string|null $language Language for provider name localisation (default: en) + * @param int $limit Number of records per page (1–1000, default: 100) + * @param int $offset Pagination offset (default: 0) + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'sign.b2e.company.provider.list', + 'https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-company-provider-list.html', + 'Returns the list of signature providers for a selected company.' + )] + public function list( + ?string $companyUuid = null, + ?int $companyCrmId = null, + ?string $language = null, + int $limit = 100, + int $offset = 0 + ): CompanyProvidersResult { + $params = [ + 'limit' => $limit, + 'offset' => $offset, + ]; + if ($companyUuid !== null) { + $params['companyUuid'] = $companyUuid; + } + + if ($companyCrmId !== null) { + $params['companyCrmId'] = $companyCrmId; + } + + if ($language !== null) { + $params['language'] = $language; + } + + return new CompanyProvidersResult($this->core->call('sign.b2e.company.provider.list', $params)); + } +} diff --git a/src/Services/Sign/B2e/Document/Result/DocumentItemResult.php b/src/Services/Sign/B2e/Document/Result/DocumentItemResult.php new file mode 100644 index 00000000..aa3a300e --- /dev/null +++ b/src/Services/Sign/B2e/Document/Result/DocumentItemResult.php @@ -0,0 +1,25 @@ + + * + * 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\Sign\B2e\Document\Result; + +use Bitrix24\SDK\Core\Result\AbstractAnnotatedItem; + +/** + * @property-read string|null $uid Unique identifier of the document + * @property-read array|null $state Document status with 'code' and 'name' keys + * @property-read array>|null $members List of signing members + */ +class DocumentItemResult extends AbstractAnnotatedItem +{ +} diff --git a/src/Services/Sign/B2e/Document/Result/DocumentResult.php b/src/Services/Sign/B2e/Document/Result/DocumentResult.php new file mode 100644 index 00000000..56ab2da7 --- /dev/null +++ b/src/Services/Sign/B2e/Document/Result/DocumentResult.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\Sign\B2e\Document\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class DocumentResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function getDocument(): DocumentItemResult + { + return new DocumentItemResult($this->getCoreResponse()->getResponseData()->getResult()); + } +} diff --git a/src/Services/Sign/B2e/Document/Service/Document.php b/src/Services/Sign/B2e/Document/Service/Document.php new file mode 100644 index 00000000..28bede8e --- /dev/null +++ b/src/Services/Sign/B2e/Document/Service/Document.php @@ -0,0 +1,94 @@ + + * + * 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\Sign\B2e\Document\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\Services\AbstractService; +use Bitrix24\SDK\Services\Sign\B2e\Document\Result\DocumentResult; +use Psr\Log\LoggerInterface; + +#[ApiServiceMetadata(new Scope(['sign.b2e']))] +class Document extends AbstractService +{ + public function __construct(CoreInterface $core, LoggerInterface $logger) + { + parent::__construct($core, $logger); + } + + /** + * Sends a document for company-side signing (КЭДО). + * + * @link https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-document-send.html + * + * @param array{ + * company?: array, + * members?: array, + * responsible?: array{userId: int}, + * companyProviderUid?: string, + * files?: array, + * regionDocumentType?: string, + * externalSettings?: array, + * language?: string + * } $fields Document fields + * @param string|null $language Language for status localisation in response (default: en) + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'sign.b2e.document.send', + 'https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-document-send.html', + 'Sends a document for company-side signing (КЭДО). Requires application context.' + )] + public function send(array $fields, ?string $language = null): DocumentResult + { + $params = ['fields' => $fields]; + if ($language !== null) { + $params['language'] = $language; + } + + return new DocumentResult($this->core->call('sign.b2e.document.send', $params)); + } + + /** + * Returns information about a document and its signing members. + * + * @link https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-document-get.html + * + * @param string $uid Unique document identifier + * @param string|null $language Language for status localisation in response (default: en) + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'sign.b2e.document.get', + 'https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-document-get.html', + 'Returns information about a document and its signing members.' + )] + public function get(string $uid, ?string $language = null): DocumentResult + { + $params = ['uid' => $uid]; + if ($language !== null) { + $params['language'] = $language; + } + + return new DocumentResult($this->core->call('sign.b2e.document.get', $params)); + } +} diff --git a/src/Services/Sign/B2e/MySafeTail/Result/MySafeTailItemResult.php b/src/Services/Sign/B2e/MySafeTail/Result/MySafeTailItemResult.php new file mode 100644 index 00000000..23de29b0 --- /dev/null +++ b/src/Services/Sign/B2e/MySafeTail/Result/MySafeTailItemResult.php @@ -0,0 +1,31 @@ + + * + * 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\Sign\B2e\MySafeTail\Result; + +use Bitrix24\SDK\Core\Result\AbstractAnnotatedItem; +use Carbon\CarbonImmutable; + +/** + * @property-read int|null $id Signed document identifier + * @property-read string|null $title Document title + * @property-read CarbonImmutable|null $create_date Document creation date + * @property-read CarbonImmutable|null $signed_date Document signing date + * @property-read int|null $creator_id User ID who created the document + * @property-read int|null $member_id User ID of the signing party + * @property-read string|null $role Employee role in the document: editor, reviewer, assignee, signer + * @property-read string|null $file_url Download URL for the signed document + */ +class MySafeTailItemResult extends AbstractAnnotatedItem +{ +} diff --git a/src/Services/Sign/B2e/MySafeTail/Result/MySafeTailResult.php b/src/Services/Sign/B2e/MySafeTail/Result/MySafeTailResult.php new file mode 100644 index 00000000..696c0d90 --- /dev/null +++ b/src/Services/Sign/B2e/MySafeTail/Result/MySafeTailResult.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\Sign\B2e\MySafeTail\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class MySafeTailResult extends AbstractResult +{ + /** + * @return MySafeTailItemResult[] + * @throws BaseException + */ + public function getItems(): array + { + $items = []; + foreach ($this->getCoreResponse()->getResponseData()->getResult() as $item) { + $items[] = new MySafeTailItemResult($item); + } + + return $items; + } +} diff --git a/src/Services/Sign/B2e/MySafeTail/Service/MySafeTail.php b/src/Services/Sign/B2e/MySafeTail/Service/MySafeTail.php new file mode 100644 index 00000000..ad623beb --- /dev/null +++ b/src/Services/Sign/B2e/MySafeTail/Service/MySafeTail.php @@ -0,0 +1,59 @@ + + * + * 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\Sign\B2e\MySafeTail\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\Services\AbstractService; +use Bitrix24\SDK\Services\Sign\B2e\MySafeTail\Result\MySafeTailResult; +use Psr\Log\LoggerInterface; + +#[ApiServiceMetadata(new Scope(['sign.b2e']))] +class MySafeTail extends AbstractService +{ + public function __construct(CoreInterface $core, LoggerInterface $logger) + { + parent::__construct($core, $logger); + } + + /** + * Returns the list of signed documents in the company safe. + * + * @link https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-mysafe-tail.html + * + * @param int $limit Number of records per page (1–50, default: 20) + * @param int $offset Pagination offset (default: 0) + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'sign.b2e.mysafe.tail', + 'https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-mysafe-tail.html', + 'Returns the list of signed documents in the company safe. Requires application context.' + )] + public function tail(int $limit = 20, int $offset = 0): MySafeTailResult + { + return new MySafeTailResult( + $this->core->call('sign.b2e.mysafe.tail', [ + 'limit' => $limit, + 'offset' => $offset, + ]) + ); + } +} diff --git a/src/Services/Sign/B2e/PersonalTail/Result/PersonalTailItemResult.php b/src/Services/Sign/B2e/PersonalTail/Result/PersonalTailItemResult.php new file mode 100644 index 00000000..0735014a --- /dev/null +++ b/src/Services/Sign/B2e/PersonalTail/Result/PersonalTailItemResult.php @@ -0,0 +1,27 @@ + + * + * 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\Sign\B2e\PersonalTail\Result; + +use Bitrix24\SDK\Core\Result\AbstractAnnotatedItem; +use Carbon\CarbonImmutable; + +/** + * @property-read int|null $id Signed document identifier + * @property-read string|null $title Document title + * @property-read CarbonImmutable|null $signed_date Document signing date + * @property-read string|null $file_url Download URL for the signed document + */ +class PersonalTailItemResult extends AbstractAnnotatedItem +{ +} diff --git a/src/Services/Sign/B2e/PersonalTail/Result/PersonalTailResult.php b/src/Services/Sign/B2e/PersonalTail/Result/PersonalTailResult.php new file mode 100644 index 00000000..1254b400 --- /dev/null +++ b/src/Services/Sign/B2e/PersonalTail/Result/PersonalTailResult.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\Sign\B2e\PersonalTail\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class PersonalTailResult extends AbstractResult +{ + /** + * @return PersonalTailItemResult[] + * @throws BaseException + */ + public function getItems(): array + { + $items = []; + foreach ($this->getCoreResponse()->getResponseData()->getResult() as $item) { + $items[] = new PersonalTailItemResult($item); + } + + return $items; + } +} diff --git a/src/Services/Sign/B2e/PersonalTail/Service/PersonalTail.php b/src/Services/Sign/B2e/PersonalTail/Service/PersonalTail.php new file mode 100644 index 00000000..092416af --- /dev/null +++ b/src/Services/Sign/B2e/PersonalTail/Service/PersonalTail.php @@ -0,0 +1,59 @@ + + * + * 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\Sign\B2e\PersonalTail\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\Services\AbstractService; +use Bitrix24\SDK\Services\Sign\B2e\PersonalTail\Result\PersonalTailResult; +use Psr\Log\LoggerInterface; + +#[ApiServiceMetadata(new Scope(['sign.b2e']))] +class PersonalTail extends AbstractService +{ + public function __construct(CoreInterface $core, LoggerInterface $logger) + { + parent::__construct($core, $logger); + } + + /** + * Returns the list of signed documents for the current user from the КЭДО section. + * + * @link https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-personal-tail.html + * + * @param int $limit Number of records per page (1–50, default: 20) + * @param int $offset Pagination offset (default: 0) + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'sign.b2e.personal.tail', + 'https://apidocs.bitrix24.com/api-reference/sign/sign-b2e-personal-tail.html', + 'Returns the list of signed documents for the current user from the КЭДО section. Requires application context.' + )] + public function tail(int $limit = 20, int $offset = 0): PersonalTailResult + { + return new PersonalTailResult( + $this->core->call('sign.b2e.personal.tail', [ + 'limit' => $limit, + 'offset' => $offset, + ]) + ); + } +} diff --git a/src/Services/Sign/Events/OnSignB2eDocumentStatusChanged/OnSignB2eDocumentStatusChanged.php b/src/Services/Sign/Events/OnSignB2eDocumentStatusChanged/OnSignB2eDocumentStatusChanged.php new file mode 100644 index 00000000..25b6e451 --- /dev/null +++ b/src/Services/Sign/Events/OnSignB2eDocumentStatusChanged/OnSignB2eDocumentStatusChanged.php @@ -0,0 +1,31 @@ + + * + * 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\Sign\Events\OnSignB2eDocumentStatusChanged; + +use Bitrix24\SDK\Application\Requests\Events\AbstractEventRequest; + +/** + * Event fired when the status of a sign.b2e document changes. + * + * @link https://apidocs.bitrix24.com/api-reference/sign/events/on-sign-b2e-document-status-changed.html + */ +class OnSignB2eDocumentStatusChanged extends AbstractEventRequest +{ + public const CODE = 'ONSIGNB2EDOCUMENTSTATUSCHANGED'; + + public function getPayload(): OnSignB2eDocumentStatusChangedPayload + { + return new OnSignB2eDocumentStatusChangedPayload($this->eventPayload['data']); + } +} diff --git a/src/Services/Sign/Events/OnSignB2eDocumentStatusChanged/OnSignB2eDocumentStatusChangedPayload.php b/src/Services/Sign/Events/OnSignB2eDocumentStatusChanged/OnSignB2eDocumentStatusChangedPayload.php new file mode 100644 index 00000000..9df3e38e --- /dev/null +++ b/src/Services/Sign/Events/OnSignB2eDocumentStatusChanged/OnSignB2eDocumentStatusChangedPayload.php @@ -0,0 +1,26 @@ + + * + * 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\Sign\Events\OnSignB2eDocumentStatusChanged; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * @property-read string $documentUid Unique document identifier + * @property-read string|null $companyUid Unique company identifier (present when a company integration exists) + * @property-read string $statusCode Document status code + * @property-read string $statusName Document status name + */ +class OnSignB2eDocumentStatusChangedPayload extends AbstractItem +{ +} diff --git a/src/Services/Sign/Events/OnSignB2eMemberStatusChanged/OnSignB2eMemberStatusChanged.php b/src/Services/Sign/Events/OnSignB2eMemberStatusChanged/OnSignB2eMemberStatusChanged.php new file mode 100644 index 00000000..e54cabfc --- /dev/null +++ b/src/Services/Sign/Events/OnSignB2eMemberStatusChanged/OnSignB2eMemberStatusChanged.php @@ -0,0 +1,31 @@ + + * + * 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\Sign\Events\OnSignB2eMemberStatusChanged; + +use Bitrix24\SDK\Application\Requests\Events\AbstractEventRequest; + +/** + * Event fired when the status of a sign.b2e document member changes. + * + * @link https://apidocs.bitrix24.com/api-reference/sign/events/on-sign-b2e-member-status-changed.html + */ +class OnSignB2eMemberStatusChanged extends AbstractEventRequest +{ + public const CODE = 'ONSIGNB2EMEMBERSTATUSCHANGED'; + + public function getPayload(): OnSignB2eMemberStatusChangedPayload + { + return new OnSignB2eMemberStatusChangedPayload($this->eventPayload['data']); + } +} diff --git a/src/Services/Sign/Events/OnSignB2eMemberStatusChanged/OnSignB2eMemberStatusChangedPayload.php b/src/Services/Sign/Events/OnSignB2eMemberStatusChanged/OnSignB2eMemberStatusChangedPayload.php new file mode 100644 index 00000000..76ede3e0 --- /dev/null +++ b/src/Services/Sign/Events/OnSignB2eMemberStatusChanged/OnSignB2eMemberStatusChangedPayload.php @@ -0,0 +1,27 @@ + + * + * 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\Sign\Events\OnSignB2eMemberStatusChanged; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * @property-read string $memberUid Unique member identifier + * @property-read string $documentUid Unique document identifier + * @property-read string|null $companyUid Unique company identifier (present when a company integration exists) + * @property-read string $statusCode Member status code + * @property-read string $statusName Member status name + */ +class OnSignB2eMemberStatusChangedPayload extends AbstractItem +{ +} diff --git a/src/Services/Sign/Events/SignB2eEventsFactory.php b/src/Services/Sign/Events/SignB2eEventsFactory.php new file mode 100644 index 00000000..d4eb11b0 --- /dev/null +++ b/src/Services/Sign/Events/SignB2eEventsFactory.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\Sign\Events; + +use Bitrix24\SDK\Core\Contracts\Events\EventInterface; +use Bitrix24\SDK\Core\Contracts\Events\EventsFabricInterface; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\SDK\Services\Sign\Events\OnSignB2eDocumentStatusChanged\OnSignB2eDocumentStatusChanged; +use Bitrix24\SDK\Services\Sign\Events\OnSignB2eMemberStatusChanged\OnSignB2eMemberStatusChanged; +use Symfony\Component\HttpFoundation\Request; + +readonly class SignB2eEventsFactory implements EventsFabricInterface +{ + #[\Override] + public function isSupport(string $eventCode): bool + { + return in_array(strtoupper($eventCode), [ + OnSignB2eDocumentStatusChanged::CODE, + OnSignB2eMemberStatusChanged::CODE, + ], true); + } + + /** + * @throws InvalidArgumentException + */ + #[\Override] + public function create(Request $eventRequest): EventInterface + { + $eventPayload = $eventRequest->request->all(); + if (!array_key_exists('event', $eventPayload)) { + throw new InvalidArgumentException('«event» key not found in event payload'); + } + + $eventCode = strtoupper((string) $eventPayload['event']); + + return match ($eventCode) { + OnSignB2eDocumentStatusChanged::CODE => new OnSignB2eDocumentStatusChanged($eventRequest), + OnSignB2eMemberStatusChanged::CODE => new OnSignB2eMemberStatusChanged($eventRequest), + default => throw new InvalidArgumentException( + sprintf('Unexpected event code «%s»', $eventCode) + ), + }; + } +} diff --git a/src/Services/Sign/SignServiceBuilder.php b/src/Services/Sign/SignServiceBuilder.php new file mode 100644 index 00000000..556d73f1 --- /dev/null +++ b/src/Services/Sign/SignServiceBuilder.php @@ -0,0 +1,74 @@ + + * + * 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\Sign; + +use Bitrix24\SDK\Attributes\ApiServiceBuilderMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Services\AbstractServiceBuilder; +use Bitrix24\SDK\Services\Sign\B2e\CompanyProvider\Service\CompanyProvider; +use Bitrix24\SDK\Services\Sign\B2e\Document\Service\Document; +use Bitrix24\SDK\Services\Sign\B2e\MySafeTail\Service\MySafeTail; +use Bitrix24\SDK\Services\Sign\B2e\PersonalTail\Service\PersonalTail; + +#[ApiServiceBuilderMetadata(new Scope(['sign.b2e']))] +class SignServiceBuilder extends AbstractServiceBuilder +{ + /** + * Document service for sign.b2e.document.* methods. + */ + public function document(): Document + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Document($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + /** + * Company provider service for sign.b2e.company.provider.* methods. + */ + public function companyProvider(): CompanyProvider + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new CompanyProvider($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + /** + * Personal tail service for sign.b2e.personal.tail method. + */ + public function personalTail(): PersonalTail + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new PersonalTail($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + /** + * My safe tail service for sign.b2e.mysafe.tail method. + */ + public function mySafeTail(): MySafeTail + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new MySafeTail($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } +} diff --git a/tests/.env b/tests/.env index 6335d1ec..6bfc6bf1 100644 --- a/tests/.env +++ b/tests/.env @@ -17,12 +17,20 @@ APP_ENV=dev BITRIX24_WEBHOOK= + # monolog log level INTEGRATION_TEST_LOG_LEVEL=100 LOGS_FILE=sdk.log + # integration tests assets INTEGRATION_TEST_OPEN_LINE_CODE=40863c519996e505b5cde98749c97413 +# sign.b2e integration test settings — override in tests/.env.local +# Set companyCrmId of a company with Electronic Personnel Document Management integration to enable CompanyProvider tests +SIGN_B2E_COMPANY_CRM_ID= +# Set a valid document UID from sign.b2e to enable Document.get annotation tests +SIGN_B2E_DOCUMENT_UID= + DOCUMENTATION_DEFAULT_TARGET_BRANCH= DOCUMENTATION_OPEN_AI_API_KEY= -DOCUMENTATION_REPOSITORY_FOLDER= \ No newline at end of file +DOCUMENTATION_REPOSITORY_FOLDER= diff --git a/tests/Integration/Services/Sign/B2e/CompanyProvider/Result/CompanyProviderItemResultAnnotationsTest.php b/tests/Integration/Services/Sign/B2e/CompanyProvider/Result/CompanyProviderItemResultAnnotationsTest.php new file mode 100644 index 00000000..bb8e4812 --- /dev/null +++ b/tests/Integration/Services/Sign/B2e/CompanyProvider/Result/CompanyProviderItemResultAnnotationsTest.php @@ -0,0 +1,115 @@ + + * + * 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\Sign\B2e\CompanyProvider\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Sign\B2e\CompanyProvider\Result\CompanyProviderItemResult; +use Bitrix24\SDK\Services\Sign\B2e\CompanyProvider\Service\CompanyProvider; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Factory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(CompanyProviderItemResult::class)] +class CompanyProviderItemResultAnnotationsTest extends TestCase +{ + use CustomBitrix24Assertions; + + private CompanyProvider $companyProviderService; + + private ?int $companyCrmId = null; + + #[\Override] + protected function setUp(): void + { + $this->companyProviderService = Factory::getServiceBuilder(true)->getSignScope()->companyProvider(); + + $value = $_ENV['SIGN_B2E_COMPANY_CRM_ID'] ?? ''; + if ($value !== '') { + $this->companyCrmId = (int) $value; + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('testAllSystemFieldsAnnotated: all fields in CompanyProviderItemResult are annotated in phpdoc and match raw API response')] + public function testAllSystemFieldsAnnotated(): void + { + if ($this->companyCrmId === null) { + $this->markTestSkipped( + 'Set SIGN_B2E_COMPANY_CRM_ID in tests/.env.local to enable this test.' + ); + } + + $rawItems = $this->companyProviderService->list(null, $this->companyCrmId) + ->getCoreResponse()->getResponseData()->getResult(); + + if ($rawItems === []) { + $this->markTestSkipped( + 'No signature providers found for this company — cannot verify annotation completeness against live API data.' + ); + } + + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys($rawItems[0]), + CompanyProviderItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('testAllSystemFieldsHasValidTypeAnnotation: all fields in CompanyProviderItemResult have valid type annotations')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + if ($this->companyCrmId === null) { + $this->markTestSkipped( + 'Set SIGN_B2E_COMPANY_CRM_ID in tests/.env.local to enable this test.' + ); + } + + $rawItems = $this->companyProviderService->list(null, $this->companyCrmId) + ->getCoreResponse()->getResponseData()->getResult(); + + if ($rawItems === []) { + $this->markTestSkipped( + 'No signature providers found for this company — cannot verify type annotations against live API data.' + ); + } + + $fieldTypesMap = []; + foreach (array_keys($rawItems[0]) as $fieldCode) { + $fieldTypesMap[$fieldCode] = match ($fieldCode) { + 'code', 'uid', 'name' => ['type' => 'string'], + 'date', 'expires' => ['type' => 'datetime'], + default => throw new \RuntimeException( + sprintf('Unknown field «%s» in sign.b2e.company.provider.list response — update the type map.', $fieldCode) + ), + }; + } + + $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation( + $fieldTypesMap, + CompanyProviderItemResult::class + ); + } +} diff --git a/tests/Integration/Services/Sign/B2e/CompanyProvider/Service/CompanyProviderTest.php b/tests/Integration/Services/Sign/B2e/CompanyProvider/Service/CompanyProviderTest.php new file mode 100644 index 00000000..b77988da --- /dev/null +++ b/tests/Integration/Services/Sign/B2e/CompanyProvider/Service/CompanyProviderTest.php @@ -0,0 +1,109 @@ + + * + * 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\Sign\B2e\CompanyProvider\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Sign\B2e\CompanyProvider\Result\CompanyProviderItemResult; +use Bitrix24\SDK\Services\Sign\B2e\CompanyProvider\Result\CompanyProvidersResult; +use Bitrix24\SDK\Services\Sign\B2e\CompanyProvider\Service\CompanyProvider; +use Bitrix24\SDK\Tests\Integration\Factory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(CompanyProvider::class)] +class CompanyProviderTest extends TestCase +{ + private CompanyProvider $companyProviderService; + + private ?int $companyCrmId = null; + + #[\Override] + protected function setUp(): void + { + $this->companyProviderService = Factory::getServiceBuilder(true)->getSignScope()->companyProvider(); + + $value = $_ENV['SIGN_B2E_COMPANY_CRM_ID'] ?? ''; + if ($value !== '') { + $this->companyCrmId = (int) $value; + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('sign.b2e.company.provider.list returns CompanyProvidersResult with items array')] + public function testListReturnsCompanyProvidersResult(): void + { + if ($this->companyCrmId === null) { + $this->markTestSkipped( + 'Set SIGN_B2E_COMPANY_CRM_ID in tests/.env.local to enable this test.' + ); + } + + $companyProvidersResult = $this->companyProviderService->list(null, $this->companyCrmId); + + self::assertInstanceOf(CompanyProvidersResult::class, $companyProvidersResult); + self::assertIsArray($companyProvidersResult->getProviders()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('sign.b2e.company.provider.list items are CompanyProviderItemResult instances when not empty')] + public function testListItemsAreCompanyProviderItemResults(): void + { + if ($this->companyCrmId === null) { + $this->markTestSkipped( + 'Set SIGN_B2E_COMPANY_CRM_ID in tests/.env.local to enable this test.' + ); + } + + $providers = $this->companyProviderService->list(null, $this->companyCrmId)->getProviders(); + + if ($providers === []) { + $this->markTestSkipped('No signature providers found for this company — cannot verify item type.'); + } + + self::assertInstanceOf(CompanyProviderItemResult::class, $providers[0]); + self::assertIsString($providers[0]->code); + self::assertIsString($providers[0]->uid); + self::assertIsString($providers[0]->name); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('sign.b2e.company.provider.list respects limit parameter')] + public function testListRespectsLimitParameter(): void + { + if ($this->companyCrmId === null) { + $this->markTestSkipped( + 'Set SIGN_B2E_COMPANY_CRM_ID in tests/.env.local to enable this test.' + ); + } + + $providers = $this->companyProviderService->list(null, $this->companyCrmId, null, 1, 0)->getProviders(); + + self::assertLessThanOrEqual(1, count($providers)); + } +} diff --git a/tests/Integration/Services/Sign/B2e/Document/Result/DocumentItemResultAnnotationsTest.php b/tests/Integration/Services/Sign/B2e/Document/Result/DocumentItemResultAnnotationsTest.php new file mode 100644 index 00000000..eadc9e84 --- /dev/null +++ b/tests/Integration/Services/Sign/B2e/Document/Result/DocumentItemResultAnnotationsTest.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\Tests\Integration\Services\Sign\B2e\Document\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Sign\B2e\Document\Result\DocumentItemResult; +use Bitrix24\SDK\Services\Sign\B2e\Document\Service\Document; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Factory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(DocumentItemResult::class)] +class DocumentItemResultAnnotationsTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Document $documentService; + + private ?string $documentUid = null; + + #[\Override] + protected function setUp(): void + { + $this->documentService = Factory::getServiceBuilder(true)->getSignScope()->document(); + + $value = $_ENV['SIGN_B2E_DOCUMENT_UID'] ?? ''; + if ($value !== '') { + $this->documentUid = $value; + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('testAllSystemFieldsAnnotated: all fields in DocumentItemResult are annotated in phpdoc and match raw API response')] + public function testAllSystemFieldsAnnotated(): void + { + if ($this->documentUid === null) { + $this->markTestSkipped( + 'Set SIGN_B2E_DOCUMENT_UID in tests/.env.local to enable this test.' + ); + } + + $rawResult = $this->documentService->get($this->documentUid) + ->getCoreResponse()->getResponseData()->getResult(); + + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys($rawResult), + DocumentItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('testAllSystemFieldsHasValidTypeAnnotation: all fields in DocumentItemResult have valid type annotations')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + if ($this->documentUid === null) { + $this->markTestSkipped( + 'Set SIGN_B2E_DOCUMENT_UID in tests/.env.local to enable this test.' + ); + } + + $rawResult = $this->documentService->get($this->documentUid) + ->getCoreResponse()->getResponseData()->getResult(); + + $fieldTypesMap = []; + foreach (array_keys($rawResult) as $fieldCode) { + $fieldTypesMap[$fieldCode] = match ($fieldCode) { + 'uid' => ['type' => 'string'], + 'state', 'members' => ['type' => 'array'], + default => throw new \RuntimeException( + sprintf('Unknown field «%s» in sign.b2e.document.get response — update the type map.', $fieldCode) + ), + }; + } + + $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation( + $fieldTypesMap, + DocumentItemResult::class + ); + } +} diff --git a/tests/Integration/Services/Sign/B2e/Document/Service/DocumentTest.php b/tests/Integration/Services/Sign/B2e/Document/Service/DocumentTest.php new file mode 100644 index 00000000..becf3b2d --- /dev/null +++ b/tests/Integration/Services/Sign/B2e/Document/Service/DocumentTest.php @@ -0,0 +1,96 @@ + + * + * 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\Sign\B2e\Document\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Sign\B2e\Document\Result\DocumentItemResult; +use Bitrix24\SDK\Services\Sign\B2e\Document\Result\DocumentResult; +use Bitrix24\SDK\Services\Sign\B2e\Document\Service\Document; +use Bitrix24\SDK\Tests\Integration\Factory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Document::class)] +class DocumentTest extends TestCase +{ + private Document $documentService; + + private ?string $documentUid = null; + + #[\Override] + protected function setUp(): void + { + $this->documentService = Factory::getServiceBuilder(true)->getSignScope()->document(); + + $value = $_ENV['SIGN_B2E_DOCUMENT_UID'] ?? ''; + if ($value !== '') { + $this->documentUid = $value; + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('sign.b2e.document.get returns DocumentResult for a known document UID')] + public function testGetReturnsDocumentResult(): void + { + if ($this->documentUid === null) { + $this->markTestSkipped( + 'Set SIGN_B2E_DOCUMENT_UID in tests/.env.local to enable this test.' + ); + } + + $documentResult = $this->documentService->get($this->documentUid); + + self::assertInstanceOf(DocumentResult::class, $documentResult); + + $documentItemResult = $documentResult->getDocument(); + self::assertInstanceOf(DocumentItemResult::class, $documentItemResult); + self::assertIsString($documentItemResult->uid); + self::assertEquals($this->documentUid, $documentItemResult->uid); + self::assertIsArray($documentItemResult->state); + self::assertArrayHasKey('code', $documentItemResult->state); + self::assertArrayHasKey('name', $documentItemResult->state); + } + + /** + * @throws TransportException + */ + #[Test] + #[TestDox('sign.b2e.document.get throws BaseException for non-existent document UID')] + public function testGetThrowsExceptionForInvalidUid(): void + { + $this->expectException(BaseException::class); + + $this->documentService->get('non-existent-uid-00000000-0000-0000-0000-000000000000'); + } + + /** + * @throws TransportException + */ + #[Test] + #[TestDox('sign.b2e.document.send throws BaseException when required fields are missing')] + public function testSendThrowsExceptionWhenRequiredFieldsMissing(): void + { + $this->expectException(BaseException::class); + + // Sending with empty fields should return BAD_REQUEST from the API + $this->documentService->send([]); + } +} diff --git a/tests/Integration/Services/Sign/B2e/MySafeTail/Result/MySafeTailItemResultAnnotationsTest.php b/tests/Integration/Services/Sign/B2e/MySafeTail/Result/MySafeTailItemResultAnnotationsTest.php new file mode 100644 index 00000000..6ca094ce --- /dev/null +++ b/tests/Integration/Services/Sign/B2e/MySafeTail/Result/MySafeTailItemResultAnnotationsTest.php @@ -0,0 +1,97 @@ + + * + * 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\Sign\B2e\MySafeTail\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Sign\B2e\MySafeTail\Result\MySafeTailItemResult; +use Bitrix24\SDK\Services\Sign\B2e\MySafeTail\Service\MySafeTail; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Factory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(MySafeTailItemResult::class)] +class MySafeTailItemResultAnnotationsTest extends TestCase +{ + use CustomBitrix24Assertions; + + private MySafeTail $mySafeTailService; + + #[\Override] + protected function setUp(): void + { + $this->mySafeTailService = Factory::getServiceBuilder(true)->getSignScope()->mySafeTail(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('testAllSystemFieldsAnnotated: all fields in MySafeTailItemResult are annotated in phpdoc and match raw API response')] + public function testAllSystemFieldsAnnotated(): void + { + $rawItems = $this->mySafeTailService->tail(20, 0) + ->getCoreResponse()->getResponseData()->getResult(); + + if ($rawItems === []) { + $this->markTestSkipped( + 'No signed documents found in company safe — cannot verify annotation completeness against live API data.' + ); + } + + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys($rawItems[0]), + MySafeTailItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('testAllSystemFieldsHasValidTypeAnnotation: all fields in MySafeTailItemResult have valid type annotations')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $rawItems = $this->mySafeTailService->tail(20, 0) + ->getCoreResponse()->getResponseData()->getResult(); + + if ($rawItems === []) { + $this->markTestSkipped( + 'No signed documents found in company safe — cannot verify type annotations against live API data.' + ); + } + + $fieldTypesMap = []; + foreach (array_keys($rawItems[0]) as $fieldCode) { + $fieldTypesMap[$fieldCode] = match ($fieldCode) { + 'id', 'creator_id', 'member_id' => ['type' => 'integer'], + 'title', 'file_url', 'role' => ['type' => 'string'], + 'create_date', 'signed_date' => ['type' => 'datetime'], + default => throw new \RuntimeException( + sprintf('Unknown field «%s» in sign.b2e.mysafe.tail response — update the type map.', $fieldCode) + ), + }; + } + + $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation( + $fieldTypesMap, + MySafeTailItemResult::class + ); + } +} diff --git a/tests/Integration/Services/Sign/B2e/MySafeTail/Service/MySafeTailTest.php b/tests/Integration/Services/Sign/B2e/MySafeTail/Service/MySafeTailTest.php new file mode 100644 index 00000000..e6da1523 --- /dev/null +++ b/tests/Integration/Services/Sign/B2e/MySafeTail/Service/MySafeTailTest.php @@ -0,0 +1,84 @@ + + * + * 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\Sign\B2e\MySafeTail\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Sign\B2e\MySafeTail\Result\MySafeTailItemResult; +use Bitrix24\SDK\Services\Sign\B2e\MySafeTail\Result\MySafeTailResult; +use Bitrix24\SDK\Services\Sign\B2e\MySafeTail\Service\MySafeTail; +use Bitrix24\SDK\Tests\Integration\Factory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(MySafeTail::class)] +class MySafeTailTest extends TestCase +{ + private MySafeTail $mySafeTailService; + + #[\Override] + protected function setUp(): void + { + $this->mySafeTailService = Factory::getServiceBuilder(true)->getSignScope()->mySafeTail(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('sign.b2e.mysafe.tail returns MySafeTailResult with items array')] + public function testTailReturnsMySafeTailResult(): void + { + $mySafeTailResult = $this->mySafeTailService->tail(20, 0); + + self::assertInstanceOf(MySafeTailResult::class, $mySafeTailResult); + self::assertIsArray($mySafeTailResult->getItems()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('sign.b2e.mysafe.tail items are MySafeTailItemResult instances when not empty')] + public function testTailItemsAreMySafeTailItemResults(): void + { + $items = $this->mySafeTailService->tail(20, 0)->getItems(); + + if ($items === []) { + $this->markTestSkipped('No signed documents found in company safe — cannot verify item type.'); + } + + self::assertInstanceOf(MySafeTailItemResult::class, $items[0]); + self::assertIsInt($items[0]->id); + self::assertIsString($items[0]->title); + self::assertIsString($items[0]->role); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('sign.b2e.mysafe.tail respects limit parameter')] + public function testTailRespectsLimitParameter(): void + { + $items = $this->mySafeTailService->tail(1, 0)->getItems(); + + self::assertLessThanOrEqual(1, count($items)); + } +} diff --git a/tests/Integration/Services/Sign/B2e/PersonalTail/Result/PersonalTailItemResultAnnotationsTest.php b/tests/Integration/Services/Sign/B2e/PersonalTail/Result/PersonalTailItemResultAnnotationsTest.php new file mode 100644 index 00000000..e41f7860 --- /dev/null +++ b/tests/Integration/Services/Sign/B2e/PersonalTail/Result/PersonalTailItemResultAnnotationsTest.php @@ -0,0 +1,97 @@ + + * + * 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\Sign\B2e\PersonalTail\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Sign\B2e\PersonalTail\Result\PersonalTailItemResult; +use Bitrix24\SDK\Services\Sign\B2e\PersonalTail\Service\PersonalTail; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Factory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(PersonalTailItemResult::class)] +class PersonalTailItemResultAnnotationsTest extends TestCase +{ + use CustomBitrix24Assertions; + + private PersonalTail $personalTailService; + + #[\Override] + protected function setUp(): void + { + $this->personalTailService = Factory::getServiceBuilder(true)->getSignScope()->personalTail(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('testAllSystemFieldsAnnotated: all fields in PersonalTailItemResult are annotated in phpdoc and match raw API response')] + public function testAllSystemFieldsAnnotated(): void + { + $rawItems = $this->personalTailService->tail(20, 0) + ->getCoreResponse()->getResponseData()->getResult(); + + if ($rawItems === []) { + $this->markTestSkipped( + 'No signed documents found in personal tail — cannot verify annotation completeness against live API data.' + ); + } + + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys($rawItems[0]), + PersonalTailItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('testAllSystemFieldsHasValidTypeAnnotation: all fields in PersonalTailItemResult have valid type annotations')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $rawItems = $this->personalTailService->tail(20, 0) + ->getCoreResponse()->getResponseData()->getResult(); + + if ($rawItems === []) { + $this->markTestSkipped( + 'No signed documents found in personal tail — cannot verify type annotations against live API data.' + ); + } + + $fieldTypesMap = []; + foreach (array_keys($rawItems[0]) as $fieldCode) { + $fieldTypesMap[$fieldCode] = match ($fieldCode) { + 'id' => ['type' => 'integer'], + 'title', 'file_url' => ['type' => 'string'], + 'signed_date' => ['type' => 'datetime'], + default => throw new \RuntimeException( + sprintf('Unknown field «%s» in sign.b2e.personal.tail response — update the type map.', $fieldCode) + ), + }; + } + + $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation( + $fieldTypesMap, + PersonalTailItemResult::class + ); + } +} diff --git a/tests/Integration/Services/Sign/B2e/PersonalTail/Service/PersonalTailTest.php b/tests/Integration/Services/Sign/B2e/PersonalTail/Service/PersonalTailTest.php new file mode 100644 index 00000000..f9951ee5 --- /dev/null +++ b/tests/Integration/Services/Sign/B2e/PersonalTail/Service/PersonalTailTest.php @@ -0,0 +1,83 @@ + + * + * 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\Sign\B2e\PersonalTail\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Sign\B2e\PersonalTail\Result\PersonalTailItemResult; +use Bitrix24\SDK\Services\Sign\B2e\PersonalTail\Result\PersonalTailResult; +use Bitrix24\SDK\Services\Sign\B2e\PersonalTail\Service\PersonalTail; +use Bitrix24\SDK\Tests\Integration\Factory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(PersonalTail::class)] +class PersonalTailTest extends TestCase +{ + private PersonalTail $personalTailService; + + #[\Override] + protected function setUp(): void + { + $this->personalTailService = Factory::getServiceBuilder(true)->getSignScope()->personalTail(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('sign.b2e.personal.tail returns PersonalTailResult with items array')] + public function testTailReturnsPersonalTailResult(): void + { + $personalTailResult = $this->personalTailService->tail(20, 0); + + self::assertInstanceOf(PersonalTailResult::class, $personalTailResult); + self::assertIsArray($personalTailResult->getItems()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('sign.b2e.personal.tail items are PersonalTailItemResult instances when not empty')] + public function testTailItemsArePersonalTailItemResults(): void + { + $items = $this->personalTailService->tail(20, 0)->getItems(); + + if ($items === []) { + $this->markTestSkipped('No signed documents found in personal tail — cannot verify item type.'); + } + + self::assertInstanceOf(PersonalTailItemResult::class, $items[0]); + self::assertIsInt($items[0]->id); + self::assertIsString($items[0]->title); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('sign.b2e.personal.tail respects limit parameter')] + public function testTailRespectsLimitParameter(): void + { + $items = $this->personalTailService->tail(1, 0)->getItems(); + + self::assertLessThanOrEqual(1, count($items)); + } +} diff --git a/tests/Unit/Services/Sign/SignServiceBuilderTest.php b/tests/Unit/Services/Sign/SignServiceBuilderTest.php new file mode 100644 index 00000000..010828c6 --- /dev/null +++ b/tests/Unit/Services/Sign/SignServiceBuilderTest.php @@ -0,0 +1,63 @@ + + * + * 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\Sign; + +use Bitrix24\SDK\Services\Sign\B2e\CompanyProvider\Service\CompanyProvider; +use Bitrix24\SDK\Services\Sign\B2e\Document\Service\Document; +use Bitrix24\SDK\Services\Sign\B2e\MySafeTail\Service\MySafeTail; +use Bitrix24\SDK\Services\Sign\B2e\PersonalTail\Service\PersonalTail; +use Bitrix24\SDK\Services\Sign\SignServiceBuilder; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(SignServiceBuilder::class)] +class SignServiceBuilderTest extends TestCase +{ + #[Test] + #[TestDox('SignServiceBuilder class exists and can be instantiated')] + public function testSignServiceBuilderExists(): void + { + $this->assertTrue(class_exists(SignServiceBuilder::class)); + } + + #[Test] + #[TestDox('Document service class exists')] + public function testDocumentServiceClassExists(): void + { + $this->assertTrue(class_exists(Document::class)); + } + + #[Test] + #[TestDox('CompanyProvider service class exists')] + public function testCompanyProviderServiceClassExists(): void + { + $this->assertTrue(class_exists(CompanyProvider::class)); + } + + #[Test] + #[TestDox('PersonalTail service class exists')] + public function testPersonalTailServiceClassExists(): void + { + $this->assertTrue(class_exists(PersonalTail::class)); + } + + #[Test] + #[TestDox('MySafeTail service class exists')] + public function testMySafeTailServiceClassExists(): void + { + $this->assertTrue(class_exists(MySafeTail::class)); + } +}