diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 6e5393c3..15e131ad 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -29,6 +29,7 @@ ->in(__DIR__ . '/src/Services/SonetGroup/') ->in(__DIR__ . '/src/Services/IMOpenLines/') ->in(__DIR__ . '/src/Services/Landing/') + ->in(__DIR__ . '/src/Services/Catalog/') ->name('*.php') ->exclude(['vendor', 'storage', 'docker', 'docs']) // Exclude directories ->ignoreDotFiles(true) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4bc3b52..6bf4f264 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ### Added +- Added service `Services\Catalog\Ratio` with support methods, + see [catalog.ratio.* methods](https://apidocs.bitrix24.com/api-reference/catalog/ratio/index.html) ([#570](https://github.com/bitrix24/b24phpsdk/issues/570)): + - `get` gets the values of a measurement unit ratio by identifier + - `list` gets the list of measurement unit ratios matching a filter + - `getFields` returns the description of measurement unit ratio fields - Added service `Services\Landing\Site\Service\Site` with support methods, see [landing.site.* methods](https://github.com/bitrix24/b24phpsdk/issues/267): - `add` adds a site diff --git a/Makefile b/Makefile index c2fc4a9a..0b509e4d 100644 --- a/Makefile +++ b/Makefile @@ -492,6 +492,10 @@ test-integration-landing-role: test-integration-landing-repowidget: docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_landing_repowidget +.PHONY: test-integration-catalog-ratio +test-integration-catalog-ratio: + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_catalog_ratio + # work dev environment .PHONY: php-dev-server-up php-dev-server-up: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d0187ae5..10b7d3f8 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -262,6 +262,9 @@ ./tests/Integration/Services/Landing/RepoWidget/ + + ./tests/Integration/Services/Catalog/Ratio/ + diff --git a/src/Services/Catalog/Catalog/Result/CatalogItemResult.php b/src/Services/Catalog/Catalog/Result/CatalogItemResult.php index e62fc7cb..189852bb 100644 --- a/src/Services/Catalog/Catalog/Result/CatalogItemResult.php +++ b/src/Services/Catalog/Catalog/Result/CatalogItemResult.php @@ -29,4 +29,4 @@ */ class CatalogItemResult extends AbstractCatalogItem { -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Catalog/Result/CatalogResult.php b/src/Services/Catalog/Catalog/Result/CatalogResult.php index aa468aa8..c5222f11 100644 --- a/src/Services/Catalog/Catalog/Result/CatalogResult.php +++ b/src/Services/Catalog/Catalog/Result/CatalogResult.php @@ -21,4 +21,4 @@ public function catalog(): CatalogItemResult { return new CatalogItemResult($this->getCoreResponse()->getResponseData()->getResult()['catalog']); } -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Catalog/Result/CatalogsResult.php b/src/Services/Catalog/Catalog/Result/CatalogsResult.php index add5f259..f721c3bb 100644 --- a/src/Services/Catalog/Catalog/Result/CatalogsResult.php +++ b/src/Services/Catalog/Catalog/Result/CatalogsResult.php @@ -32,4 +32,4 @@ public function getCatalogs(): array return $res; } -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Catalog/Service/Catalog.php b/src/Services/Catalog/Catalog/Service/Catalog.php index d9cb55fe..0da76ea3 100644 --- a/src/Services/Catalog/Catalog/Service/Catalog.php +++ b/src/Services/Catalog/Catalog/Service/Catalog.php @@ -82,4 +82,4 @@ public function fields(): FieldsResult { return new FieldsResult($this->core->call('catalog.catalog.getFields')); } -} \ No newline at end of file +} diff --git a/src/Services/Catalog/CatalogServiceBuilder.php b/src/Services/Catalog/CatalogServiceBuilder.php index 57c91b88..a394a253 100644 --- a/src/Services/Catalog/CatalogServiceBuilder.php +++ b/src/Services/Catalog/CatalogServiceBuilder.php @@ -17,6 +17,7 @@ use Bitrix24\SDK\Core\Credentials\Scope; use Bitrix24\SDK\Services\AbstractServiceBuilder; use Bitrix24\SDK\Services\Catalog; + #[ApiServiceBuilderMetadata(new Scope(['catalog']))] class CatalogServiceBuilder extends AbstractServiceBuilder { @@ -44,4 +45,16 @@ public function catalog(): Catalog\Catalog\Service\Catalog return $this->serviceCache[__METHOD__]; } -} \ No newline at end of file + + public function ratio(): Catalog\Ratio\Service\Ratio + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Catalog\Ratio\Service\Ratio( + $this->core, + $this->log + ); + } + + return $this->serviceCache[__METHOD__]; + } +} diff --git a/src/Services/Catalog/Common/ProductType.php b/src/Services/Catalog/Common/ProductType.php index 0bf8ef09..ad568a47 100644 --- a/src/Services/Catalog/Common/ProductType.php +++ b/src/Services/Catalog/Common/ProductType.php @@ -20,4 +20,4 @@ enum ProductType: int case SKU = 3; case productOffer = 4; case genericOffer = 5; -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Common/Result/AbstractCatalogItem.php b/src/Services/Catalog/Common/Result/AbstractCatalogItem.php index e2929d73..9761daed 100644 --- a/src/Services/Catalog/Common/Result/AbstractCatalogItem.php +++ b/src/Services/Catalog/Common/Result/AbstractCatalogItem.php @@ -109,4 +109,4 @@ protected function getKeyWithUserfieldByFieldName(string $fieldName) return $this->$fieldName; } -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Product/Result/ProductItemResult.php b/src/Services/Catalog/Product/Result/ProductItemResult.php index c576d9b7..58c80851 100644 --- a/src/Services/Catalog/Product/Result/ProductItemResult.php +++ b/src/Services/Catalog/Product/Result/ProductItemResult.php @@ -52,4 +52,4 @@ */ class ProductItemResult extends AbstractCatalogItem { -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Product/Result/ProductResult.php b/src/Services/Catalog/Product/Result/ProductResult.php index 2751fda8..fc83fa8c 100644 --- a/src/Services/Catalog/Product/Result/ProductResult.php +++ b/src/Services/Catalog/Product/Result/ProductResult.php @@ -26,4 +26,4 @@ public function product(): ProductItemResult return new ProductItemResult($this->getCoreResponse()->getResponseData()->getResult()['product']); } -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Product/Result/ProductsResult.php b/src/Services/Catalog/Product/Result/ProductsResult.php index aa644fa3..268d6d8f 100644 --- a/src/Services/Catalog/Product/Result/ProductsResult.php +++ b/src/Services/Catalog/Product/Result/ProductsResult.php @@ -31,4 +31,4 @@ public function getProducts(): array return $res; } -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Product/Service/Batch.php b/src/Services/Catalog/Product/Service/Batch.php index 45dddcb7..42970546 100644 --- a/src/Services/Catalog/Product/Service/Batch.php +++ b/src/Services/Catalog/Product/Service/Batch.php @@ -25,7 +25,7 @@ { public function __construct( protected BatchOperationsInterface $batch, - protected LoggerInterface $log) - { + protected LoggerInterface $log + ) { } -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Product/Service/Product.php b/src/Services/Catalog/Product/Service/Product.php index 730fd7c9..d37d34f6 100644 --- a/src/Services/Catalog/Product/Service/Product.php +++ b/src/Services/Catalog/Product/Service/Product.php @@ -25,7 +25,6 @@ use Bitrix24\SDK\Services\Catalog\Common\ProductType; use Bitrix24\SDK\Services\Catalog\Product\Result\ProductResult; use Bitrix24\SDK\Services\Catalog\Product\Result\ProductsResult; - use Psr\Log\LoggerInterface; #[ApiServiceMetadata(new Scope(['catalog']))] @@ -35,8 +34,7 @@ public function __construct( public Batch $batch, CoreInterface $core, LoggerInterface $logger - ) - { + ) { parent::__construct($core, $logger); } @@ -71,7 +69,9 @@ public function get(int $productId): ProductResult )] public function add(array $productFields): ProductResult { - return new ProductResult($this->core->call('catalog.product.add', [ + return new ProductResult($this->core->call( + 'catalog.product.add', + [ 'fields' => $productFields ] )); @@ -140,4 +140,4 @@ public function fieldsByFilter(int $iblockId, ProductType $productType, ?array $ return new FieldsResult($this->core->call('catalog.product.getFieldsByFilter', ['filter' => $filter])); } -} \ No newline at end of file +} diff --git a/src/Services/Catalog/Ratio/Result/RatioItemResult.php b/src/Services/Catalog/Ratio/Result/RatioItemResult.php new file mode 100644 index 00000000..4176e4fe --- /dev/null +++ b/src/Services/Catalog/Ratio/Result/RatioItemResult.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\Catalog\Ratio\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * @property-read int $id + * @property-read bool $isDefault + * @property-read int $productId + * @property-read float $ratio + */ +class RatioItemResult extends AbstractItem +{ +} diff --git a/src/Services/Catalog/Ratio/Result/RatioResult.php b/src/Services/Catalog/Ratio/Result/RatioResult.php new file mode 100644 index 00000000..c693067e --- /dev/null +++ b/src/Services/Catalog/Ratio/Result/RatioResult.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\Catalog\Ratio\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class RatioResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function ratio(): RatioItemResult + { + return new RatioItemResult($this->getCoreResponse()->getResponseData()->getResult()['ratio']); + } +} diff --git a/src/Services/Catalog/Ratio/Result/RatiosResult.php b/src/Services/Catalog/Ratio/Result/RatiosResult.php new file mode 100644 index 00000000..25ffdec2 --- /dev/null +++ b/src/Services/Catalog/Ratio/Result/RatiosResult.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\Catalog\Ratio\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class RatiosResult extends AbstractResult +{ + /** + * @return RatioItemResult[] + * @throws BaseException + */ + public function getRatios(): array + { + $items = []; + foreach ($this->getCoreResponse()->getResponseData()->getResult()['ratios'] as $item) { + $items[] = new RatioItemResult($item); + } + + return $items; + } + + /** + * @throws BaseException + */ + public function getTotal(): int + { + return $this->getCoreResponse()->getResponseData()->getPagination()->getTotal() ?? 0; + } +} diff --git a/src/Services/Catalog/Ratio/Service/Ratio.php b/src/Services/Catalog/Ratio/Service/Ratio.php new file mode 100644 index 00000000..8741bf5a --- /dev/null +++ b/src/Services/Catalog/Ratio/Service/Ratio.php @@ -0,0 +1,88 @@ + + * + * 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\Catalog\Ratio\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +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\Catalog\Ratio\Result\RatioResult; +use Bitrix24\SDK\Services\Catalog\Ratio\Result\RatiosResult; + +#[ApiServiceMetadata(new Scope(['catalog']))] +class Ratio extends AbstractService +{ + /** + * Returns the values of the measurement unit ratio fields by identifier. + * + * @link https://apidocs.bitrix24.com/api-reference/catalog/ratio/catalog-ratio-get.html + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'catalog.ratio.get', + 'https://apidocs.bitrix24.com/api-reference/catalog/ratio/catalog-ratio-get.html', + 'Returns the values of the measurement unit ratio fields by identifier.' + )] + public function get(int $id): RatioResult + { + $this->guardPositiveId($id); + + return new RatioResult($this->core->call('catalog.ratio.get', ['id' => $id])); + } + + /** + * Returns a list of measurement unit ratios from the catalog matching the given filter. + * + * @link https://apidocs.bitrix24.com/api-reference/catalog/ratio/catalog-ratio-list.html + * + * @param string[] $select + * @param array $filter + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'catalog.ratio.list', + 'https://apidocs.bitrix24.com/api-reference/catalog/ratio/catalog-ratio-list.html', + 'Returns a list of measurement unit ratios from the catalog matching the given filter.' + )] + public function list(array $select = [], array $filter = []): RatiosResult + { + return new RatiosResult($this->core->call('catalog.ratio.list', [ + 'select' => $select, + 'filter' => $filter, + ])); + } + + /** + * Returns the available fields of a measurement unit ratio. + * + * @link https://apidocs.bitrix24.com/api-reference/catalog/ratio/catalog-ratio-get-fields.html + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'catalog.ratio.getFields', + 'https://apidocs.bitrix24.com/api-reference/catalog/ratio/catalog-ratio-get-fields.html', + 'Returns the available fields of a measurement unit ratio.' + )] + public function fields(): FieldsResult + { + return new FieldsResult($this->core->call('catalog.ratio.getFields')); + } +} diff --git a/tests/CustomAssertions/CustomBitrix24Assertions.php b/tests/CustomAssertions/CustomBitrix24Assertions.php index dd1d32b7..8193ba03 100644 --- a/tests/CustomAssertions/CustomBitrix24Assertions.php +++ b/tests/CustomAssertions/CustomBitrix24Assertions.php @@ -22,9 +22,10 @@ use Carbon\CarbonImmutable; use MoneyPHP\Percentage\Percentage; use Typhoon\Reflection\TyphoonReflector; -use function Typhoon\Type\stringify; use Money\Currency; +use function Typhoon\Type\stringify; + trait CustomBitrix24Assertions { /** @@ -225,6 +226,20 @@ protected function assertBitrix24AllResultItemFieldsHasValidTypeAnnotation( ); break; } + if (str_contains(mb_strtoupper($fieldCode), 'RATIO')) { + $this->assertTrue( + str_contains($propsFromAnnotations[$fieldCode], 'float'), + sprintf( + 'class «%s» field «%s» has invalid type phpdoc annotation «%s», field type from bitrix24 is «%s», expected sdk-type «%s»', + $resultItemClassName, + $fieldCode, + $propsFromAnnotations[$fieldCode], + $fieldData['type'], + 'float' + ) + ); + break; + } $this->assertTrue( str_contains($propsFromAnnotations[$fieldCode], 'Money\Money'), sprintf( diff --git a/tests/Integration/Services/Catalog/Ratio/Result/RatioItemResultAnnotationsTest.php b/tests/Integration/Services/Catalog/Ratio/Result/RatioItemResultAnnotationsTest.php new file mode 100644 index 00000000..5b359b73 --- /dev/null +++ b/tests/Integration/Services/Catalog/Ratio/Result/RatioItemResultAnnotationsTest.php @@ -0,0 +1,79 @@ + + * + * 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\Catalog\Ratio\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Fields\FieldsFilter; +use Bitrix24\SDK\Services\Catalog\Ratio\Result\RatioItemResult; +use Bitrix24\SDK\Services\Catalog\Ratio\Service\Ratio; +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(RatioItemResult::class)] +class RatioItemResultAnnotationsTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Ratio $ratioService; + + #[\Override] + protected function setUp(): void + { + $this->ratioService = Fabric::getServiceBuilder()->getCatalogScope()->ratio(); + } + + /** + * catalog.ratio.getFields wraps the actual field descriptions under a «ratio» key, + * unlike most other bitrix24 *.getFields methods that return a flat field map. + * + * @return array> + * @throws BaseException + * @throws TransportException + */ + private function getRatioFieldsDescription(): array + { + return $this->ratioService->fields()->getFieldsDescription()['ratio']; + } + + #[Test] + #[TestDox('all fields in RatioItemResult are annotated in phpdoc and match with raw api response')] + public function testAllSystemFieldsAnnotated(): void + { + $propListFromApi = (new FieldsFilter())->filterSystemFields(array_keys($this->getRatioFieldsDescription())); + + $this->assertBitrix24AllResultItemFieldsAnnotated( + $propListFromApi, + RatioItemResult::class + ); + } + + #[Test] + #[TestDox('all fields in RatioItemResult have valid type casting in magic getters')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $allFields = $this->getRatioFieldsDescription(); + $systemFieldsCodes = (new FieldsFilter())->filterSystemFields(array_keys($allFields)); + $systemFields = array_filter($allFields, static fn ($code): bool => in_array($code, $systemFieldsCodes, true), ARRAY_FILTER_USE_KEY); + + $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation( + $systemFields, + RatioItemResult::class + ); + } +} diff --git a/tests/Integration/Services/Catalog/Ratio/Service/RatioTest.php b/tests/Integration/Services/Catalog/Ratio/Service/RatioTest.php new file mode 100644 index 00000000..eae82689 --- /dev/null +++ b/tests/Integration/Services/Catalog/Ratio/Service/RatioTest.php @@ -0,0 +1,85 @@ + + * + * 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\Catalog\Ratio\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Catalog\Ratio\Result\RatioItemResult; +use Bitrix24\SDK\Services\Catalog\Ratio\Service\Ratio; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\TestCase; + +#[CoversMethod(Ratio::class, 'get')] +#[CoversMethod(Ratio::class, 'list')] +#[CoversMethod(Ratio::class, 'fields')] +class RatioTest extends TestCase +{ + private Ratio $ratioService; + + #[\Override] + protected function setUp(): void + { + $this->ratioService = Fabric::getServiceBuilder()->getCatalogScope()->ratio(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testGetFields(): void + { + $fields = $this->ratioService->fields()->getFieldsDescription(); + + self::assertArrayHasKey('ratio', $fields); + self::assertArrayHasKey('id', $fields['ratio']); + self::assertArrayHasKey('isDefault', $fields['ratio']); + self::assertArrayHasKey('productId', $fields['ratio']); + self::assertArrayHasKey('ratio', $fields['ratio']); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testList(): void + { + $ratiosResult = $this->ratioService->list(); + + self::assertIsArray($ratiosResult->getRatios()); + self::assertGreaterThanOrEqual(0, $ratiosResult->getTotal()); + } + + /** + * catalog.ratio has no REST method to create a ratio — ratios are created implicitly when a + * product's measurement unit ratio is configured. + * If the portal has none, this test is skipped as there is no way to fabricate one via REST. + * + * @throws BaseException + * @throws TransportException + */ + public function testGet(): void + { + $ratios = $this->ratioService->list()->getRatios(); + if ($ratios === []) { + $this->markTestSkipped('portal has no catalog ratios (catalog.ratio) configured to test get() against'); + } + + $firstRatio = $ratios[0]; + $ratioItemResult = $this->ratioService->get($firstRatio->id)->ratio(); + + self::assertInstanceOf(RatioItemResult::class, $ratioItemResult); + self::assertEquals($firstRatio->id, $ratioItemResult->id); + } +} diff --git a/tests/Unit/Services/Catalog/Ratio/Service/RatioTest.php b/tests/Unit/Services/Catalog/Ratio/Service/RatioTest.php new file mode 100644 index 00000000..ecf4a098 --- /dev/null +++ b/tests/Unit/Services/Catalog/Ratio/Service/RatioTest.php @@ -0,0 +1,62 @@ + + * + * 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\Catalog\Ratio\Service; + +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\SDK\Core\Result\FieldsResult; +use Bitrix24\SDK\Services\Catalog\Ratio\Result\RatioResult; +use Bitrix24\SDK\Services\Catalog\Ratio\Result\RatiosResult; +use Bitrix24\SDK\Services\Catalog\Ratio\Service\Ratio; +use Bitrix24\SDK\Tests\Unit\Stubs\NullCore; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; + +#[CoversClass(Ratio::class)] +class RatioTest extends TestCase +{ + private Ratio $service; + + #[\Override] + protected function setUp(): void + { + $this->service = new Ratio(new NullCore(), new NullLogger()); + } + + #[Test] + public function testGetReturnsRatioResult(): void + { + $this->assertInstanceOf(RatioResult::class, $this->service->get(1)); + } + + #[Test] + public function testListReturnsRatiosResult(): void + { + $this->assertInstanceOf(RatiosResult::class, $this->service->list()); + } + + #[Test] + public function testFieldsReturnsFieldsResult(): void + { + $this->assertInstanceOf(FieldsResult::class, $this->service->fields()); + } + + #[Test] + public function testGetThrowsOnNonPositiveId(): void + { + $this->expectException(InvalidArgumentException::class); + $this->service->get(0); + } +}