diff --git a/rector.php b/rector.php index 0a8995c7b..a51182ee7 100644 --- a/rector.php +++ b/rector.php @@ -3,7 +3,7 @@ use Rector\Config\RectorConfig; use Rector\DeadCode\Rector\ClassLike\RemoveAnnotationRector; use Rector\Php70\Rector\StmtsAwareInterface\IfIssetToCoalescingRector; -use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector; +use Rector\Php80\Rector\Switch_\ChangeSwitchToMatchRector; use Rector\Set\ValueObject\LevelSetList; return static function (RectorConfig $rectorConfig): void { @@ -17,13 +17,14 @@ // Modernize code $rectorConfig->sets([LevelSetList::UP_TO_PHP_74]); + $rectorConfig->rule(ChangeSwitchToMatchRector::class); + // phpcs:disable Squiz.Arrays.ArrayDeclaration.KeySpecified $rectorConfig->skip([ // Do not use ternaries extensively IfIssetToCoalescingRector::class, - // Not necessary in documentation examples - JsonThrowOnErrorRector::class => [ - __DIR__ . '/tests/DocumentationExamplesTest.php', + ChangeSwitchToMatchRector::class => [ + __DIR__ . '/tests/SpecTests/Operation.php', ], ]); // phpcs:enable diff --git a/src/Builder/Encoder/OperatorEncoder.php b/src/Builder/Encoder/OperatorEncoder.php index f52ad392a..69ef27c24 100644 --- a/src/Builder/Encoder/OperatorEncoder.php +++ b/src/Builder/Encoder/OperatorEncoder.php @@ -38,27 +38,14 @@ public function encode(mixed $value): stdClass throw UnsupportedValueException::invalidEncodableValue($value); } - switch ($value::ENCODE) { - case Encode::Single: - return $this->encodeAsSingle($value); - - case Encode::Array: - return $this->encodeAsArray($value); - - case Encode::Object: - case Encode::FlatObject: - return $this->encodeAsObject($value); - - case Encode::DollarObject: - return $this->encodeAsDollarObject($value); - - case Encode::Group: - assert($value instanceof GroupStage); - - return $this->encodeAsGroup($value); - } - - throw new LogicException(sprintf('Class "%s" does not have a valid ENCODE constant.', $value::class)); + return match ($value::ENCODE) { + Encode::Single => $this->encodeAsSingle($value), + Encode::Array => $this->encodeAsArray($value), + Encode::Object, Encode::FlatObject => $this->encodeAsObject($value), + Encode::DollarObject => $this->encodeAsDollarObject($value), + Encode::Group => $this->encodeAsGroup($value), + default => throw new LogicException(sprintf('Class "%s" does not have a valid ENCODE constant.', $value::class)), + }; } /** @@ -111,8 +98,10 @@ private function encodeAsDollarObject(OperatorInterface $value): stdClass /** * $group stage have a specific encoding because the _id argument is required and others are variadic */ - private function encodeAsGroup(GroupStage $value): stdClass + private function encodeAsGroup(OperatorInterface $value): stdClass { + assert($value instanceof GroupStage); + $result = new stdClass(); $result->_id = $this->recursiveEncode($value->_id); diff --git a/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php b/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php deleted file mode 100644 index aebfa6303..000000000 --- a/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php +++ /dev/null @@ -1,476 +0,0 @@ -=')) { - $this->markTestIncomplete('Range protocol V1 is not supported by ext-mongodb 1.20+'); - } - - if ($this->isStandalone()) { - $this->markTestSkipped('Range explicit encryption tests require replica sets'); - } - - $this->skipIfServerVersion('<', '8.0.0', 'Range explicit encryption tests require MongoDB 8.0 or later'); - - $client = static::createTestClient(); - - $key1Document = $this->decodeJson(file_get_contents(__DIR__ . '/../client-side-encryption/etc/data/keys/key1-document.json')); - $this->key1Id = $key1Document->_id; - - // Drop the key vault collection and insert key1Document with a majority write concern - self::insertKeyVaultData($client, [$key1Document]); - - $this->clientEncryption = $client->createClientEncryption([ - 'keyVaultNamespace' => 'keyvault.datakeys', - 'kmsProviders' => ['local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY))]], - ]); - - $autoEncryptionOpts = [ - 'keyVaultNamespace' => 'keyvault.datakeys', - 'kmsProviders' => ['local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY))]], - 'bypassQueryAnalysis' => true, - ]; - - $this->encryptedClient = self::createTestClient(null, [], [ - 'autoEncryption' => $autoEncryptionOpts, - /* libmongocrypt caches results from listCollections. Use a new - * client in each test to ensure its encryptedFields is applied. */ - 'disableClientPersistence' => true, - ]); - } - - public function setUpWithTypeAndRangeOpts(string $type, array $rangeOpts): void - { - if ($type === 'DecimalNoPrecision' || $type === 'DecimalPrecision') { - $this->markTestSkipped('Bundled libmongocrypt does not support Decimal128 (PHPC-2207)'); - } - - /* Read the encryptedFields file directly into BSON to preserve typing - * for 64-bit integers. This means that DropEncryptedCollection and - * CreateEncryptedCollection will be unable to inspect the option for - * metadata collection names, but that's not necessary for the test. */ - $encryptedFields = Document::fromJSON(file_get_contents(__DIR__ . '/../client-side-encryption/etc/data/range-encryptedFields-' . $type . '.json')); - - $database = $this->encryptedClient->selectDatabase($this->getDatabaseName()); - $database->dropCollection('explicit_encryption', ['encryptedFields' => $encryptedFields]); - $database->createCollection('explicit_encryption', ['encryptedFields' => $encryptedFields]); - $this->collection = $database->selectCollection('explicit_encryption'); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts, - ]; - - $cast = self::getCastCallableForType($type); - $fieldName = 'encrypted' . $type; - - $this->collection->insertMany([ - ['_id' => 0, $fieldName => $this->clientEncryption->encrypt($cast(0), $encryptOpts)], - ['_id' => 1, $fieldName => $this->clientEncryption->encrypt($cast(6), $encryptOpts)], - ['_id' => 2, $fieldName => $this->clientEncryption->encrypt($cast(30), $encryptOpts)], - ['_id' => 3, $fieldName => $this->clientEncryption->encrypt($cast(200), $encryptOpts)], - ]); - } - - public function tearDown(): void - { - /* Since encryptedClient is created with disableClientPersistence=true, - * free any objects that may hold a reference to its mongoc_client_t */ - $this->collection = null; - $this->clientEncryption = null; - $this->encryptedClient = null; - } - - /** @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#test-setup-rangeopts */ - public static function provideTypeAndRangeOpts(): Generator - { - // TODO: skip DecimalNoPrecision test on mongos - yield 'DecimalNoPrecision' => [ - 'DecimalNoPrecision', - ['sparsity' => 1], - ]; - - yield 'DecimalPrecision' => [ - 'DecimalPrecision', - [ - 'min' => new Decimal128('0'), - 'max' => new Decimal128('200'), - 'sparsity' => 1, - 'precision' => 2, - ], - ]; - - yield 'DoubleNoPrecision' => [ - 'DoubleNoPrecision', - ['sparsity' => 1], - ]; - - yield 'DoublePrecision' => [ - 'DoublePrecision', - [ - 'min' => 0.0, - 'max' => 200.0, - 'sparsity' => 1, - 'precision' => 2, - ], - ]; - - yield 'Date' => [ - 'Date', - [ - 'min' => new UTCDateTime(0), - 'max' => new UTCDateTime(200), - 'sparsity' => 1, - ], - ]; - - yield 'Int' => [ - 'Int', - [ - 'min' => 0, - 'max' => 200, - 'sparsity' => 1, - ], - ]; - - yield 'Long' => [ - 'Long', - [ - 'min' => new Int64(0), - 'max' => new Int64(200), - 'sparsity' => 1, - ], - ]; - } - - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-1-can-decrypt-a-payload - * @dataProvider provideTypeAndRangeOpts - */ - public function testCase1_CanDecryptAPayload(string $type, array $rangeOpts): void - { - $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts, - ]; - - $cast = self::getCastCallableForType($type); - $originalValue = $cast(6); - - $insertPayload = $this->clientEncryption->encrypt($originalValue, $encryptOpts); - $decryptedValue = $this->clientEncryption->decrypt($insertPayload); - - /* Decryption of a 64-bit integer will likely result in a scalar int, so - * cast it back to an Int64 before comparing to the original value. */ - if ($type === 'Long' && is_int($decryptedValue)) { - $decryptedValue = $cast($decryptedValue); - } - - /* Use separate assertions for type and equality as assertSame isn't - * suitable for comparing BSON objects and using assertEquals alone - * would disregard scalar type differences. */ - $this->assertSame(get_debug_type($originalValue), get_debug_type($decryptedValue)); - $this->assertEquals($originalValue, $decryptedValue); - } - - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-2-can-find-encrypted-range-and-return-the-maximum - * @dataProvider provideTypeAndRangeOpts - */ - public function testCase2_CanFindEncryptedRangeAndReturnTheMaximum(string $type, array $rangeOpts): void - { - $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'queryType' => ClientEncryption::QUERY_TYPE_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts, - ]; - - $cast = self::getCastCallableForType($type); - $fieldName = 'encrypted' . $type; - - $expr = [ - '$and' => [ - [$fieldName => ['$gte' => $cast(6)]], - [$fieldName => ['$lte' => $cast(200)]], - ], - ]; - - $encryptedExpr = $this->clientEncryption->encryptExpression($expr, $encryptOpts); - $cursor = $this->collection->find($encryptedExpr, ['sort' => ['_id' => 1]]); - - $expectedDocuments = [ - ['_id' => 1, $fieldName => $cast(6)], - ['_id' => 2, $fieldName => $cast(30)], - ['_id' => 3, $fieldName => $cast(200)], - ]; - - $this->assertMultipleDocumentsMatch($expectedDocuments, $cursor); - } - - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-3-can-find-encrypted-range-and-return-the-minimum - * @dataProvider provideTypeAndRangeOpts - */ - public function testCase3_CanFindEncryptedRangeAndReturnTheMinimum(string $type, array $rangeOpts): void - { - $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'queryType' => ClientEncryption::QUERY_TYPE_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts, - ]; - - $cast = self::getCastCallableForType($type); - $fieldName = 'encrypted' . $type; - - $expr = [ - '$and' => [ - [$fieldName => ['$gte' => $cast(0)]], - [$fieldName => ['$lte' => $cast(6)]], - ], - ]; - - $encryptedExpr = $this->clientEncryption->encryptExpression($expr, $encryptOpts); - $cursor = $this->collection->find($encryptedExpr, ['sort' => ['_id' => 1]]); - - $expectedDocuments = [ - ['_id' => 0, $fieldName => $cast(0)], - ['_id' => 1, $fieldName => $cast(6)], - ]; - - $this->assertMultipleDocumentsMatch($expectedDocuments, $cursor); - } - - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-4-can-find-encrypted-range-with-an-open-range-query - * @dataProvider provideTypeAndRangeOpts - */ - public function testCase4_CanFindEncryptedRangeWithAnOpenRangeQuery(string $type, array $rangeOpts): void - { - $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'queryType' => ClientEncryption::QUERY_TYPE_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts, - ]; - - $cast = self::getCastCallableForType($type); - $fieldName = 'encrypted' . $type; - - $expr = ['$and' => [[$fieldName => ['$gt' => $cast(30)]]]]; - - $encryptedExpr = $this->clientEncryption->encryptExpression($expr, $encryptOpts); - $cursor = $this->collection->find($encryptedExpr, ['sort' => ['_id' => 1]]); - $expectedDocuments = [['_id' => 3, $fieldName => $cast(200)]]; - - $this->assertMultipleDocumentsMatch($expectedDocuments, $cursor); - } - - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-5-can-run-an-aggregation-expression-inside-expr - * @dataProvider provideTypeAndRangeOpts - */ - public function testCase5_CanRunAnAggregationExpressionInsideExpr(string $type, array $rangeOpts): void - { - $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'queryType' => ClientEncryption::QUERY_TYPE_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts, - ]; - - $cast = self::getCastCallableForType($type); - $fieldName = 'encrypted' . $type; - $fieldPath = '$' . $fieldName; - - $expr = ['$and' => [['$lt' => [$fieldPath, $cast(30)]]]]; - - $encryptedExpr = $this->clientEncryption->encryptExpression($expr, $encryptOpts); - $cursor = $this->collection->find(['$expr' => $encryptedExpr], ['sort' => ['_id' => 1]]); - - $expectedDocuments = [ - ['_id' => 0, $fieldName => $cast(0)], - ['_id' => 1, $fieldName => $cast(6)], - ]; - - $this->assertMultipleDocumentsMatch($expectedDocuments, $cursor); - } - - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-6-encrypting-a-document-greater-than-the-maximum-errors - * @dataProvider provideTypeAndRangeOpts - */ - public function testCase6_EncryptingADocumentGreaterThanTheMaximumErrors(string $type, array $rangeOpts): void - { - if ($type === 'DecimalNoPrecision' || $type === 'DoubleNoPrecision') { - $this->markTestSkipped('Test is not applicable to "NoPrecision" types'); - } - - $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts, - ]; - - $cast = self::getCastCallableForType($type); - - $this->expectException(EncryptionException::class); - $this->expectExceptionMessage('Value must be greater than or equal to the minimum value and less than or equal to the maximum value'); - $this->clientEncryption->encrypt($cast(201), $encryptOpts); - } - - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-7-encrypting-a-value-of-a-different-type-errors - * @dataProvider provideTypeAndRangeOpts - */ - public function testCase7_EncryptingAValueOfADifferentTypeErrors(string $type, array $rangeOpts): void - { - if ($type === 'DecimalNoPrecision' || $type === 'DoubleNoPrecision') { - /* Explicit encryption relies on min/max range options to check - * types and "NoPrecision" intentionally omits those options. */ - $this->markTestSkipped('Test is not applicable to DoubleNoPrecision and DecimalNoPrecision'); - } - - $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts, - ]; - - $value = $type === 'Int' ? 6.0 : 6; - - $this->expectException(EncryptionException::class); - $this->expectExceptionMessage('expected matching \'min\' and value type'); - $this->clientEncryption->encrypt($value, $encryptOpts); - } - - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-8-setting-precision-errors-if-the-type-is-not-double-or-decimal128 - * @dataProvider provideTypeAndRangeOpts - */ - public function testCase8_SettingPrecisionErrorsIfTheTypeIsNotDoubleOrDecimal128(string $type, array $rangeOpts): void - { - if ($type === 'DecimalNoPrecision' || $type === 'DecimalPrecision' || $type === 'DoubleNoPrecision' || $type === 'DoublePrecision') { - $this->markTestSkipped('Test is not applicable to Double and Decimal types'); - } - - $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts + ['precision' => 2], - ]; - - $cast = self::getCastCallableForType($type); - - $this->expectException(EncryptionException::class); - $this->expectExceptionMessage('expected \'precision\' to be set with double or decimal128 index'); - $this->clientEncryption->encrypt($cast(6), $encryptOpts); - } - - private function assertMultipleDocumentsMatch(array $expectedDocuments, Iterator $actualDocuments): void - { - $mi = new MultipleIterator(MultipleIterator::MIT_NEED_ANY); - $mi->attachIterator(new ArrayIterator($expectedDocuments)); - $mi->attachIterator($actualDocuments); - - foreach ($mi as $documents) { - [$expectedDocument, $actualDocument] = $documents; - $this->assertNotNull($expectedDocument); - $this->assertNotNull($actualDocument); - - $this->assertDocumentsMatch($expectedDocument, $actualDocument); - } - } - - private static function getCastCallableForType(string $type): callable - { - switch ($type) { - case 'DecimalNoPrecision': - case 'DecimalPrecision': - return fn (int $value) => new Decimal128((string) $value); - - case 'DoubleNoPrecision': - case 'DoublePrecision': - return fn (int $value) => (double) $value; - - case 'Date': - return fn (int $value) => new UTCDateTime($value); - - case 'Int': - return fn (int $value) => $value; - - case 'Long': - return fn (int $value) => new Int64($value); - - default: - throw new LogicException('Unsupported type: ' . $type); - } - } -} diff --git a/tests/SpecTests/ClientSideEncryptionSpecTest.php b/tests/SpecTests/ClientSideEncryptionSpecTest.php index 574cf4f23..447f7b385 100644 --- a/tests/SpecTests/ClientSideEncryptionSpecTest.php +++ b/tests/SpecTests/ClientSideEncryptionSpecTest.php @@ -38,12 +38,10 @@ use function in_array; use function iterator_to_array; use function json_decode; -use function phpversion; use function sprintf; use function str_repeat; use function str_starts_with; use function substr; -use function version_compare; use const JSON_THROW_ON_ERROR; @@ -167,10 +165,6 @@ public function testClientSideEncryption(stdClass $test, ?array $runOn, array $d $this->markTestIncomplete(self::$incompleteTests[$this->dataDescription()]); } - if (str_starts_with($this->dataDescription(), 'fle2v2-Range-') && version_compare(phpversion('mongodb'), '1.20.0dev', '>=')) { - $this->markTestIncomplete('Range protocol V1 is not supported by ext-mongodb 1.20+'); - } - if (isset($runOn)) { $this->checkServerRequirements($runOn); } diff --git a/tests/SpecTests/CommandExpectations.php b/tests/SpecTests/CommandExpectations.php index 7c9a946f0..8adc8cd5c 100644 --- a/tests/SpecTests/CommandExpectations.php +++ b/tests/SpecTests/CommandExpectations.php @@ -40,25 +40,15 @@ class CommandExpectations implements CommandSubscriber private function __construct(private Client $observedClient, array $events) { foreach ($events as $event) { - switch (key((array) $event)) { - case 'command_failed_event': - // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps - $this->expectedEvents[] = [$event->command_failed_event, CommandFailedEvent::class]; - break; - - case 'command_started_event': - // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps - $this->expectedEvents[] = [$event->command_started_event, CommandStartedEvent::class]; - break; - - case 'command_succeeded_event': - // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps - $this->expectedEvents[] = [$event->command_succeeded_event, CommandSucceededEvent::class]; - break; - - default: - throw new LogicException('Unsupported event type: ' . key($event)); - } + $this->expectedEvents[] = match (key((array) $event)) { + // phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps + 'command_failed_event' => [$event->command_failed_event, CommandFailedEvent::class], + // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps + 'command_started_event' => [$event->command_started_event, CommandStartedEvent::class], + // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps + 'command_succeeded_event' => [$event->command_succeeded_event, CommandSucceededEvent::class], + default => throw new LogicException('Unsupported event type: ' . key($event)), + }; } } diff --git a/tests/SpecTests/Context.php b/tests/SpecTests/Context.php index 1341e65da..c3704900f 100644 --- a/tests/SpecTests/Context.php +++ b/tests/SpecTests/Context.php @@ -358,18 +358,11 @@ public function replaceArgumentSessionPlaceholder(array &$args): void return; } - switch ($args['session']) { - case 'session0': - $args['session'] = $this->session0; - break; - - case 'session1': - $args['session'] = $this->session1; - break; - - default: - throw new LogicException('Unsupported session placeholder: ' . $args['session']); - } + $args['session'] = match ($args['session']) { + 'session0' => $this->session0, + 'session1' => $this->session1, + default => throw new LogicException('Unsupported session placeholder: ' . $args['session']), + }; } /** @@ -386,18 +379,11 @@ public function replaceCommandSessionPlaceholder(stdClass $command): void return; } - switch ($command->lsid) { - case 'session0': - $command->lsid = $this->session0Lsid; - break; - - case 'session1': - $command->lsid = $this->session1Lsid; - break; - - default: - throw new LogicException('Unsupported session placeholder: ' . $command->lsid); - } + $command->lsid = match ($command->lsid) { + 'session0' => $this->session0Lsid, + 'session1' => $this->session1Lsid, + default => throw new LogicException('Unsupported session placeholder: ' . $command->lsid), + }; } public function selectCollection($databaseName, $collectionName, array $collectionOptions = [], array $databaseOptions = []) diff --git a/tests/SpecTests/FunctionalTestCase.php b/tests/SpecTests/FunctionalTestCase.php index cd02f5908..b4ca35a9e 100644 --- a/tests/SpecTests/FunctionalTestCase.php +++ b/tests/SpecTests/FunctionalTestCase.php @@ -126,18 +126,11 @@ protected function assertOutcomeCollectionData(array $expectedDocuments, int $re $this->assertNotNull($expectedDocument); $this->assertNotNull($actualDocument); - switch ($resultExpectation) { - case ResultExpectation::ASSERT_SAME_DOCUMENT: - $this->assertSameDocument($expectedDocument, $actualDocument); - break; - - case ResultExpectation::ASSERT_DOCUMENTS_MATCH: - $this->assertDocumentsMatch($expectedDocument, $actualDocument); - break; - - default: - $this->fail(sprintf('Invalid result expectation "%d" for %s', $resultExpectation, __METHOD__)); - } + match ($resultExpectation) { + ResultExpectation::ASSERT_SAME_DOCUMENT => $this->assertSameDocument($expectedDocument, $actualDocument), + ResultExpectation::ASSERT_DOCUMENTS_MATCH => $this->assertDocumentsMatch($expectedDocument, $actualDocument), + default => $this->fail(sprintf('Invalid result expectation "%d" for %s', $resultExpectation, __METHOD__)), + }; } } @@ -284,18 +277,12 @@ private function isServerlessRequirementSatisfied(?string $serverlessMode): bool return true; } - switch ($serverlessMode) { - case self::SERVERLESS_ALLOW: - return true; - - case self::SERVERLESS_FORBID: - return ! static::isServerless(); - - case self::SERVERLESS_REQUIRE: - return static::isServerless(); - } - - throw new UnexpectedValueException(sprintf('Invalid serverless requirement "%s" found.', $serverlessMode)); + return match ($serverlessMode) { + self::SERVERLESS_ALLOW => true, + self::SERVERLESS_FORBID => ! static::isServerless(), + self::SERVERLESS_REQUIRE => static::isServerless(), + default => throw new UnexpectedValueException(sprintf('Invalid serverless requirement "%s" found.', $serverlessMode)), + }; } /** diff --git a/tests/UnifiedSpecTests/Constraint/IsBsonType.php b/tests/UnifiedSpecTests/Constraint/IsBsonType.php index e2f73e807..7d17a9af7 100644 --- a/tests/UnifiedSpecTests/Constraint/IsBsonType.php +++ b/tests/UnifiedSpecTests/Constraint/IsBsonType.php @@ -90,77 +90,32 @@ public static function anyOf(string ...$types): Constraint private function doMatches($other): bool { - switch ($this->type) { - case 'double': - return is_float($other); - - case 'string': - return is_string($other); - - case 'object': - return self::isObject($other); - - case 'array': - return self::isArray($other); - - case 'binData': - return $other instanceof BinaryInterface; - - case 'undefined': - return $other instanceof Undefined; - - case 'objectId': - return $other instanceof ObjectIdInterface; - - case 'bool': - return is_bool($other); - - case 'date': - return $other instanceof UTCDateTimeInterface; - - case 'null': - return $other === null; - - case 'regex': - return $other instanceof RegexInterface; - - case 'dbPointer': - return $other instanceof DBPointer; - - case 'javascript': - return $other instanceof JavascriptInterface && $other->getScope() === null; - - case 'symbol': - return $other instanceof Symbol; - - case 'javascriptWithScope': - return $other instanceof JavascriptInterface && $other->getScope() !== null; - - case 'int': - return is_int($other); - - case 'timestamp': - return $other instanceof TimestampInterface; - - case 'long': - return is_int($other) || $other instanceof Int64; - - case 'decimal': - return $other instanceof Decimal128Interface; - - case 'minKey': - return $other instanceof MinKeyInterface; - - case 'maxKey': - return $other instanceof MaxKeyInterface; - - case 'number': - return is_int($other) || $other instanceof Int64 || is_float($other) || $other instanceof Decimal128Interface; - - default: - // This should already have been caught in the constructor - throw new LogicException('Unsupported type: ' . $this->type); - } + return match ($this->type) { + 'double' => is_float($other), + 'string' => is_string($other), + 'object' => self::isObject($other), + 'array' => self::isArray($other), + 'binData' => $other instanceof BinaryInterface, + 'undefined' => $other instanceof Undefined, + 'objectId' => $other instanceof ObjectIdInterface, + 'bool' => is_bool($other), + 'date' => $other instanceof UTCDateTimeInterface, + 'null' => $other === null, + 'regex' => $other instanceof RegexInterface, + 'dbPointer' => $other instanceof DBPointer, + 'javascript' => $other instanceof JavascriptInterface && $other->getScope() === null, + 'symbol' => $other instanceof Symbol, + 'javascriptWithScope' => $other instanceof JavascriptInterface && $other->getScope() !== null, + 'int' => is_int($other), + 'timestamp' => $other instanceof TimestampInterface, + 'long' => is_int($other) || $other instanceof Int64, + 'decimal' => $other instanceof Decimal128Interface, + 'minKey' => $other instanceof MinKeyInterface, + 'maxKey' => $other instanceof MaxKeyInterface, + 'number' => is_int($other) || $other instanceof Int64 || is_float($other) || $other instanceof Decimal128Interface, + // This should already have been caught in the constructor + default => throw new LogicException('Unsupported type: ' . $this->type), + }; } private function doToString(): string diff --git a/tests/UnifiedSpecTests/Context.php b/tests/UnifiedSpecTests/Context.php index 2210cad75..56f68adc0 100644 --- a/tests/UnifiedSpecTests/Context.php +++ b/tests/UnifiedSpecTests/Context.php @@ -95,34 +95,15 @@ public function createEntities(array $entities): void $id = $def->id ?? null; assertIsString($id); - switch ($type) { - case 'client': - $this->createClient($id, $def); - break; - - case 'clientEncryption': - $this->createClientEncryption($id, $def); - break; - - case 'database': - $this->createDatabase($id, $def); - break; - - case 'collection': - $this->createCollection($id, $def); - break; - - case 'session': - $this->createSession($id, $def); - break; - - case 'bucket': - $this->createBucket($id, $def); - break; - - default: - throw new LogicException('Unsupported entity type: ' . $type); - } + match ($type) { + 'client' => $this->createClient($id, $def), + 'clientEncryption' => $this->createClientEncryption($id, $def), + 'database' => $this->createDatabase($id, $def), + 'collection' => $this->createCollection($id, $def), + 'session' => $this->createSession($id, $def), + 'bucket' => $this->createBucket($id, $def), + default => throw new LogicException('Unsupported entity type: ' . $type), + }; } } diff --git a/tests/UnifiedSpecTests/UnifiedTestRunner.php b/tests/UnifiedSpecTests/UnifiedTestRunner.php index 605eb2cac..39ea3832d 100644 --- a/tests/UnifiedSpecTests/UnifiedTestRunner.php +++ b/tests/UnifiedSpecTests/UnifiedTestRunner.php @@ -299,25 +299,16 @@ private function getServerVersion(): string */ private function getTopology(): string { - switch ($this->getPrimaryServer()->getType()) { - case Server::TYPE_STANDALONE: - return RunOnRequirement::TOPOLOGY_SINGLE; - - case Server::TYPE_RS_PRIMARY: - return RunOnRequirement::TOPOLOGY_REPLICASET; - - case Server::TYPE_MONGOS: - /* Since MongoDB 3.6, all sharded clusters use replica sets. The - * unified test format deprecated use of "sharded-replicaset" in - * tests but we should still identify as such. */ - return RunOnRequirement::TOPOLOGY_SHARDED_REPLICASET; - - case Server::TYPE_LOAD_BALANCER: - return RunOnRequirement::TOPOLOGY_LOAD_BALANCED; - - default: - throw new UnexpectedValueException('Topology is neither single nor RS nor sharded'); - } + return match ($this->getPrimaryServer()->getType()) { + Server::TYPE_STANDALONE => RunOnRequirement::TOPOLOGY_SINGLE, + Server::TYPE_RS_PRIMARY => RunOnRequirement::TOPOLOGY_REPLICASET, + /* Since MongoDB 3.6, all sharded clusters use replica sets. The + * unified test format deprecated use of "sharded-replicaset" in + * tests but we should still identify as such. */ + Server::TYPE_MONGOS => RunOnRequirement::TOPOLOGY_SHARDED_REPLICASET, + Server::TYPE_LOAD_BALANCER => RunOnRequirement::TOPOLOGY_LOAD_BALANCED, + default => throw new UnexpectedValueException('Topology is neither single nor RS nor sharded'), + }; } private function isAtlasDataLake(): bool