diff --git a/tests/acceptance/features/apiNotification/notification.feature b/tests/acceptance/features/apiNotification/notification.feature index b9e13fc47e..c6a55ca7d2 100644 --- a/tests/acceptance/features/apiNotification/notification.feature +++ b/tests/acceptance/features/apiNotification/notification.feature @@ -85,7 +85,7 @@ Feature: Notification }, "id": { "type": "string", - "enim": ["%user_id%"] + "pattern": "^%user_id_pattern%$" }, "name": { "type": "string", diff --git a/tests/acceptance/features/apiNotification/spaceNotification.feature b/tests/acceptance/features/apiNotification/spaceNotification.feature index e03a080b48..db68bd123f 100644 --- a/tests/acceptance/features/apiNotification/spaceNotification.feature +++ b/tests/acceptance/features/apiNotification/spaceNotification.feature @@ -97,9 +97,7 @@ Feature: Notification }, "id": { "type": "string", - "enim": [ - "%user_id%" - ] + "pattern": "^%user_id_pattern%$" }, "name": { "type": "string", @@ -232,9 +230,7 @@ Feature: Notification }, "id": { "type": "string", - "enim": [ - "%user_id%" - ] + "pattern": "^%user_id_pattern%$" }, "name": { "type": "string", @@ -368,9 +364,7 @@ Feature: Notification }, "id": { "type": "string", - "enim": [ - "%user_id%" - ] + "pattern": "^%user_id_pattern%$" }, "name": { "type": "string", diff --git a/tests/acceptance/features/apiSharingNg/linkShare.feature b/tests/acceptance/features/apiSharingNg/linkShare.feature index 252b0a9b8d..836514b704 100644 --- a/tests/acceptance/features/apiSharingNg/linkShare.feature +++ b/tests/acceptance/features/apiSharingNg/linkShare.feature @@ -553,8 +553,6 @@ Feature: Create a share link for a resource }, "link": { "type": "object", - "minItems": 5, - "maxItems": 5, "required": [ "@libre.graph.displayName", "@libre.graph.quickLink", diff --git a/tests/acceptance/features/apiSharingNg/updateShareInvitations.feature b/tests/acceptance/features/apiSharingNg/updateShareInvitations.feature index 22be6872dd..610d13f031 100644 --- a/tests/acceptance/features/apiSharingNg/updateShareInvitations.feature +++ b/tests/acceptance/features/apiSharingNg/updateShareInvitations.feature @@ -102,8 +102,6 @@ Feature: Update permission of a share """ { "type": "object", - "minItems": 3, - "maxItems": 3, "required": [ "grantedToV2", "id", diff --git a/tests/acceptance/features/bootstrap/FeatureContext.php b/tests/acceptance/features/bootstrap/FeatureContext.php index f6e76fe000..14112957ae 100644 --- a/tests/acceptance/features/bootstrap/FeatureContext.php +++ b/tests/acceptance/features/bootstrap/FeatureContext.php @@ -56,6 +56,12 @@ class FeatureContext extends BehatVariablesContext { use Sharing; use WebDav; + /** + * json schema validator keywords + * See: https://json-schema.org/draft-06/draft-wright-json-schema-validation-01#rfc.section.6 + */ + private array $jsonSchemaValidators = []; + /** * Unix timestamp seconds */ @@ -572,6 +578,8 @@ class FeatureContext extends BehatVariablesContext { $this->publicLinkSharePassword = $publicLinkSharePasswordFromEnvironment; } $this->originalAdminPassword = $this->adminPassword; + + $this->jsonSchemaValidators = \array_keys(JsonSchema::properties()->getDataKeyMap()); } /** @@ -1206,6 +1214,56 @@ class FeatureContext extends BehatVariablesContext { return $a; } + /** + * @param JsonSchema $schemaObj + * + * @return void + * @throws Exception + */ + private function checkInvalidValidator(JsonSchema $schemaObj): void { + $validators = \array_keys((array)$schemaObj->jsonSerialize()); + foreach ($validators as $validator) { + Assert::assertContains(\ltrim($validator, "$"), $this->jsonSchemaValidators, "Invalid schema validator: '$validator'"); + } + } + + /** + * Validates against the requirements that object schema should adhere to + * + * @param JsonSchema $schemaObj + * + * @return void + * @throws Exception + */ + public function validateSchemaObject(JsonSchema $schemaObj): void { + $this->checkInvalidValidator($schemaObj); + + if ($schemaObj->type && $schemaObj->type !== "object") { + return; + } + + $notAllowedValidators = ["items", "maxItems", "minItems", "uniqueItems"]; + + // check invalid validators + foreach ($notAllowedValidators as $validator) { + Assert::assertTrue(null === $schemaObj->$validator, "'$validator' should not be used with object type"); + } + + $propNames = $schemaObj->getPropertyNames(); + $props = $schemaObj->getProperties(); + foreach ($propNames as $propName) { + switch ($props->type) { + case "array": + $this->validateSchemaArray($props->$propName); + break; + default: + break; + } + // traverse for nested properties + $this->validateSchemaObject($props->$propName); + } + } + /** * Validates against the requirements that array schema should adhere to * @@ -1214,7 +1272,13 @@ class FeatureContext extends BehatVariablesContext { * @return void * @throws Exception */ - private function validateSchemaArrayEntries(JsonSchema $schemaObj): void { + private function validateSchemaArray(JsonSchema $schemaObj): void { + $this->checkInvalidValidator($schemaObj); + + if ($schemaObj->type && $schemaObj->type !== "array") { + return; + } + $hasTwoElementValidator = ($schemaObj->enum && $schemaObj->const) || ($schemaObj->enum && $schemaObj->items) || ($schemaObj->const && $schemaObj->items); Assert::assertFalse($hasTwoElementValidator, "'items', 'enum' and 'const' should not be used together"); if ($schemaObj->enum || $schemaObj->const) { @@ -1224,20 +1288,26 @@ class FeatureContext extends BehatVariablesContext { $requiredValidators = ["maxItems", "minItems"]; $optionalValidators = ["items", "uniqueItems"]; + $notAllowedValidators = ["properties", "minProperties", "maxProperties", "required"]; $errMsg = "'%s' is required for array assertion"; - // validate required keywords + // check invalid validators + foreach ($notAllowedValidators as $validator) { + Assert::assertTrue($schemaObj->$validator === null, "'$validator' should not be used with array type"); + } + + // check required validators foreach ($requiredValidators as $validator) { Assert::assertNotNull($schemaObj->$validator, \sprintf($errMsg, $validator)); } Assert::assertEquals($schemaObj->minItems, $schemaObj->maxItems, "'minItems' and 'maxItems' should be equal for strict assertion"); - // validate optional keywords + // check optional validators foreach ($optionalValidators as $validator) { $value = $schemaObj->$validator; switch ($validator) { - case 'items': + case "items": if ($schemaObj->maxItems === 0) { break; } @@ -1267,6 +1337,15 @@ class FeatureContext extends BehatVariablesContext { break; } } + + $items = $schemaObj->items; + if ($items !== null && $items->oneOf !== null) { + foreach ($items->oneOf as $oneOfItem) { + $this->validateSchemaObject($oneOfItem); + } + } elseif ($items !== null) { + $this->validateSchemaObject($items); + } } /** @@ -1278,29 +1357,17 @@ class FeatureContext extends BehatVariablesContext { * @throws Exception */ public function validateSchemaRequirements(JsonSchema $schema): void { - $propNames = $schema->getPropertyNames(); - $props = $schema->getProperties(); - foreach ($propNames as $propName) { - switch ($props->$propName->type) { - case 'array': - $this->validateSchemaArrayEntries($props->$propName); - $items = $props->$propName->items; - if ($items && $items->oneOf) { - foreach ($items->oneOf as $oneOfItem) { - $this->validateSchemaRequirements($oneOfItem); - } - break; - } elseif ($items) { - $this->validateSchemaRequirements($items); - } - break; - default: - break; - } - // traverse for nested properties - if ($props->$propName->getProperties()) { - $this->validateSchemaRequirements($props->$propName); - } + Assert::assertNotNull($schema->type, "'type' is required for root level schema"); + + switch ($schema->type) { + case "object": + $this->validateSchemaObject($schema); + break; + case "array": + $this->validateSchemaArray($schema); + break; + default: + break; } }