diff --git a/src/ConstraintCheck/Helper/ConstraintParameterParser.php b/src/ConstraintCheck/Helper/ConstraintParameterParser.php index c5b5882d..9b8d70c2 100644 --- a/src/ConstraintCheck/Helper/ConstraintParameterParser.php +++ b/src/ConstraintCheck/Helper/ConstraintParameterParser.php @@ -1,984 +1,976 @@ config = $config; $this->snakDeserializer = $factory->newSnakDeserializer(); - $this->conceptBaseUris = $conceptBaseUris; + $this->unitItemConceptBaseUri = $unitItemConceptBaseUri; } /** * Check if any errors are recorded in the constraint parameters. * @param array $parameters * @throws ConstraintParameterException */ public function checkError( array $parameters ) { if ( array_key_exists( '@error', $parameters ) ) { $error = $parameters['@error']; if ( array_key_exists( 'toolong', $error ) && $error['toolong'] ) { $msg = 'wbqc-violation-message-parameters-error-toolong'; } else { $msg = 'wbqc-violation-message-parameters-error-unknown'; } throw new ConstraintParameterException( new ViolationMessage( $msg ) ); } } /** * Require that $parameters contains exactly one $parameterId parameter. * @param array $parameters * @param string $parameterId * @throws ConstraintParameterException */ private function requireSingleParameter( array $parameters, $parameterId ) { if ( count( $parameters[$parameterId] ) !== 1 ) { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-single' ) ) ->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ); } } /** * Require that $snak is a {@link PropertyValueSnak}. * @param Snak $snak * @param string $parameterId * @return void * @throws ConstraintParameterException */ private function requireValueParameter( Snak $snak, $parameterId ) { if ( !( $snak instanceof PropertyValueSnak ) ) { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-value' ) ) ->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ); } } /** * Parse a single entity ID parameter. * @param array $snakSerialization * @param string $parameterId * @throws ConstraintParameterException * @return EntityId */ private function parseEntityIdParameter( array $snakSerialization, $parameterId ) { $snak = $this->snakDeserializer->deserialize( $snakSerialization ); $this->requireValueParameter( $snak, $parameterId ); $value = $snak->getDataValue(); if ( $value instanceof EntityIdValue ) { return $value->getEntityId(); } else { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-entity' ) ) ->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ->withDataValue( $value, Role::CONSTRAINT_PARAMETER_VALUE ) ); } } /** * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} * @param string $constraintTypeItemId used in error messages * @throws ConstraintParameterException if the parameter is invalid or missing * @return string[] class entity ID serializations */ public function parseClassParameter( array $constraintParameters, $constraintTypeItemId ) { $this->checkError( $constraintParameters ); $classId = $this->config->get( 'WBQualityConstraintsClassId' ); if ( !array_key_exists( $classId, $constraintParameters ) ) { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) ->withEntityId( new PropertyId( $classId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ); } $classes = []; foreach ( $constraintParameters[$classId] as $class ) { $classes[] = $this->parseEntityIdParameter( $class, $classId )->getSerialization(); } return $classes; } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @param string $constraintTypeItemId used in error messages * @throws ConstraintParameterException if the parameter is invalid or missing * @return string 'instance', 'subclass', or 'instanceOrSubclass' */ public function parseRelationParameter( array $constraintParameters, $constraintTypeItemId ) { $this->checkError( $constraintParameters ); $relationId = $this->config->get( 'WBQualityConstraintsRelationId' ); if ( !array_key_exists( $relationId, $constraintParameters ) ) { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) ->withEntityId( new PropertyId( $relationId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ); } $this->requireSingleParameter( $constraintParameters, $relationId ); $relationEntityId = $this->parseEntityIdParameter( $constraintParameters[$relationId][0], $relationId ); $instanceId = $this->config->get( 'WBQualityConstraintsInstanceOfRelationId' ); $subclassId = $this->config->get( 'WBQualityConstraintsSubclassOfRelationId' ); $instanceOrSubclassId = $this->config->get( 'WBQualityConstraintsInstanceOrSubclassOfRelationId' ); switch ( $relationEntityId ) { case $instanceId: return 'instance'; case $subclassId: return 'subclass'; case $instanceOrSubclassId: return 'instanceOrSubclass'; default: throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-oneof' ) ) ->withEntityId( new PropertyId( $relationId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ->withEntityIdList( [ new ItemId( $instanceId ), new ItemId( $subclassId ), new ItemId( $instanceOrSubclassId ), ], Role::CONSTRAINT_PARAMETER_VALUE ) ); } } /** * Parse a single property ID parameter. * @param array $snakSerialization * @param string $parameterId used in error messages * @throws ConstraintParameterException * @return PropertyId */ private function parsePropertyIdParameter( array $snakSerialization, $parameterId ) { $snak = $this->snakDeserializer->deserialize( $snakSerialization ); $this->requireValueParameter( $snak, $parameterId ); $value = $snak->getDataValue(); if ( $value instanceof EntityIdValue ) { $id = $value->getEntityId(); if ( $id instanceof PropertyId ) { return $id; } } throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-property' ) ) ->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ->withDataValue( $value, Role::CONSTRAINT_PARAMETER_VALUE ) ); } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @param string $constraintTypeItemId used in error messages * * @throws ConstraintParameterException if the parameter is invalid or missing * @return PropertyId */ public function parsePropertyParameter( array $constraintParameters, $constraintTypeItemId ) { $this->checkError( $constraintParameters ); $propertyId = $this->config->get( 'WBQualityConstraintsPropertyId' ); if ( !array_key_exists( $propertyId, $constraintParameters ) ) { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) ->withEntityId( new PropertyId( $propertyId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ); } $this->requireSingleParameter( $constraintParameters, $propertyId ); return $this->parsePropertyIdParameter( $constraintParameters[$propertyId][0], $propertyId ); } private function parseItemIdParameter( PropertyValueSnak $snak, $parameterId ) { $dataValue = $snak->getDataValue(); if ( $dataValue instanceof EntityIdValue ) { $entityId = $dataValue->getEntityId(); if ( $entityId instanceof ItemId ) { return ItemIdSnakValue::fromItemId( $entityId ); } } throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-item' ) ) ->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ->withDataValue( $dataValue, Role::CONSTRAINT_PARAMETER_VALUE ) ); } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @param string $constraintTypeItemId used in error messages * @param bool $required whether the parameter is required (error if absent) or not ([] if absent) * @param string|null $parameterId the property ID to use, defaults to 'qualifier of property constraint' * @throws ConstraintParameterException if the parameter is invalid or missing * @return ItemIdSnakValue[] array of values */ public function parseItemsParameter( array $constraintParameters, $constraintTypeItemId, $required, $parameterId = null ) { $this->checkError( $constraintParameters ); if ( $parameterId === null ) { $parameterId = $this->config->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); } if ( !array_key_exists( $parameterId, $constraintParameters ) ) { if ( $required ) { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) ->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ); } else { return []; } } $values = []; foreach ( $constraintParameters[$parameterId] as $parameter ) { $snak = $this->snakDeserializer->deserialize( $parameter ); switch ( true ) { case $snak instanceof PropertyValueSnak: $values[] = $this->parseItemIdParameter( $snak, $parameterId ); break; case $snak instanceof PropertySomeValueSnak: $values[] = ItemIdSnakValue::someValue(); break; case $snak instanceof PropertyNoValueSnak: $values[] = ItemIdSnakValue::noValue(); break; } } return $values; } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @param string $constraintTypeItemId used in error messages * @throws ConstraintParameterException if the parameter is invalid or missing * @return PropertyId[] */ public function parsePropertiesParameter( array $constraintParameters, $constraintTypeItemId ) { $this->checkError( $constraintParameters ); $propertyId = $this->config->get( 'WBQualityConstraintsPropertyId' ); if ( !array_key_exists( $propertyId, $constraintParameters ) ) { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) ->withEntityId( new PropertyId( $propertyId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ); } $parameters = $constraintParameters[$propertyId]; if ( count( $parameters ) === 1 && $this->snakDeserializer->deserialize( $parameters[0] ) instanceof PropertyNoValueSnak ) { return []; } $properties = []; foreach ( $parameters as $parameter ) { $properties[] = $this->parsePropertyIdParameter( $parameter, $propertyId ); } return $properties; } /** * @param array $snakSerialization * @param string $parameterId * @throws ConstraintParameterException * @return DataValue|null */ private function parseValueOrNoValueParameter( array $snakSerialization, $parameterId ) { $snak = $this->snakDeserializer->deserialize( $snakSerialization ); if ( $snak instanceof PropertyValueSnak ) { return $snak->getDataValue(); } elseif ( $snak instanceof PropertyNoValueSnak ) { return null; } else { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-value-or-novalue' ) ) ->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ); } } /** * @param array $snakSerialization * @param string $parameterId * @return DataValue|null */ private function parseValueOrNoValueOrNowParameter( array $snakSerialization, $parameterId ) { try { return $this->parseValueOrNoValueParameter( $snakSerialization, $parameterId ); } catch ( ConstraintParameterException $e ) { // unknown value means “now” return new NowValue(); } } /** * Checks whether there is exactly one non-null quantity with the given unit. * @param ?DataValue $min * @param ?DataValue $max * @param string $unit * @return bool */ private function exactlyOneQuantityWithUnit( ?DataValue $min, ?DataValue $max, $unit ) { if ( !( $min instanceof UnboundedQuantityValue ) || !( $max instanceof UnboundedQuantityValue ) ) { return false; } return ( $min->getUnit() === $unit ) !== ( $max->getUnit() === $unit ); } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @param string $minimumId * @param string $maximumId * @param string $constraintTypeItemId used in error messages * @param string $type 'quantity' or 'time' (can be data type or data value type) * * @throws ConstraintParameterException if the parameter is invalid or missing * @return DataValue[] if the parameter is invalid or missing */ private function parseRangeParameter( array $constraintParameters, $minimumId, $maximumId, $constraintTypeItemId, $type ) { $this->checkError( $constraintParameters ); if ( !array_key_exists( $minimumId, $constraintParameters ) || !array_key_exists( $maximumId, $constraintParameters ) ) { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-range-parameters-needed' ) ) ->withDataValueType( $type ) ->withEntityId( new PropertyId( $minimumId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ->withEntityId( new PropertyId( $maximumId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) ); } $this->requireSingleParameter( $constraintParameters, $minimumId ); $this->requireSingleParameter( $constraintParameters, $maximumId ); $parseFunction = $type === 'time' ? 'parseValueOrNoValueOrNowParameter' : 'parseValueOrNoValueParameter'; $min = $this->$parseFunction( $constraintParameters[$minimumId][0], $minimumId ); $max = $this->$parseFunction( $constraintParameters[$maximumId][0], $maximumId ); $yearUnit = $this->config->get( 'WBQualityConstraintsYearUnit' ); if ( $this->exactlyOneQuantityWithUnit( $min, $max, $yearUnit ) ) { throw new ConstraintParameterException( new ViolationMessage( 'wbqc-violation-message-range-parameters-one-year' ) ); } if ( $min === null && $max === null || $min !== null && $max !== null && $min->equals( $max ) ) { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-range-parameters-same' ) ) ->withEntityId( new PropertyId( $minimumId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ->withEntityId( new PropertyId( $maximumId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ); } return [ $min, $max ]; } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @param string $constraintTypeItemId used in error messages * * @throws ConstraintParameterException if the parameter is invalid or missing * @return DataValue[] a pair of two data values, either of which may be null to signify an open range */ public function parseQuantityRangeParameter( array $constraintParameters, $constraintTypeItemId ) { return $this->parseRangeParameter( $constraintParameters, $this->config->get( 'WBQualityConstraintsMinimumQuantityId' ), $this->config->get( 'WBQualityConstraintsMaximumQuantityId' ), $constraintTypeItemId, 'quantity' ); } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @param string $constraintTypeItemId used in error messages * * @throws ConstraintParameterException if the parameter is invalid or missing * @return DataValue[] a pair of two data values, either of which may be null to signify an open range */ public function parseTimeRangeParameter( array $constraintParameters, $constraintTypeItemId ) { return $this->parseRangeParameter( $constraintParameters, $this->config->get( 'WBQualityConstraintsMinimumDateId' ), $this->config->get( 'WBQualityConstraintsMaximumDateId' ), $constraintTypeItemId, 'time' ); } /** * Parse a single string parameter. * @param array $snakSerialization * @param string $parameterId * @throws ConstraintParameterException * @return string */ private function parseStringParameter( array $snakSerialization, $parameterId ) { $snak = $this->snakDeserializer->deserialize( $snakSerialization ); $this->requireValueParameter( $snak, $parameterId ); $value = $snak->getDataValue(); if ( $value instanceof StringValue ) { return $value->getValue(); } else { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-string' ) ) ->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ->withDataValue( $value, Role::CONSTRAINT_PARAMETER_VALUE ) ); } } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @param string $constraintTypeItemId used in error messages * @throws ConstraintParameterException if the parameter is invalid or missing * @return string */ public function parseNamespaceParameter( array $constraintParameters, $constraintTypeItemId ) { $this->checkError( $constraintParameters ); $namespaceId = $this->config->get( 'WBQualityConstraintsNamespaceId' ); if ( !array_key_exists( $namespaceId, $constraintParameters ) ) { return ''; } $this->requireSingleParameter( $constraintParameters, $namespaceId ); return $this->parseStringParameter( $constraintParameters[$namespaceId][0], $namespaceId ); } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @param string $constraintTypeItemId used in error messages * @throws ConstraintParameterException if the parameter is invalid or missing * @return string */ public function parseFormatParameter( array $constraintParameters, $constraintTypeItemId ) { $this->checkError( $constraintParameters ); $formatId = $this->config->get( 'WBQualityConstraintsFormatAsARegularExpressionId' ); if ( !array_key_exists( $formatId, $constraintParameters ) ) { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) ->withEntityId( new PropertyId( $formatId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ); } $this->requireSingleParameter( $constraintParameters, $formatId ); return $this->parseStringParameter( $constraintParameters[$formatId][0], $formatId ); } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @throws ConstraintParameterException if the parameter is invalid * @return EntityId[] */ public function parseExceptionParameter( array $constraintParameters ) { $this->checkError( $constraintParameters ); $exceptionId = $this->config->get( 'WBQualityConstraintsExceptionToConstraintId' ); if ( !array_key_exists( $exceptionId, $constraintParameters ) ) { return []; } return array_map( function( $snakSerialization ) use ( $exceptionId ) { return $this->parseEntityIdParameter( $snakSerialization, $exceptionId ); }, $constraintParameters[$exceptionId] ); } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @throws ConstraintParameterException if the parameter is invalid * @return string|null 'mandatory', 'suggestion' or null */ public function parseConstraintStatusParameter( array $constraintParameters ) { $this->checkError( $constraintParameters ); $constraintStatusId = $this->config->get( 'WBQualityConstraintsConstraintStatusId' ); if ( !array_key_exists( $constraintStatusId, $constraintParameters ) ) { return null; } $mandatoryId = $this->config->get( 'WBQualityConstraintsMandatoryConstraintId' ); $supportedStatuses = [ new ItemId( $mandatoryId ) ]; if ( $this->config->get( 'WBQualityConstraintsEnableSuggestionConstraintStatus' ) ) { $suggestionId = $this->config->get( 'WBQualityConstraintsSuggestionConstraintId' ); $supportedStatuses[] = new ItemId( $suggestionId ); } else { $suggestionId = null; } $this->requireSingleParameter( $constraintParameters, $constraintStatusId ); $snak = $this->snakDeserializer->deserialize( $constraintParameters[$constraintStatusId][0] ); $this->requireValueParameter( $snak, $constraintStatusId ); '@phan-var \Wikibase\DataModel\Snak\PropertyValueSnak $snak'; $dataValue = $snak->getDataValue(); '@phan-var EntityIdValue $dataValue'; $entityId = $dataValue->getEntityId(); $statusId = $entityId->getSerialization(); if ( $statusId === $mandatoryId ) { return 'mandatory'; } elseif ( $statusId === $suggestionId ) { return 'suggestion'; } else { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-oneof' ) ) ->withEntityId( new PropertyId( $constraintStatusId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ->withEntityIdList( $supportedStatuses, Role::CONSTRAINT_PARAMETER_VALUE ) ); } } /** * Require that $dataValue is a {@link MonolingualTextValue}. * @param DataValue $dataValue * @param string $parameterId * @return void * @throws ConstraintParameterException */ private function requireMonolingualTextParameter( DataValue $dataValue, $parameterId ) { if ( !( $dataValue instanceof MonolingualTextValue ) ) { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-monolingualtext' ) ) ->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ->withDataValue( $dataValue, Role::CONSTRAINT_PARAMETER_VALUE ) ); } } /** * Parse a series of monolingual text snaks (serialized) into a map from language code to string. * * @param array $snakSerializations * @param string $parameterId * @throws ConstraintParameterException if invalid snaks are found or a language has multiple texts * @return MultilingualTextValue */ private function parseMultilingualTextParameter( array $snakSerializations, $parameterId ) { $result = []; foreach ( $snakSerializations as $snakSerialization ) { $snak = $this->snakDeserializer->deserialize( $snakSerialization ); $this->requireValueParameter( $snak, $parameterId ); $value = $snak->getDataValue(); $this->requireMonolingualTextParameter( $value, $parameterId ); /** @var MonolingualTextValue $value */ '@phan-var MonolingualTextValue $value'; $code = $value->getLanguageCode(); if ( array_key_exists( $code, $result ) ) { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-single-per-language' ) ) ->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ->withLanguage( $code ) ); } $result[$code] = $value; } return new MultilingualTextValue( $result ); } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @throws ConstraintParameterException if the parameter is invalid * @return MultilingualTextValue */ public function parseSyntaxClarificationParameter( array $constraintParameters ) { $syntaxClarificationId = $this->config->get( 'WBQualityConstraintsSyntaxClarificationId' ); if ( !array_key_exists( $syntaxClarificationId, $constraintParameters ) ) { return new MultilingualTextValue( [] ); } $syntaxClarifications = $this->parseMultilingualTextParameter( $constraintParameters[$syntaxClarificationId], $syntaxClarificationId ); return $syntaxClarifications; } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @param string $constraintTypeItemId used in error messages * @param string[]|null $validScopes a list of Context::TYPE_* constants which are valid where this parameter appears. * If this is not null and one of the specified scopes is not in this list, a ConstraintParameterException is thrown. * @throws ConstraintParameterException if the parameter is invalid * @return string[]|null Context::TYPE_* constants */ public function parseConstraintScopeParameter( array $constraintParameters, $constraintTypeItemId, array $validScopes = null ) { $contextTypes = []; $parameterId = $this->config->get( 'WBQualityConstraintsConstraintScopeId' ); $items = $this->parseItemsParameter( $constraintParameters, $constraintTypeItemId, false, $parameterId ); if ( $items === [] ) { return null; } foreach ( $items as $item ) { $contextTypes[] = $this->parseContextTypeItem( $item, 'constraint scope', $parameterId ); } if ( $validScopes !== null ) { $invalidScopes = array_diff( $contextTypes, $validScopes ); if ( $invalidScopes !== [] ) { $invalidScope = array_pop( $invalidScopes ); throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-invalid-scope' ) ) ->withConstraintScope( $invalidScope, Role::CONSTRAINT_PARAMETER_VALUE ) ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) ->withConstraintScopeList( $validScopes, Role::CONSTRAINT_PARAMETER_VALUE ) ); } } return $contextTypes; } /** * Turn an item ID into a full unit string (using the concept URI). * * @param ItemId $unitId * @return string unit */ private function parseUnitParameter( ItemId $unitId ) { - $unitRepositoryName = $unitId->getRepositoryName(); - if ( !array_key_exists( $unitRepositoryName, $this->conceptBaseUris ) ) { - throw new LogicException( - 'No base URI for concept URI for repository: ' . $unitRepositoryName - ); - } - $baseUri = $this->conceptBaseUris[$unitRepositoryName]; - return $baseUri . $unitId->getSerialization(); + return $this->unitItemConceptBaseUri . $unitId->getSerialization(); } /** * Turn an ItemIdSnakValue into a single unit parameter. * * @param ItemIdSnakValue $item * @return UnitsParameter * @throws ConstraintParameterException */ private function parseUnitItem( ItemIdSnakValue $item ) { switch ( true ) { case $item->isValue(): $unit = $this->parseUnitParameter( $item->getItemId() ); return new UnitsParameter( [ $item->getItemId() ], [ UnboundedQuantityValue::newFromNumber( 1, $unit ) ], false ); case $item->isSomeValue(): $qualifierId = $this->config->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-value-or-novalue' ) ) ->withEntityId( new PropertyId( $qualifierId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ); case $item->isNoValue(): return new UnitsParameter( [], [], true ); } } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @param string $constraintTypeItemId used in error messages * @throws ConstraintParameterException if the parameter is invalid or missing * @return UnitsParameter */ public function parseUnitsParameter( array $constraintParameters, $constraintTypeItemId ) { $items = $this->parseItemsParameter( $constraintParameters, $constraintTypeItemId, true ); $unitItems = []; $unitQuantities = []; $unitlessAllowed = false; foreach ( $items as $item ) { $unit = $this->parseUnitItem( $item ); $unitItems = array_merge( $unitItems, $unit->getUnitItemIds() ); $unitQuantities = array_merge( $unitQuantities, $unit->getUnitQuantities() ); $unitlessAllowed = $unitlessAllowed || $unit->getUnitlessAllowed(); } if ( $unitQuantities === [] && !$unitlessAllowed ) { throw new LogicException( 'The "units" parameter is required, and yet we seem to be missing any allowed unit' ); } return new UnitsParameter( $unitItems, $unitQuantities, $unitlessAllowed ); } /** * Turn an ItemIdSnakValue into a single entity type parameter. * * @param ItemIdSnakValue $item * @return EntityTypesParameter * @throws ConstraintParameterException */ private function parseEntityTypeItem( ItemIdSnakValue $item ) { $parameterId = $this->config->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); if ( !$item->isValue() ) { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-value' ) ) ->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ); } $itemId = $item->getItemId(); switch ( $itemId->getSerialization() ) { case $this->config->get( 'WBQualityConstraintsWikibaseItemId' ): $entityType = 'item'; break; case $this->config->get( 'WBQualityConstraintsWikibasePropertyId' ): $entityType = 'property'; break; case $this->config->get( 'WBQualityConstraintsWikibaseLexemeId' ): $entityType = 'lexeme'; break; case $this->config->get( 'WBQualityConstraintsWikibaseFormId' ): $entityType = 'form'; break; case $this->config->get( 'WBQualityConstraintsWikibaseSenseId' ): $entityType = 'sense'; break; case $this->config->get( 'WBQualityConstraintsWikibaseMediaInfoId' ): $entityType = 'mediainfo'; break; default: $allowed = [ new ItemId( $this->config->get( 'WBQualityConstraintsWikibaseItemId' ) ), new ItemId( $this->config->get( 'WBQualityConstraintsWikibasePropertyId' ) ), new ItemId( $this->config->get( 'WBQualityConstraintsWikibaseLexemeId' ) ), new ItemId( $this->config->get( 'WBQualityConstraintsWikibaseFormId' ) ), new ItemId( $this->config->get( 'WBQualityConstraintsWikibaseSenseId' ) ), new ItemId( $this->config->get( 'WBQualityConstraintsWikibaseMediaInfoId' ) ), ]; throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-oneof' ) ) ->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ->withEntityIdList( $allowed, Role::CONSTRAINT_PARAMETER_VALUE ) ); } return new EntityTypesParameter( [ $entityType ], [ $itemId ] ); } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @param string $constraintTypeItemId used in error messages * @throws ConstraintParameterException if the parameter is invalid or missing * @return EntityTypesParameter */ public function parseEntityTypesParameter( array $constraintParameters, $constraintTypeItemId ) { $entityTypes = []; $entityTypeItemIds = []; $items = $this->parseItemsParameter( $constraintParameters, $constraintTypeItemId, true ); foreach ( $items as $item ) { $entityType = $this->parseEntityTypeItem( $item ); $entityTypes = array_merge( $entityTypes, $entityType->getEntityTypes() ); $entityTypeItemIds = array_merge( $entityTypeItemIds, $entityType->getEntityTypeItemIds() ); } if ( empty( $entityTypes ) ) { // @codeCoverageIgnoreStart throw new LogicException( 'The "entity types" parameter is required, ' . 'and yet we seem to be missing any allowed entity type' ); // @codeCoverageIgnoreEnd } return new EntityTypesParameter( $entityTypes, $entityTypeItemIds ); } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @throws ConstraintParameterException if the parameter is invalid * @return PropertyId[] */ public function parseSeparatorsParameter( array $constraintParameters ) { $separatorId = $this->config->get( 'WBQualityConstraintsSeparatorId' ); if ( !array_key_exists( $separatorId, $constraintParameters ) ) { return []; } $parameters = $constraintParameters[$separatorId]; $separators = []; foreach ( $parameters as $parameter ) { $separators[] = $this->parsePropertyIdParameter( $parameter, $separatorId ); } return $separators; } /** * Turn an ItemIdSnakValue into a single context type parameter. * * @param ItemIdSnakValue $item * @param string $use 'constraint scope' or 'property scope' * @param string $parameterId used in error messages * @return string one of the Context::TYPE_* constants * @throws ConstraintParameterException */ private function parseContextTypeItem( ItemIdSnakValue $item, $use, $parameterId ) { if ( !$item->isValue() ) { throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-value' ) ) ->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ); } if ( $use === 'constraint scope' ) { $mainSnakId = $this->config->get( 'WBQualityConstraintsConstraintCheckedOnMainValueId' ); $qualifiersId = $this->config->get( 'WBQualityConstraintsConstraintCheckedOnQualifiersId' ); $referencesId = $this->config->get( 'WBQualityConstraintsConstraintCheckedOnReferencesId' ); } else { $mainSnakId = $this->config->get( 'WBQualityConstraintsAsMainValueId' ); $qualifiersId = $this->config->get( 'WBQualityConstraintsAsQualifiersId' ); $referencesId = $this->config->get( 'WBQualityConstraintsAsReferencesId' ); } $itemId = $item->getItemId(); switch ( $itemId->getSerialization() ) { case $mainSnakId: return Context::TYPE_STATEMENT; case $qualifiersId: return Context::TYPE_QUALIFIER; case $referencesId: return Context::TYPE_REFERENCE; default: $allowed = [ $mainSnakId, $qualifiersId, $referencesId ]; throw new ConstraintParameterException( ( new ViolationMessage( 'wbqc-violation-message-parameter-oneof' ) ) ->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) ->withEntityIdList( $allowed, Role::CONSTRAINT_PARAMETER_VALUE ) ); } } /** * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()} * @param string $constraintTypeItemId used in error messages * @throws ConstraintParameterException if the parameter is invalid or missing * @return string[] list of Context::TYPE_* constants */ public function parsePropertyScopeParameter( array $constraintParameters, $constraintTypeItemId ) { $contextTypes = []; $parameterId = $this->config->get( 'WBQualityConstraintsPropertyScopeId' ); $items = $this->parseItemsParameter( $constraintParameters, $constraintTypeItemId, true, $parameterId ); foreach ( $items as $item ) { $contextTypes[] = $this->parseContextTypeItem( $item, 'property scope', $parameterId ); } if ( empty( $contextTypes ) ) { // @codeCoverageIgnoreStart throw new LogicException( 'The "property scope" parameter is required, ' . 'and yet we seem to be missing any allowed scope' ); // @codeCoverageIgnoreEnd } return $contextTypes; } } diff --git a/src/ServiceWiring.php b/src/ServiceWiring.php index 4c8d3d1e..bf6a46b5 100644 --- a/src/ServiceWiring.php +++ b/src/ServiceWiring.php @@ -1,278 +1,278 @@ function( MediaWikiServices $services ) { return new ExpiryLock( ObjectCache::getInstance( CACHE_ANYTHING ) ); }, ConstraintsServices::LOGGING_HELPER => function( MediaWikiServices $services ) { return new LoggingHelper( $services->getStatsdDataFactory(), LoggerFactory::getInstance( 'WikibaseQualityConstraints' ), $services->getMainConfig() ); }, ConstraintsServices::CONSTRAINT_REPOSITORY => function( MediaWikiServices $services ) { $sourceDefinitions = WikibaseRepo::getDefaultInstance()->getEntitySourceDefinitions(); $propertySource = $sourceDefinitions->getSourceForEntityType( Property::ENTITY_TYPE ); return new ConstraintRepository( $services->getDBLoadBalancer(), $propertySource->getDatabaseName() ); }, ConstraintsServices::CONSTRAINT_LOOKUP => function( MediaWikiServices $services ) { $constraintRepository = ConstraintsServices::getConstraintRepository( $services ); return new CachingConstraintLookup( $constraintRepository ); }, ConstraintsServices::CHECK_RESULT_SERIALIZER => function( MediaWikiServices $services ) { return new CheckResultSerializer( new ConstraintSerializer( false // constraint parameters are not exposed ), new ContextCursorSerializer(), new ViolationMessageSerializer(), false // unnecessary to serialize individual result dependencies ); }, ConstraintsServices::CHECK_RESULT_DESERIALIZER => function( MediaWikiServices $services ) { // TODO in the future, get EntityIdParser and DataValueFactory from $services? $repo = WikibaseRepo::getDefaultInstance(); $entityIdParser = $repo->getEntityIdParser(); $dataValueFactory = $repo->getDataValueFactory(); return new CheckResultDeserializer( new ConstraintDeserializer(), new ContextCursorDeserializer(), new ViolationMessageDeserializer( $entityIdParser, $dataValueFactory ), $entityIdParser ); }, ConstraintsServices::VIOLATION_MESSAGE_SERIALIZER => function( MediaWikiServices $services ) { return new ViolationMessageSerializer(); }, ConstraintsServices::VIOLATION_MESSAGE_DESERIALIZER => function( MediaWikiServices $services ) { // TODO in the future, get EntityIdParser and DataValueFactory from $services? $repo = WikibaseRepo::getDefaultInstance(); $entityIdParser = $repo->getEntityIdParser(); $dataValueFactory = $repo->getDataValueFactory(); return new ViolationMessageDeserializer( $entityIdParser, $dataValueFactory ); }, ConstraintsServices::CONSTRAINT_PARAMETER_PARSER => function( MediaWikiServices $services ) { - // TODO in the future, get DeserializerFactory and concept base URIs from $services? + // TODO in the future, get DeserializerFactory and entity source definitions from $services? $repo = WikibaseRepo::getDefaultInstance(); $deserializerFactory = $repo->getBaseDataModelDeserializerFactory(); - $conceptBaseUris = $repo->getConceptBaseUris(); + $entitySourceDefinitions = $repo->getEntitySourceDefinitions(); return new ConstraintParameterParser( $services->getMainConfig(), $deserializerFactory, - $conceptBaseUris + $entitySourceDefinitions->getSourceForEntityType( 'item' )->getConceptBaseUri() ); }, ConstraintsServices::CONNECTION_CHECKER_HELPER => function( MediaWikiServices $services ) { return new ConnectionCheckerHelper(); }, ConstraintsServices::RANGE_CHECKER_HELPER => function( MediaWikiServices $services ) { // TODO in the future, get UnitConverter from $services? $repo = WikibaseRepo::getDefaultInstance(); $unitConverter = $repo->getUnitConverter(); return new RangeCheckerHelper( $services->getMainConfig(), $unitConverter ); }, ConstraintsServices::SPARQL_HELPER => function( MediaWikiServices $services ) { $endpoint = $services->getMainConfig()->get( 'WBQualityConstraintsSparqlEndpoint' ); if ( $endpoint === '' ) { return new DummySparqlHelper(); } // TODO in the future, get RDFVocabulary, EntityIdParser and PropertyDataTypeLookup from $services? $repo = WikibaseRepo::getDefaultInstance(); $rdfVocabulary = $repo->getRdfVocabulary(); $entityIdParser = $repo->getEntityIdParser(); $propertyDataTypeLookup = $repo->getPropertyDataTypeLookup(); return new SparqlHelper( $services->getMainConfig(), $rdfVocabulary, $entityIdParser, $propertyDataTypeLookup, $services->getMainWANObjectCache(), ConstraintsServices::getViolationMessageSerializer( $services ), ConstraintsServices::getViolationMessageDeserializer( $services ), $services->getStatsdDataFactory(), ConstraintsServices::getExpiryLock( $services ), ConstraintsServices::getLoggingHelper(), wfWikiID() . ' WikibaseQualityConstraints ' . Http::userAgent(), $services->getHttpRequestFactory() ); }, ConstraintsServices::TYPE_CHECKER_HELPER => function( MediaWikiServices $services ) { return new TypeCheckerHelper( WikibaseServices::getEntityLookup( $services ), $services->getMainConfig(), ConstraintsServices::getSparqlHelper( $services ), $services->getStatsdDataFactory() ); }, ConstraintsServices::DELEGATING_CONSTRAINT_CHECKER => function( MediaWikiServices $services ) { // TODO in the future, get StatementGuidParser from $services? $repo = WikibaseRepo::getDefaultInstance(); $statementGuidParser = $repo->getStatementGuidParser(); $config = $services->getMainConfig(); $checkerMap = [ $config->get( 'WBQualityConstraintsConflictsWithConstraintId' ) => ConstraintCheckerServices::getConflictsWithChecker( $services ), $config->get( 'WBQualityConstraintsItemRequiresClaimConstraintId' ) => ConstraintCheckerServices::getItemChecker( $services ), $config->get( 'WBQualityConstraintsValueRequiresClaimConstraintId' ) => ConstraintCheckerServices::getTargetRequiredClaimChecker( $services ), $config->get( 'WBQualityConstraintsSymmetricConstraintId' ) => ConstraintCheckerServices::getSymmetricChecker( $services ), $config->get( 'WBQualityConstraintsInverseConstraintId' ) => ConstraintCheckerServices::getInverseChecker( $services ), $config->get( 'WBQualityConstraintsUsedAsQualifierConstraintId' ) => ConstraintCheckerServices::getQualifierChecker( $services ), $config->get( 'WBQualityConstraintsAllowedQualifiersConstraintId' ) => ConstraintCheckerServices::getQualifiersChecker( $services ), $config->get( 'WBQualityConstraintsMandatoryQualifierConstraintId' ) => ConstraintCheckerServices::getMandatoryQualifiersChecker( $services ), $config->get( 'WBQualityConstraintsRangeConstraintId' ) => ConstraintCheckerServices::getRangeChecker( $services ), $config->get( 'WBQualityConstraintsDifferenceWithinRangeConstraintId' ) => ConstraintCheckerServices::getDiffWithinRangeChecker( $services ), $config->get( 'WBQualityConstraintsTypeConstraintId' ) => ConstraintCheckerServices::getTypeChecker( $services ), $config->get( 'WBQualityConstraintsValueTypeConstraintId' ) => ConstraintCheckerServices::getValueTypeChecker( $services ), $config->get( 'WBQualityConstraintsSingleValueConstraintId' ) => ConstraintCheckerServices::getSingleValueChecker( $services ), $config->get( 'WBQualityConstraintsMultiValueConstraintId' ) => ConstraintCheckerServices::getMultiValueChecker( $services ), $config->get( 'WBQualityConstraintsDistinctValuesConstraintId' ) => ConstraintCheckerServices::getUniqueValueChecker( $services ), $config->get( 'WBQualityConstraintsFormatConstraintId' ) => ConstraintCheckerServices::getFormatChecker( $services ), $config->get( 'WBQualityConstraintsCommonsLinkConstraintId' ) => ConstraintCheckerServices::getCommonsLinkChecker( $services ), $config->get( 'WBQualityConstraintsOneOfConstraintId' ) => ConstraintCheckerServices::getOneOfChecker( $services ), $config->get( 'WBQualityConstraintsUsedForValuesOnlyConstraintId' ) => ConstraintCheckerServices::getValueOnlyChecker( $services ), $config->get( 'WBQualityConstraintsUsedAsReferenceConstraintId' ) => ConstraintCheckerServices::getReferenceChecker( $services ), $config->get( 'WBQualityConstraintsNoBoundsConstraintId' ) => ConstraintCheckerServices::getNoBoundsChecker( $services ), $config->get( 'WBQualityConstraintsAllowedUnitsConstraintId' ) => ConstraintCheckerServices::getAllowedUnitsChecker( $services ), $config->get( 'WBQualityConstraintsSingleBestValueConstraintId' ) => ConstraintCheckerServices::getSingleBestValueChecker( $services ), $config->get( 'WBQualityConstraintsAllowedEntityTypesConstraintId' ) => ConstraintCheckerServices::getEntityTypeChecker( $services ), $config->get( 'WBQualityConstraintsNoneOfConstraintId' ) => ConstraintCheckerServices::getNoneOfChecker( $services ), $config->get( 'WBQualityConstraintsIntegerConstraintId' ) => ConstraintCheckerServices::getIntegerChecker( $services ), $config->get( 'WBQualityConstraintsCitationNeededConstraintId' ) => ConstraintCheckerServices::getCitationNeededChecker( $services ), $config->get( 'WBQualityConstraintsPropertyScopeConstraintId' ) => ConstraintCheckerServices::getPropertyScopeChecker( $services ), $config->get( 'WBQualityConstraintsContemporaryConstraintId' ) => ConstraintCheckerServices::getContemporaryChecker( $services ), ]; return new DelegatingConstraintChecker( WikibaseServices::getEntityLookup( $services ), $checkerMap, ConstraintsServices::getConstraintLookup( $services ), ConstraintsServices::getConstraintParameterParser( $services ), $statementGuidParser, ConstraintsServices::getLoggingHelper( $services ), $config->get( 'WBQualityConstraintsCheckQualifiers' ), $config->get( 'WBQualityConstraintsCheckReferences' ), $config->get( 'WBQualityConstraintsPropertiesWithViolatingQualifiers' ) ); }, ConstraintsServices::RESULTS_SOURCE => function( MediaWikiServices $services ) { $config = $services->getMainConfig(); $resultsSource = new CheckingResultsSource( ConstraintsServices::getDelegatingConstraintChecker( $services ) ); if ( $config->get( 'WBQualityConstraintsCacheCheckConstraintsResults' ) ) { $possiblyStaleConstraintTypes = [ $config->get( 'WBQualityConstraintsCommonsLinkConstraintId' ), $config->get( 'WBQualityConstraintsTypeConstraintId' ), $config->get( 'WBQualityConstraintsValueTypeConstraintId' ), $config->get( 'WBQualityConstraintsDistinctValuesConstraintId' ), ]; // TODO in the future, get EntityIdParser and WikiPageEntityMetaDataAccessor from $services? $repo = WikibaseRepo::getDefaultInstance(); $entityIdParser = $repo->getEntityIdParser(); $wikiPageEntityMetaDataAccessor = $repo->getLocalRepoWikiPageMetaDataAccessor(); $resultsSource = new CachingResultsSource( $resultsSource, ResultsCache::getDefaultInstance(), ConstraintsServices::getCheckResultSerializer( $services ), ConstraintsServices::getCheckResultDeserializer( $services ), $wikiPageEntityMetaDataAccessor, $entityIdParser, $config->get( 'WBQualityConstraintsCacheCheckConstraintsTTLSeconds' ), $possiblyStaleConstraintTypes, $config->get( 'WBQualityConstraintsCacheCheckConstraintsMaximumRevisionIds' ), ConstraintsServices::getLoggingHelper( $services ) ); } return $resultsSource; }, ]; diff --git a/tests/phpunit/Api/CheckConstraintsTest.php b/tests/phpunit/Api/CheckConstraintsTest.php index d5ffc6b6..98b5f1bb 100644 --- a/tests/phpunit/Api/CheckConstraintsTest.php +++ b/tests/phpunit/Api/CheckConstraintsTest.php @@ -1,343 +1,343 @@ addEntity( new Item( new ItemId( self::EMPTY_ITEM ) ) ); $wgAPIModules['wbcheckconstraints']['factory'] = function ( $main, $name ) { $repo = WikibaseRepo::getDefaultInstance(); $factory = new EntityIdLabelFormatterFactory(); $languageFallbackChainFactory = new LanguageFallbackChainFactory(); $language = Language::factory( 'en' ); $entityIdFormatter = $factory->getEntityIdFormatter( $language ); $formatterOptions = new FormatterOptions(); $factoryFunctions = []; Assert::parameterElementType( 'callable', $factoryFunctions, '$factoryFunctions' ); $formatterOptions->setOption( SnakFormatter::OPT_LANG, $language->getCode() ); $valueFormatterFactory = new OutputFormatValueFormatterFactory( $factoryFunctions, $language, $languageFallbackChainFactory ); $valueFormatter = $valueFormatterFactory->getValueFormatter( SnakFormatter::FORMAT_HTML, $formatterOptions ); // we can’t use the DefaultConfig trait because we’re in a static method $config = new HashConfig( [ 'WBQualityConstraintsPropertyConstraintId' => 'P1', 'WBQualityConstraintsExceptionToConstraintId' => 'P2', 'WBQualityConstraintsConstraintStatusId' => 'P3', 'WBQualityConstraintsConstraintScopeId' => 'P4', 'WBQualityConstraintsConstraintCheckedOnMainValueId' => 'Q1', 'WBQualityConstraintsConstraintCheckedOnQualifiersId' => 'Q2', 'WBQualityConstraintsConstraintCheckedOnReferencesId' => 'Q3', 'WBQualityConstraintsCheckDurationInfoSeconds' => 1.0, 'WBQualityConstraintsCheckDurationWarningSeconds' => 10.0, 'WBQualityConstraintsCheckOnEntityDurationInfoSeconds' => 5.0, 'WBQualityConstraintsCheckOnEntityDurationWarningSeconds' => 55.0, 'WBQualityConstraintsIncludeDetailInApi' => true, ] ); $entityIdParser = new ItemIdParser(); $constraintParameterParser = new ConstraintParameterParser( $config, $repo->getBaseDataModelDeserializerFactory(), - $repo->getConceptBaseUris() + $repo->getEntitySourceDefinitions()->getSourceForEntityType( 'item' )->getConceptBaseUri() ); $dataFactory = new NullStatsdDataFactory(); $constraintChecker = new DelegatingConstraintChecker( self::$entityLookup, self::$checkerMap, new InMemoryConstraintLookup( self::$constraintLookupContents ), $constraintParameterParser, $repo->getStatementGuidParser(), new LoggingHelper( $dataFactory, LoggerFactory::getInstance( 'WikibaseQualityConstraints' ), $config ), false, false, [] ); return new CheckConstraints( $main, $name, $entityIdParser, new StatementGuidValidator( $entityIdParser ), $repo->getApiHelperFactory( RequestContext::getMain() ), new CheckingResultsSource( $constraintChecker ), new CheckResultsRenderer( $repo->getEntityTitleLookup(), $entityIdFormatter, new ViolationMessageRenderer( $entityIdFormatter, $valueFormatter, new MockMessageLocalizer(), $config ) ), $dataFactory ); }; } protected function tearDown() : void { self::$constraintLookupContents = []; self::$checkerMap = []; parent::tearDown(); } public static function tearDownAfterClass() : void { global $wgAPIModules; $wgAPIModules['wbcheckconstraints'] = self::$oldModuleDeclaration; parent::tearDownAfterClass(); } public function testReportForEmptyItemIsEmpty() { $entityId = self::EMPTY_ITEM; $result = $this->doRequest( [ CheckConstraints::PARAM_ID => $entityId ] ); $this->assertArrayHasKey( $entityId, $result['wbcheckconstraints'] ); $this->assertArrayHasKey( 'claims', $result['wbcheckconstraints'][$entityId] ); $this->assertEmpty( $result['wbcheckconstraints'][$entityId]['claims'] ); } public function testReportForNonexistentItemIsEmpty() { $entityId = self::NONEXISTENT_ITEM; $result = $this->doRequest( [ CheckConstraints::PARAM_ID => $entityId ] ); $this->assertArrayHasKey( $entityId, $result['wbcheckconstraints'] ); $this->assertArrayHasKey( 'claims', $result['wbcheckconstraints'][$entityId] ); $this->assertEmpty( $result['wbcheckconstraints'][$entityId]['claims'] ); } public function testReportForNonexistentClaimIsEmpty() { $result = $this->doRequest( [ CheckConstraints::PARAM_CLAIM_ID => self::NONEXISTENT_CLAIM ] ); $this->assertEmpty( $result['wbcheckconstraints'] ); } public function testItemExistsAndHasViolation_WillGetOnlyThisViolationInTheResult() { $this->givenItemWithPropertyExists( new ItemId( 'Q1' ), new PropertyId( 'P1' ), '46fc8ec9-4903-4592-9a0e-afdd1fa03183' ); $this->givenPropertyHasStatus( new PropertyId( 'P1' ), CheckResult::STATUS_VIOLATION ); $result = $this->doRequest( [ CheckConstraints::PARAM_ID => 'Q1' ] ); $this->assertCount( 1, $result['wbcheckconstraints'] ); $resultStatement = $result['wbcheckconstraints']['Q1']['claims']['P1'][0]; $this->assertSame( 'Q1$46fc8ec9-4903-4592-9a0e-afdd1fa03183', $resultStatement['id'] ); $resultsForItem = $resultStatement['mainsnak']['results']; $this->assertCount( 1, $resultsForItem ); $this->assertEquals( CheckResult::STATUS_WARNING, $resultsForItem[0]['status'] ); $this->assertEquals( 'P1', $resultsForItem[0]['property'] ); } public function testItemWithClaimExistsAndHasViolation_WillGetOnlyThisViolationInTheResult() { $this->givenItemWithPropertyExists( new ItemId( 'Q1' ), new PropertyId( 'P1' ), '46fc8ec9-4903-4592-9a0e-afdd1fa03183' ); $this->givenPropertyHasStatus( new PropertyId( 'P1' ), CheckResult::STATUS_VIOLATION ); $result = $this->doRequest( [ CheckConstraints::PARAM_CLAIM_ID => 'Q1$46fc8ec9-4903-4592-9a0e-afdd1fa03183' ] ); $this->assertCount( 1, $result['wbcheckconstraints'] ); $resultStatement = $result['wbcheckconstraints']['Q1']['claims']['P1'][0]; $this->assertSame( 'Q1$46fc8ec9-4903-4592-9a0e-afdd1fa03183', $resultStatement['id'] ); $resultsForItem = $resultStatement['mainsnak']['results']; $this->assertCount( 1, $resultsForItem ); $this->assertEquals( CheckResult::STATUS_WARNING, $resultsForItem[0]['status'] ); $this->assertEquals( 'P1', $resultsForItem[0]['property'] ); } public function testItemWithAlternativeCaseClaimExistsAndHasViolation_WillGetOnlyThisViolationInTheResult() { $itemId = 'Q1'; $propertyId = 'P1'; $guid = 'q1$46FC8EC9-4903-4592-9A0E-AFDD1FA03183'; $item = new Item( new ItemId( $itemId ) ); $statement = new Statement( new PropertyValueSnak( new PropertyId( $propertyId ), new UnknownValue( null ) ) ); $statement->setGuid( $guid ); $item->getStatements()->addStatement( $statement ); self::$entityLookup->addEntity( $item ); $this->givenPropertyHasStatus( new PropertyId( $propertyId ), CheckResult::STATUS_VIOLATION ); $result = $this->doRequest( [ CheckConstraints::PARAM_CLAIM_ID => $guid ] ); $this->assertCount( 1, $result['wbcheckconstraints'] ); $resultStatement = $result['wbcheckconstraints']['Q1']['claims']['P1'][0]; $this->assertSame( $guid, $resultStatement['id'] ); $resultsForItem = $resultStatement['mainsnak']['results']; $this->assertCount( 1, $resultsForItem ); $this->assertEquals( CheckResult::STATUS_WARNING, $resultsForItem[0]['status'] ); $this->assertEquals( $propertyId, $resultsForItem[0]['property'] ); } public function testStatusParameterFiltersResults() { $item = NewItem::withId( 'Q1' ) ->andStatement( NewStatement::noValueFor( 'P1' ) ) ->andStatement( NewStatement::noValueFor( 'P2' ) ) ->build(); self::$entityLookup->addEntity( $item ); $this->givenPropertyHasStatus( new PropertyId( 'P1' ), CheckResult::STATUS_VIOLATION ); $this->givenPropertyHasStatus( new PropertyId( 'P2' ), CheckResult::STATUS_COMPLIANCE ); $result = $this->doRequest( [ CheckConstraints::PARAM_ID => 'Q1', CheckConstraints::PARAM_STATUS => CheckResult::STATUS_VIOLATION . '|' . CheckResult::STATUS_WARNING, ] ); $this->assertCount( 1, $result['wbcheckconstraints'] ); $this->assertCount( 2, $result['wbcheckconstraints']['Q1']['claims'] ); $violatingStatement = $result['wbcheckconstraints']['Q1']['claims']['P1'][0]; $violatingStatementResults = $violatingStatement['mainsnak']['results']; $this->assertCount( 1, $violatingStatementResults ); $this->assertEquals( CheckResult::STATUS_WARNING, $violatingStatementResults[0]['status'] ); $this->assertEquals( 'P1', $violatingStatementResults[0]['property'] ); $compliantStatement = $result['wbcheckconstraints']['Q1']['claims']['P2'][0]; $compliantStatementResults = $compliantStatement['mainsnak']['results']; $this->assertCount( 0, $compliantStatementResults ); } /** * @param array $params * @return array Array of violations */ private function doRequest( array $params ) { $params['action'] = 'wbcheckconstraints'; return $this->doApiRequest( $params, [], false, null )[0]; } private function givenPropertyHasStatus( PropertyId $propertyId, $status ) { static $itemIdNumber = 1234; $itemId = 'Q' . $itemIdNumber++; self::$checkerMap[$itemId] = new FakeChecker( $status ); self::$constraintLookupContents[] = new Constraint( 'P1234$6a4d1930-922b-4c2e-b6e1-9a06bf04c2f8', $propertyId, $itemId, [] ); } private function givenItemWithPropertyExists( ItemId $itemId, PropertyId $propertyId, $statementId = 'some-id' ) { $item = new Item( $itemId, null, null, new StatementList( [ new Statement( new PropertyValueSnak( $propertyId, new UnknownValue( null ) ), null, null, $itemId->getSerialization() . '$' . $statementId ) ] ) ); self::$entityLookup->addEntity( $item ); } } diff --git a/tests/phpunit/ConstraintParameters.php b/tests/phpunit/ConstraintParameters.php index f3a07f1b..f0571a51 100644 --- a/tests/phpunit/ConstraintParameters.php +++ b/tests/phpunit/ConstraintParameters.php @@ -1,378 +1,378 @@ parser === null ) { $this->parser = new ConstraintParameterParser( $this->getDefaultConfig(), WikibaseRepo::getDefaultInstance()->getBaseDataModelDeserializerFactory(), - [ '' => 'http://wikibase.example/entity/' ] + 'http://wikibase.example/entity/' ); } return $this->parser; } /** * @return Serializer */ private function getSnakSerializer() { if ( $this->snakSerializer == null ) { $this->snakSerializer = WikibaseRepo::getDefaultInstance()->getBaseDataModelSerializerFactory()->newSnakSerializer(); } return $this->snakSerializer; } /** * @param string[] $classIds item ID serializations * @return array[] */ public function classParameter( array $classIds ) { $classParameterId = $this->getDefaultConfig()->get( 'WBQualityConstraintsClassId' ); return [ $classParameterId => array_map( function( $classId ) use ( $classParameterId ) { return $this->getSnakSerializer()->serialize( new PropertyValueSnak( new PropertyId( $classParameterId ), new EntityIdValue( new ItemId( $classId ) ) ) ); }, $classIds ) ]; } /** * @param string $relation 'instance', 'subclass', or 'instanceOrSubclass' * @return array[] */ public function relationParameter( $relation ) { $relationParameterId = $this->getDefaultConfig()->get( 'WBQualityConstraintsRelationId' ); switch ( $relation ) { case 'instance': $configKey = 'WBQualityConstraintsInstanceOfRelationId'; break; case 'subclass': $configKey = 'WBQualityConstraintsSubclassOfRelationId'; break; case 'instanceOrSubclass': $configKey = 'WBQualityConstraintsInstanceOrSubclassOfRelationId'; break; default: throw new InvalidArgumentException( '$relation must be instance or subclass' ); } return [ $relationParameterId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( new PropertyId( $relationParameterId ), new EntityIdValue( new ItemId( $this->getDefaultConfig()->get( $configKey ) ) ) ) ) ] ]; } /** * @param string $propertyId * @return array[] */ public function propertyParameter( $propertyId ) { $propertyParameterId = $this->getDefaultConfig()->get( 'WBQualityConstraintsPropertyId' ); return [ $propertyParameterId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( new PropertyId( $propertyParameterId ), new EntityIdValue( new PropertyId( $propertyId ) ) ) ) ] ]; } /** * @param string[] $properties property ID serializations * @return array[] */ public function propertiesParameter( array $properties ) { $propertyParameterId = $this->getDefaultConfig()->get( 'WBQualityConstraintsPropertyId' ); return [ $propertyParameterId => array_map( function( $property ) use ( $propertyParameterId ) { $value = new EntityIdValue( new PropertyId( $property ) ); $snak = new PropertyValueSnak( new PropertyId( $propertyParameterId ), $value ); return $this->getSnakSerializer()->serialize( $snak ); }, $properties ) ]; } /** * @param (string|Snak)[] $items item ID serializations or snaks * @return array[] */ public function itemsParameter( array $items ) { $qualifierParameterId = $this->getDefaultConfig()->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); return [ $qualifierParameterId => array_map( function( $item ) use ( $qualifierParameterId ) { if ( $item instanceof Snak ) { $snak = $item; } else { $value = new EntityIdValue( new ItemId( $item ) ); $snak = new PropertyValueSnak( new PropertyId( $qualifierParameterId ), $value ); } return $this->getSnakSerializer()->serialize( $snak ); }, $items ) ]; } /** * Convert an abbreviated value for a range endpoint * to a full snak for range constraint parameters. * A numeric argument means a numeric endpoint, * 'now' corresponds to a somevalue snak, * any other string is parsed as a time value, * a DataValue is used directly, * and null corresponds to a novalue snak (open-ended range). * * @param DataValue|int|float|string|null $value * @param string $property property ID serialization * @return Snak */ private function rangeEndpoint( $value, $property ) { $propertyId = new PropertyId( $property ); if ( $value === null ) { return new PropertyNoValueSnak( $propertyId ); } else { if ( is_string( $value ) ) { if ( $value === 'now' ) { return new PropertySomeValueSnak( $propertyId ); } $timeParser = ( new TimeParserFactory() )->getTimeParser(); $value = $timeParser->parse( $value ); } elseif ( is_numeric( $value ) ) { $value = UnboundedQuantityValue::newFromNumber( $value ); } return new PropertyValueSnak( $propertyId, $value ); } } /** * @param string $type 'quantity' or 'time' * @param DataValue|int|float|string|null $min lower boundary, see rangeEndpoint() for details * @param DataValue|int|float|string|null $max upper boundary, see rangeEndpoint() for details * @return array[] */ public function rangeParameter( $type, $min, $max ) { $configKey = $type === 'quantity' ? 'Quantity' : 'Date'; $config = $this->getDefaultConfig(); $minimumId = $config->get( 'WBQualityConstraintsMinimum' . $configKey . 'Id' ); $maximumId = $config->get( 'WBQualityConstraintsMaximum' . $configKey . 'Id' ); $minimumSnak = $this->rangeEndpoint( $min, $minimumId ); $maximumSnak = $this->rangeEndpoint( $max, $maximumId ); $snakSerializer = $this->getSnakSerializer(); return [ $minimumId => [ $snakSerializer->serialize( $minimumSnak ) ], $maximumId => [ $snakSerializer->serialize( $maximumSnak ) ] ]; } /** * @param string $namespace * @return array[] */ public function namespaceParameter( $namespace ) { $namespaceId = $this->getDefaultConfig()->get( 'WBQualityConstraintsNamespaceId' ); $value = new StringValue( $namespace ); $snak = new PropertyValueSnak( new PropertyId( $namespaceId ), $value ); return [ $namespaceId => [ $this->getSnakSerializer()->serialize( $snak ) ] ]; } /** * @param string $format * @return array[] */ public function formatParameter( $format ) { $formatId = $this->getDefaultConfig()->get( 'WBQualityConstraintsFormatAsARegularExpressionId' ); $value = new StringValue( $format ); $snak = new PropertyValueSnak( new PropertyId( $formatId ), $value ); return [ $formatId => [ $this->getSnakSerializer()->serialize( $snak ) ] ]; } /** * @param string $languageCode * @param string $syntaxClarification * @return array[] */ public function syntaxClarificationParameter( $languageCode, $syntaxClarification ) { $syntaxClarificationId = $this->getDefaultConfig()->get( 'WBQualityConstraintsSyntaxClarificationId' ); $value = new MonolingualTextValue( $languageCode, $syntaxClarificationId ); $snak = new PropertyValueSnak( new PropertyId( $syntaxClarificationId ), $value ); return [ $syntaxClarificationId => [ $this->getSnakSerializer()->serialize( $snak ) ] ]; } /** * @param string[] $exceptions item ID serializations (other entity types currently not supported) * @return array[] */ public function exceptionsParameter( $exceptions ) { $exceptionId = $this->getDefaultConfig()->get( 'WBQualityConstraintsExceptionToConstraintId' ); return [ $exceptionId => array_map( function ( $exception ) use ( $exceptionId ) { $value = new EntityIdValue( new ItemId( $exception ) ); $snak = new PropertyValueSnak( new PropertyId( $exceptionId ), $value ); return $this->getSnakSerializer()->serialize( $snak ); }, $exceptions ) ]; } /** * @param string $status ('mandatory' or 'suggestion') * @return array[] */ public function statusParameter( $status ) { $statusParameterId = $this->getDefaultConfig()->get( 'WBQualityConstraintsConstraintStatusId' ); switch ( $status ) { case 'mandatory': $configKey = 'WBQualityConstraintsMandatoryConstraintId'; break; case 'suggestion': $configKey = 'WBQualityConstraintsSuggestionConstraintId'; break; default: throw new InvalidArgumentException( '$status must be mandatory or suggestion' ); } return [ $statusParameterId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( new PropertyId( $statusParameterId ), new EntityIdValue( new ItemId( $this->getDefaultConfig()->get( $configKey ) ) ) ) ) ] ]; } /** * @param string[] $contextTypes Context::TYPE_* constants * @return array */ public function constraintScopeParameter( array $contextTypes ) { $config = $this->getDefaultConfig(); $constraintScopeParameterId = $config->get( 'WBQualityConstraintsConstraintScopeId' ); $itemIds = []; foreach ( $contextTypes as $contextType ) { switch ( $contextType ) { case Context::TYPE_STATEMENT: $itemIds[] = $config->get( 'WBQualityConstraintsConstraintCheckedOnMainValueId' ); break; case Context::TYPE_QUALIFIER: $itemIds[] = $config->get( 'WBQualityConstraintsConstraintCheckedOnQualifiersId' ); break; case Context::TYPE_REFERENCE: $itemIds[] = $config->get( 'WBQualityConstraintsConstraintCheckedOnReferencesId' ); break; default: $this->assertTrue( false, 'unknown context type ' . $contextType ); } } return [ $constraintScopeParameterId => array_map( function ( $itemId ) use ( $constraintScopeParameterId ) { return $this->getSnakSerializer()->serialize( new PropertyValueSnak( new PropertyId( $constraintScopeParameterId ), new EntityIdValue( new ItemId( $itemId ) ) ) ); }, $itemIds ) ]; } public function separatorsParameter( array $separators ) { $separatorId = $this->getDefaultConfig()->get( 'WBQualityConstraintsSeparatorId' ); return [ $separatorId => array_map( function( $separator ) use ( $separatorId ) { $value = new EntityIdValue( new PropertyId( $separator ) ); $snak = new PropertyValueSnak( new PropertyId( $separatorId ), $value ); return $this->getSnakSerializer()->serialize( $snak ); }, $separators ) ]; } public function propertyScopeParameter( array $contextTypes ) { $config = $this->getDefaultConfig(); $parameterId = $config->get( 'WBQualityConstraintsPropertyScopeId' ); return [ $parameterId => array_map( function( $contextType ) use ( $config, $parameterId ) { switch ( $contextType ) { case Context::TYPE_STATEMENT: $itemId = $config->get( 'WBQualityConstraintsAsMainValueId' ); break; case Context::TYPE_QUALIFIER: $itemId = $config->get( 'WBQualityConstraintsAsQualifiersId' ); break; case Context::TYPE_REFERENCE: $itemId = $config->get( 'WBQualityConstraintsAsReferencesId' ); break; default: $this->assertTrue( false, 'unknown context type ' . $contextType ); } $value = new EntityIdValue( new ItemId( $itemId ) ); $snak = new PropertyValueSnak( new PropertyId( $parameterId ), $value ); return $this->getSnakSerializer()->serialize( $snak ); }, $contextTypes ) ]; } } diff --git a/tests/phpunit/DelegatingConstraintCheckerTest.php b/tests/phpunit/DelegatingConstraintCheckerTest.php index e6d14079..c369ef4c 100644 --- a/tests/phpunit/DelegatingConstraintCheckerTest.php +++ b/tests/phpunit/DelegatingConstraintCheckerTest.php @@ -1,951 +1,951 @@ resetServiceForTesting( ConstraintsServices::CONSTRAINT_LOOKUP ); $config = new MultiConfig( [ new HashConfig( [ 'WBQualityConstraintsEnableSuggestionConstraintStatus' => true ] ), $this->getDefaultConfig(), MediaWikiServices::getInstance()->getMainConfig(), ] ); $this->setService( 'MainConfig', $config ); $constraintParameterParser = new ConstraintParameterParser( $config, WikibaseRepo::getDefaultInstance()->getBaseDataModelDeserializerFactory(), - [ '' => 'http://wikibase.example/entity/' ] + 'http://wikibase.example/entity/' ); $this->setService( ConstraintsServices::CONSTRAINT_PARAMETER_PARSER, $constraintParameterParser ); $this->lookup = new InMemoryEntityLookup(); $this->setService( WikibaseServices::ENTITY_LOOKUP, $this->lookup ); $dataTypeLookup = new EntityRetrievingDataTypeLookup( $this->lookup ); $this->setService( WikibaseServices::PROPERTY_DATA_TYPE_LOOKUP, $dataTypeLookup ); $pageNameNormalizer = $this->createMock( MediaWikiPageNameNormalizer::class ); $pageNameNormalizer->method( 'normalizePageName' ) ->willReturnArgument( 0 ); $commonsLinkChecker = new CommonsLinkChecker( $constraintParameterParser, $pageNameNormalizer ); $this->setService( ConstraintCheckerServices::COMMONS_LINK_CHECKER, $commonsLinkChecker ); $this->constraintChecker = ConstraintsServices::getDelegatingConstraintChecker(); // specify database tables used by this test $this->tablesUsed[] = 'wbqc_constraints'; } protected function tearDown() : void { MediaWikiServices::getInstance()->resetServiceForTesting( ConstraintsServices::CONSTRAINT_LOOKUP ); parent::tearDown(); } /** * @param string $name */ private function getConstraintTypeItemId( $name ) { return $this->getDefaultConfig()->get( 'WBQualityConstraints' . $name . 'ConstraintId' ); } /** * Adds temporary test data to database. * * @throws DBUnexpectedError */ public function addDBData() { $config = $this->getDefaultConfig(); $constraints = [ [ 'constraint_guid' => 'P1$ecb8f617-90f1-4ef3-afab-f4bf3881ec28', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'CommonsLink' ), 'constraint_parameters' => json_encode( $this->namespaceParameter( 'File' ) ) ], [ 'constraint_guid' => 'P10$0bdbe1cb-8afb-4d16-9fd0-c1d0a5b717ce', 'pid' => 10, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'CommonsLink' ), 'constraint_parameters' => json_encode( array_merge( $this->namespaceParameter( 'File' ), $this->exceptionsParameter( [ 'Q5' ] ) ) ) ], [ 'constraint_guid' => 'P11$01c56d1f-b3ce-4a1a-bef7-8c652f395eb2', 'pid' => 11, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'UsedAsQualifier' ), 'constraint_parameters' => json_encode( [ $this->getDefaultConfig()->get( 'WBQualityConstraintsExceptionToConstraintId' ) => [ [ 'snaktype' => 'novalue', 'property' => 'P2316' ] ] ] ) ], [ 'constraint_guid' => 'P1$6ad9eb64-13fd-43a1-afc8-84857108bd59', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'MandatoryQualifier' ), 'constraint_parameters' => json_encode( $this->propertyParameter( 'P2' ) ) ], [ 'constraint_guid' => 'P1$cfff6d73-320c-43c5-8582-e9cbb98e2ca2', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'ConflictsWith' ), 'constraint_parameters' => json_encode( $this->propertyParameter( 'P2' ) ) ], [ 'constraint_guid' => 'P1$c81a981e-4eab-44c9-8aa2-62c63072902e', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'Inverse' ), 'constraint_parameters' => json_encode( $this->propertyParameter( 'P2' ) ) ], [ 'constraint_guid' => 'P1$2040dee1-8c9d-45b7-ac01-2ce8046f578b', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'AllowedQualifiers' ), 'constraint_parameters' => json_encode( $this->propertiesParameter( [ 'P2', 'P3' ] ) ) ], [ 'constraint_guid' => 'P1$09a20b38-fe36-444b-b9ed-22eb46c3ea73', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'DifferenceWithinRange' ), 'constraint_parameters' => json_encode( array_merge( $this->propertyParameter( 'P2' ), $this->rangeParameter( 'quantity', 0, 150 ) ) ) ], [ 'constraint_guid' => 'P1$3dac547d-3faf-4198-9b9c-0ba1eae32752', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'Format' ), 'constraint_parameters' => json_encode( $this->formatParameter( '[0-9]' ) ) ], [ 'constraint_guid' => 'P1$cc5708c8-3ec8-4bf3-8931-409530e4d634', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'MultiValue' ), 'constraint_parameters' => '{}' ], [ 'constraint_guid' => 'P1$021b2558-8e7c-4c2c-ba14-4596dc11536e', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'DistinctValues' ), 'constraint_parameters' => '{}' ], [ 'constraint_guid' => 'P1$3ddc8c54-c056-425c-8745-d257004d585f', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'SingleValue' ), 'constraint_parameters' => '{}' ], [ 'constraint_guid' => 'P1$dc4464ed-42a5-47f6-b725-04b1d9d1dfc6', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'Symmetric' ), 'constraint_parameters' => '{}' ], [ 'constraint_guid' => 'P1$713ec92d-cd08-413d-b4dc-8e6eeb8c7861', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'UsedAsQualifier' ), 'constraint_parameters' => '{}' ], [ 'constraint_guid' => 'P1$a083d339-7bd6-4737-a987-b55ae8a1a5f3', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'OneOf' ), 'constraint_parameters' => json_encode( $this->itemsParameter( [ 'Q2', 'Q3' ] ) ) ], [ 'constraint_guid' => 'P1$b8587fb1-7315-46ba-9d04-07f0e9af857d', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'Range' ), 'constraint_parameters' => json_encode( $this->rangeParameter( 'time', '0', '2015' ) ) ], [ 'constraint_guid' => 'P1$83ee554c-41fd-4bfa-ae9b-960d0eee2fa4', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'ValueRequiresClaim' ), 'constraint_parameters' => json_encode( array_merge( $this->propertyParameter( 'P2' ), $this->itemsParameter( [ 'Q2' ] ) ) ) ], [ 'constraint_guid' => 'P1$370a45b5-b007-455d-b5fa-03b90c629fe5', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'ItemRequiresClaim' ), 'constraint_parameters' => json_encode( array_merge( $this->propertyParameter( 'P2' ), $this->itemsParameter( [ 'Q2', 'Q3' ] ) ) ) ], [ 'constraint_guid' => 'P1$831d9d5d-ed77-48f2-8433-fb80a9ef3aad', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'Type' ), 'constraint_parameters' => json_encode( array_merge( $this->relationParameter( 'instance' ), $this->classParameter( [ 'Q2', 'Q3' ] ) ) ) ], [ 'constraint_guid' => 'P1$fe667c64-be46-4521-a54d-8a895b6005b0', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'ValueType' ), 'constraint_parameters' => json_encode( array_merge( $this->relationParameter( 'instance' ), $this->classParameter( [ 'Q2', 'Q3' ] ) ) ) ], [ 'constraint_guid' => 'P3$0a011ed8-1e2b-470c-a306-fb8ea6953779', 'pid' => 3, 'constraint_type_qid' => 'Is not inside', 'constraint_parameters' => '{}' ], [ 'constraint_guid' => 'P5$2234a1ee-0257-4e13-b619-11211c23734a', 'pid' => 5, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'UsedAsQualifier' ), 'constraint_parameters' => json_encode( $this->statusParameter( 'suggestion' ) ), ], [ 'constraint_guid' => 'P6$ad792000-6a12-413d-9fe5-11d2467b7a92', 'pid' => 6, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'UsedAsQualifier' ), 'constraint_parameters' => json_encode( $this->statusParameter( 'mandatory' ) ) ], [ 'constraint_guid' => 'P7$a3f746e7-66a0-46fd-96ab-6ff6638332a4', 'pid' => 7, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'UsedAsQualifier' ), 'constraint_parameters' => '{}' ], [ 'constraint_guid' => 'P8$34c8af8e-bb50-4458-994b-f355ff899fff', 'pid' => 8, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'UsedAsQualifier' ), 'constraint_parameters' => '{"@error":{"toolong":true}}' ], [ 'constraint_guid' => 'P9$43053ee8-79da-4326-a2ac-f85098291db3', 'pid' => 9, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'UsedAsQualifier' ), 'constraint_parameters' => json_encode( [ 'P2316' => [ [ 'snaktype' => 'novalue', 'property' => 'P2316', ] ], 'P2303' => [ [ 'snaktype' => 'novalue', 'property' => 'P2303', ] ], ] ), ], [ 'constraint_guid' => 'P1$a1b1f3d8-6215-4cb6-9edd-3af126ae134f', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'UsedForValuesOnly' ), 'constraint_parameters' => '{}' ], [ 'constraint_guid' => 'P1$d7398ac7-aee4-4a29-9e8c-79944e664b67', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'UsedAsReference' ), 'constraint_parameters' => '{}' ], [ 'constraint_guid' => 'P1$e5da3fee-1097-49fa-a776-d4e96fe35c0b', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'NoBounds' ), 'constraint_parameters' => '{}', ], [ 'constraint_guid' => 'P1$1e2d6650-4249-4ea4-9271-9c95e19b1f41', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'AllowedUnits' ), 'constraint_parameters' => json_encode( $this->itemsParameter( [ 'Q1' ] ) ) ], [ 'constraint_guid' => 'P1$a6970e0d-d67d-4465-b8b6-8debff189332', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'SingleBestValue' ), 'constraint_parameters' => '{}' ], [ 'constraint_guid' => 'P1$398c030c-2ed8-4194-9fb1-3e38610f6a31', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'Integer' ), 'constraint_parameters' => '{}' ], [ 'constraint_guid' => 'P1$c16e14f7-9f71-4587-9ffd-47790e725cfa', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'CitationNeeded' ), 'constraint_parameters' => '{}', ], [ 'constraint_guid' => 'P1$6161fca9-71a7-4d5b-9b86-4e717b35ea17', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'PropertyScope' ), 'constraint_parameters' => json_encode( $this->propertyScopeParameter( [ Context::TYPE_STATEMENT ] ) ), ], [ 'constraint_guid' => 'P1$24a3af57-da68-4339-9968-94ded7d99961', 'pid' => 1, 'constraint_type_qid' => $this->getConstraintTypeItemId( 'Contemporary' ), 'constraint_parameters' => '{}', ], ]; $this->constraintCount = count( array_filter( $constraints, function ( $constraint ) { return $constraint['pid'] === 1; } ) ); $this->db->delete( 'wbqc_constraints', '*' ); $this->db->insert( 'wbqc_constraints', $constraints ); } public function testCheckOnEntityId() { $entity = NewItem::withId( 'Q1' ) ->andStatement( NewStatement::forProperty( 'P1' ) ->withValue( 'foo' ) ) ->build(); $this->lookup->addEntity( $entity ); $result = $this->constraintChecker->checkAgainstConstraintsOnEntityId( $entity->getId() ); $this->assertCount( $this->constraintCount, $result, 'Every constraint should be represented by one result' ); foreach ( $result as $checkResult ) { $constraintTypesWithSparqlErrors = [ $this->getConstraintTypeItemId( 'DistinctValues' ), $this->getConstraintTypeItemId( 'Format' ), ]; $constraintType = $checkResult->getConstraint()->getConstraintTypeItemId(); if ( !in_array( $constraintType, $constraintTypesWithSparqlErrors ) ) { $this->assertNotSame( 'todo', $checkResult->getStatus(), 'Constraints should not be unimplemented' ); } $entityIds = $checkResult->getMetadata()->getDependencyMetadata()->getEntityIds(); $this->assertContains( $entity->getId(), $entityIds ); $this->assertContains( new PropertyId( 'P1' ), $entityIds, '', false, false // undocumented parameter: don’t check object identity ); } } public function testCheckOnEntityIdEmptyResult() { $entity = NewItem::withId( 'Q2' ) ->andStatement( NewStatement::forProperty( 'P2' ) ->withValue( 'foo' ) ) ->build(); $this->lookup->addEntity( $entity ); $result = $this->constraintChecker->checkAgainstConstraintsOnEntityId( $entity->getId() ); $this->assertEmpty( $result ); } public function testCheckOnEntityIdNullResult() { $statement = NewStatement::forProperty( 'P2' ) ->withValue( 'foo' ); $entity = NewItem::withId( 'Q2' ) ->andStatement( $statement ) ->andStatement( $statement ) ->build(); $this->lookup->addEntity( $entity ); $result = $this->constraintChecker->checkAgainstConstraintsOnEntityId( $entity->getId(), null, function( Context $context ) { return [ new NullResult( $context->getCursor() ) ]; } ); $this->assertCount( 2, $result ); } public function testCheckOnEntityIdNoStatements() { $entity = NewItem::withId( 'Q2' )->build(); $this->lookup->addEntity( $entity ); $result = $this->constraintChecker->checkAgainstConstraintsOnEntityId( $entity->getId(), null, null, function( EntityId $entityId ) { return [ new NullResult( new EntityContextCursor( $entityId->getSerialization() ) ) ]; } ); $this->assertCount( 1, $result ); } public function testCheckOnEntityIdUnknownConstraint() { $entity = NewItem::withId( 'Q3' ) ->andStatement( NewStatement::forProperty( 'P3' ) ->withValue( 'foo' ) ) ->build(); $this->lookup->addEntity( $entity ); $result = $this->constraintChecker->checkAgainstConstraintsOnEntityId( $entity->getId() ); $this->assertCount( 1, $result ); $this->assertTodo( $result[0] ); } public function testCheckOnEntityIdNoValue() { $entity = NewItem::withId( 'Q4' ) ->andStatement( NewStatement::noValueFor( 'P4' ) ) ->build(); $this->lookup->addEntity( $entity ); $result = $this->constraintChecker->checkAgainstConstraintsOnEntityId( $entity->getId() ); $this->assertEmpty( $result ); } public function testCheckOnEntityIdKnownException() { $entity = NewItem::withId( 'Q5' ) ->andStatement( NewStatement::forProperty( 'P10' ) ->withValue( 'foo' ) ) ->build(); $this->lookup->addEntity( $entity ); $result = $this->constraintChecker->checkAgainstConstraintsOnEntityId( $entity->getId() ); $this->assertEquals( 'exception', $result[ 0 ]->getStatus() ); } public function testCheckOnEntityIdBrokenException() { $entity = NewItem::withId( 'Q5' ) ->andStatement( NewStatement::noValueFor( 'P11' ) ) ->build(); $this->lookup->addEntity( $entity ); $result = $this->constraintChecker->checkAgainstConstraintsOnEntityId( $entity->getId() ); $this->assertEquals( 'bad-parameters', $result[ 0 ]->getStatus() ); } public function testCheckOnEntityIdMandatoryConstraint() { $entity = NewItem::withId( 'Q6' ) ->andStatement( NewStatement::noValueFor( 'P6' ) ) ->build(); $this->lookup->addEntity( $entity ); $results = $this->constraintChecker->checkAgainstConstraintsOnEntityId( $entity->getId() ); $this->assertCount( 1, $results ); $result = $results[0]; $this->assertViolation( $result ); $this->assertArrayHasKey( 'constraint_status', $result->getParameters() ); $this->assertSame( [ 'mandatory' ], $result->getParameters()[ 'constraint_status' ] ); } public function testCheckOnEntityIdNonMandatoryConstraint() { $entity = NewItem::withId( 'Q7' ) ->andStatement( NewStatement::noValueFor( 'P7' ) ) ->build(); $this->lookup->addEntity( $entity ); $result = $this->constraintChecker->checkAgainstConstraintsOnEntityId( $entity->getId() ); $this->assertEquals( 'warning', $result[ 0 ]->getStatus() ); } public function testCheckOnEntityIdSuggestionConstraint() { $entity = NewItem::withId( 'Q5' ) ->andStatement( NewStatement::noValueFor( 'P5' ) ) ->build(); $this->lookup->addEntity( $entity ); $results = $this->constraintChecker->checkAgainstConstraintsOnEntityId( $entity->getId() ); $this->assertCount( 1, $results ); $result = $results[0]; $this->assertSame( 'suggestion', $result->getStatus() ); $this->assertArrayHasKey( 'constraint_status', $result->getParameters() ); $this->assertSame( [ 'suggestion' ], $result->getParameters()[ 'constraint_status' ] ); } public function testCheckOnEntityIdSelectConstraintIds() { $entity = NewItem::withId( 'Q1' ) ->andStatement( NewStatement::forProperty( 'P1' ) ->withValue( 'foo' ) ) ->build(); $this->lookup->addEntity( $entity ); $result = $this->constraintChecker->checkAgainstConstraintsOnEntityId( $entity->getId(), [ 'P1$ecb8f617-90f1-4ef3-afab-f4bf3881ec28', 'P1$6ad9eb64-13fd-43a1-afc8-84857108bd59', 'P1$cfff6d73-320c-43c5-8582-e9cbb98e2ca2', ] ); $this->assertCount( 3, $result, 'Every selected constraint should be represented by one result' ); foreach ( $result as $checkResult ) { $this->assertNotSame( 'todo', $checkResult->getStatus(), 'Constraints should not be unimplemented' ); } } public function testCheckOnClaimId() { $statement = NewStatement::forProperty( 'P1' ) ->withValue( 'foo' ) ->withGuid( 'Q1$c0f25a6f-9e33-41c8-be34-c86a730ff30b' ) ->build(); $entity = NewItem::withId( 'Q1' ) ->andStatement( $statement ) ->build(); $this->lookup->addEntity( $entity ); $result = $this->constraintChecker->checkAgainstConstraintsOnClaimId( $statement->getGuid() ); $this->assertCount( $this->constraintCount, $result, 'Every constraint should be represented by one result' ); } public function testCheckOnClaimIdEmptyResult() { $statement = NewStatement::forProperty( 'P2' ) ->withValue( 'foo' ) ->withGuid( 'Q2$1d1fd258-91ca-4db5-91e4-26219c5aae7a' ) ->build(); $entity = NewItem::withId( 'Q2' ) ->andStatement( $statement ) ->build(); $this->lookup->addEntity( $entity ); $result = $this->constraintChecker->checkAgainstConstraintsOnClaimId( $statement->getGuid() ); $this->assertEmpty( $result ); } public function testCheckOnClaimIdUnknownClaimId() { $result = $this->constraintChecker->checkAgainstConstraintsOnClaimId( 'Q99$does-not-exist' ); $this->assertEmpty( $result ); } public function testCheckConstraintParametersOnPropertyId() { $entity = new Property( new PropertyId( 'P1' ), null, 'time' ); $this->lookup->addEntity( $entity ); $result = $this->constraintChecker->checkConstraintParametersOnPropertyId( $entity->getId() ); $this->assertCount( $this->constraintCount, $result, 'Every constraint should be represented by one result' ); foreach ( $result as $constraintGuid => $constraintResult ) { $this->assertSame( [], $constraintResult, 'Constraint should have no bad parameters' ); } } public function testCheckConstraintParametersOnPropertyIdWithError() { $result = $this->constraintChecker->checkConstraintParametersOnPropertyId( new PropertyId( 'P8' ) ); $this->assertCount( 1, $result, 'Every constraint should be represented by one result' ); $this->assertCount( 1, $result['P8$34c8af8e-bb50-4458-994b-f355ff899fff'], 'The constraint should have one exception' ); $this->assertInstanceOf( ConstraintParameterException::class, $result['P8$34c8af8e-bb50-4458-994b-f355ff899fff'][0] ); } public function testCheckConstraintParametersOnPropertyIdWithMetaErrors() { $result = $this->constraintChecker->checkConstraintParametersOnPropertyId( new PropertyId( 'P9' ) ); $this->assertCount( 1, $result, 'Every constraint should be represented by one result' ); $this->assertCount( 2, $result['P9$43053ee8-79da-4326-a2ac-f85098291db3'], 'The constraint should have two exceptions' ); $this->assertInstanceOf( ConstraintParameterException::class, $result['P9$43053ee8-79da-4326-a2ac-f85098291db3'][0] ); $this->assertInstanceOf( ConstraintParameterException::class, $result['P9$43053ee8-79da-4326-a2ac-f85098291db3'][1] ); } public function testCheckConstraintParametersOnConstraintId() { $result = $this->constraintChecker->checkConstraintParametersOnConstraintId( 'P1$ecb8f617-90f1-4ef3-afab-f4bf3881ec28' ); $this->assertSame( [], $result, 'Constraint should exist and have no bad parameters' ); } public function testCheckConstraintParametersOnConstraintIdWhenConstraintDoesNotExist() { $result = $this->constraintChecker->checkConstraintParametersOnConstraintId( 'P1$1735c111-e88c-42f9-8b7a-0692c9c797a3' ); $this->assertNull( $result, 'Constraint should not exist' ); } public function testCheckConstraintParametersOnConstraintIdWithError() { $result = $this->constraintChecker->checkConstraintParametersOnConstraintId( 'P8$34c8af8e-bb50-4458-994b-f355ff899fff' ); $this->assertCount( 1, $result, 'The constraint should have one exception' ); $this->assertInstanceOf( ConstraintParameterException::class, $result[0] ); } public function testCheckConstraintParametersOnConstraintIdWithMetaErrors() { $result = $this->constraintChecker->checkConstraintParametersOnConstraintId( 'P9$43053ee8-79da-4326-a2ac-f85098291db3' ); $this->assertCount( 2, $result, 'The constraint should have two exceptions' ); $this->assertInstanceOf( ConstraintParameterException::class, $result[0] ); $this->assertInstanceOf( ConstraintParameterException::class, $result[1] ); } public function testPropertiesWithViolatingQualifiers() { $q1 = new ItemId( 'Q1' ); $p2 = new PropertyId( 'P2' ); $qualifierNotToCheck = new PropertyValueSnak( $p2, new StringValue( 'do not check this ' ) ); $qualifierToCheck = new PropertyValueSnak( $p2, new StringValue( 'do check this ' ) ); $entityLookup = new InMemoryEntityLookup(); $entityLookup->addEntity( NewItem::withId( $q1 ) ->andStatement( new Statement( new PropertyNoValueSnak( new PropertyId( 'P1' ) ), new SnakList( [ $qualifierNotToCheck ] ) ) ) ->andStatement( new Statement( new PropertyNoValueSnak( new PropertyId( 'P11' ) ), new SnakList( [ $qualifierToCheck ] ) ) ) ->build() ); $delegatingConstraintChecker = new DelegatingConstraintChecker( $entityLookup, [ /* no constraint checkers, status "not implemented" is enough for this test */ ], new InMemoryConstraintLookup( [ new Constraint( 'P456$a34344b1-1843-4005-bd92-c082d7f7af2f', new PropertyId( 'P2' ), 'Q1', [] ), ] ), $this->getConstraintParameterParser(), new StatementGuidParser( new ItemIdParser() ), $this->getMockBuilder( LoggingHelper::class ) ->disableOriginalConstructor() ->getMock(), true, false, [ 'P1' ] ); $results = $delegatingConstraintChecker->checkAgainstConstraintsOnEntityId( $q1 ); $this->assertCount( 1, $results ); $this->assertSame( $qualifierToCheck->getHash(), $results[0]->getContextCursor()->getSnakHash() ); } public function testSupportedContextTypes_NotSupported() { $checker = $this->createMock( ConstraintChecker::class ); $checker->method( 'getSupportedContextTypes' ) ->willReturn( [ Context::TYPE_STATEMENT => CheckResult::STATUS_NOT_IN_SCOPE, ] ); $checker->method( 'getDefaultContextTypes' ) ->willReturn( [] ); $checker->expects( $this->never() )->method( 'checkConstraint' ); $lookup = new InMemoryEntityLookup(); $q2 = new ItemId( 'Q2' ); $lookup->addEntity( NewItem::withId( $q2 ) ->andStatement( NewStatement::noValueFor( 'P1' ) ) ->build() ); $delegatingConstraintChecker = new DelegatingConstraintChecker( $lookup, [ 'Q1' => $checker ], new InMemoryConstraintLookup( [ new Constraint( '', new PropertyId( 'P1' ), 'Q1', [] ) ] ), $this->getConstraintParameterParser(), new StatementGuidParser( new ItemIdParser() ), $this->getMockBuilder( LoggingHelper::class ) ->disableOriginalConstructor() ->getMock(), true, true, [] ); $results = $delegatingConstraintChecker->checkAgainstConstraintsOnEntityId( $q2 ); $this->assertCount( 1, $results ); $this->assertNotInScope( $results[0] ); } public function testSupportedContextTypes_NotImplemented() { $checker = $this->createMock( ConstraintChecker::class ); $checker->method( 'getSupportedContextTypes' ) ->willReturn( [ Context::TYPE_STATEMENT => CheckResult::STATUS_TODO, ] ); $checker->method( 'getDefaultContextTypes' ) ->willReturn( [ Context::TYPE_STATEMENT ] ); $checker->expects( $this->never() )->method( 'checkConstraint' ); $lookup = new InMemoryEntityLookup(); $q2 = new ItemId( 'Q2' ); $lookup->addEntity( NewItem::withId( $q2 ) ->andStatement( NewStatement::noValueFor( 'P1' ) ) ->build() ); $delegatingConstraintChecker = new DelegatingConstraintChecker( $lookup, [ 'Q1' => $checker ], new InMemoryConstraintLookup( [ new Constraint( '', new PropertyId( 'P1' ), 'Q1', [] ) ] ), $this->getConstraintParameterParser(), new StatementGuidParser( new ItemIdParser() ), $this->getMockBuilder( LoggingHelper::class ) ->disableOriginalConstructor() ->getMock(), true, true, [] ); $results = $delegatingConstraintChecker->checkAgainstConstraintsOnEntityId( $q2 ); $this->assertCount( 1, $results ); $this->assertTodo( $results[0] ); } public function testSupportedContextTypes_DefaultScope() { $checker = $this->createMock( ConstraintChecker::class ); $checker->method( 'getSupportedContextTypes' ) ->willReturn( [ Context::TYPE_STATEMENT => CheckResult::STATUS_COMPLIANCE, Context::TYPE_QUALIFIER => CheckResult::STATUS_COMPLIANCE, ] ); $checker->method( 'getDefaultContextTypes' ) ->willReturn( [ Context::TYPE_QUALIFIER ] ); $checker->expects( $this->once() ) ->method( 'checkConstraint' ) ->willReturnCallback( function( Context $context, Constraint $constraint ) { $this->assertSame( Context::TYPE_QUALIFIER, $context->getType() ); return new CheckResult( $context, $constraint ); } ); $lookup = new InMemoryEntityLookup(); $q2 = new ItemId( 'Q2' ); $lookup->addEntity( NewItem::withId( $q2 ) ->andStatement( NewStatement::noValueFor( 'P1' ) ->withQualifier( 'P1', 'value' ) ) ->build() ); $delegatingConstraintChecker = new DelegatingConstraintChecker( $lookup, [ 'Q1' => $checker ], new InMemoryConstraintLookup( [ new Constraint( '', new PropertyId( 'P1' ), 'Q1', [] ) ] ), $this->getConstraintParameterParser(), new StatementGuidParser( new ItemIdParser() ), $this->getMockBuilder( LoggingHelper::class ) ->disableOriginalConstructor() ->getMock(), true, true, [] ); $results = $delegatingConstraintChecker->checkAgainstConstraintsOnEntityId( $q2 ); } public function testSupportedContextTypes_ExplicitScope() { $checker = $this->createMock( ConstraintChecker::class ); $checker->method( 'getSupportedContextTypes' ) ->willReturn( [ Context::TYPE_STATEMENT => CheckResult::STATUS_COMPLIANCE, Context::TYPE_QUALIFIER => CheckResult::STATUS_COMPLIANCE, ] ); $checker->method( 'getDefaultContextTypes' ) ->willReturn( [ Context::TYPE_QUALIFIER ] ); $checker->expects( $this->once() ) ->method( 'checkConstraint' ) ->willReturnCallback( function( Context $context, Constraint $constraint ) { $this->assertSame( Context::TYPE_STATEMENT, $context->getType() ); return new CheckResult( $context, $constraint ); } ); $lookup = new InMemoryEntityLookup(); $q2 = new ItemId( 'Q2' ); $lookup->addEntity( NewItem::withId( $q2 ) ->andStatement( NewStatement::noValueFor( 'P1' ) ->withQualifier( 'P1', 'value' ) ) ->build() ); $delegatingConstraintChecker = new DelegatingConstraintChecker( $lookup, [ 'Q1' => $checker ], new InMemoryConstraintLookup( [ new Constraint( '', new PropertyId( 'P1' ), 'Q1', $this->constraintScopeParameter( [ Context::TYPE_STATEMENT ] ) ) ] ), $this->getConstraintParameterParser(), new StatementGuidParser( new ItemIdParser() ), $this->getMockBuilder( LoggingHelper::class ) ->disableOriginalConstructor() ->getMock(), true, true, [] ); $results = $delegatingConstraintChecker->checkAgainstConstraintsOnEntityId( $q2 ); } } diff --git a/tests/phpunit/Helper/ConstraintParameterParserTest.php b/tests/phpunit/Helper/ConstraintParameterParserTest.php index c97be84a..6a15a16d 100644 --- a/tests/phpunit/Helper/ConstraintParameterParserTest.php +++ b/tests/phpunit/Helper/ConstraintParameterParserTest.php @@ -1,1334 +1,1334 @@ constraint = new Constraint( 'constraint ID', new PropertyId( 'P1' ), 'constraint type Q-ID', [] ); } /** * @param string $itemId * @return array */ private function serializeItemId( $itemId ) { return $this->getSnakSerializer()->serialize( new PropertyValueSnak( new PropertyId( 'P1' ), new EntityIdValue( new ItemId( $itemId ) ) ) ); } /** * @param string $propertyId * @return array */ private function serializePropertyId( $propertyId ) { return $this->getSnakSerializer()->serialize( new PropertyValueSnak( new PropertyId( 'P1' ), new EntityIdValue( new PropertyId( $propertyId ) ) ) ); } /** * @param string $method * @param array $arguments * @param string $messageKey * @see \WikibaseQuality\ConstraintReport\Tests\ResultAssertions::assertViolation */ private function assertThrowsConstraintParameterException( $method, array $arguments, $messageKey ) { try { call_user_func_array( [ $this->getConstraintParameterParser(), $method ], $arguments ); $this->assertTrue( false, "$method should have thrown a ConstraintParameterException with message ⧼${messageKey}⧽." ); } catch ( ConstraintParameterException $exception ) { $checkResult = new CheckResult( new MainSnakContext( new Item( new ItemId( 'Q1' ) ), new Statement( new PropertyNoValueSnak( new PropertyId( 'P1' ) ) ) ), $this->constraint, [], CheckResult::STATUS_VIOLATION, $exception->getViolationMessage() ); $this->assertViolation( $checkResult, $messageKey ); } } public function testParseClassParameter() { $config = $this->getDefaultConfig(); $classId = $config->get( 'WBQualityConstraintsClassId' ); $parsed = $this->getConstraintParameterParser()->parseClassParameter( [ $classId => [ $this->serializeItemId( 'Q100' ), $this->serializeItemId( 'Q101' ) ] ], 'Q1' ); $this->assertEquals( [ 'Q100', 'Q101' ], $parsed ); } public function testParseClassParameter_Missing() { $this->assertThrowsConstraintParameterException( 'parseClassParameter', [ [], 'Q21503250' ], 'wbqc-violation-message-parameter-needed' ); } public function testParseClassParameter_NoValue() { $config = $this->getDefaultConfig(); $classId = $config->get( 'WBQualityConstraintsClassId' ); $this->assertThrowsConstraintParameterException( 'parseClassParameter', [ [ $classId => [ $this->getSnakSerializer()->serialize( new PropertyNoValueSnak( new PropertyId( $classId ) ) ) ] ], 'Q21503250' ], 'wbqc-violation-message-parameter-value' ); } public function testParseClassParameter_StringValue() { $config = $this->getDefaultConfig(); $classId = $config->get( 'WBQualityConstraintsClassId' ); $this->assertThrowsConstraintParameterException( 'parseClassParameter', [ [ $classId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( new PropertyId( $classId ), new StringValue( 'Q100' ) ) ) ] ], 'Q21503250' ], 'wbqc-violation-message-parameter-entity' ); } public function testParseRelationParameter() { $config = $this->getDefaultConfig(); $relationId = $config->get( 'WBQualityConstraintsRelationId' ); $instanceOfId = $config->get( 'WBQualityConstraintsInstanceOfRelationId' ); $parsed = $this->getConstraintParameterParser()->parseRelationParameter( [ $relationId => [ $this->serializeItemId( $instanceOfId ) ] ], 'Q21503250' ); $this->assertEquals( 'instance', $parsed ); } public function testParseRelationParameter_Missing() { $this->assertThrowsConstraintParameterException( 'parseRelationParameter', [ [], 'Q21503250' ], 'wbqc-violation-message-parameter-needed' ); } public function testParseRelationParameter_NoValue() { $config = $this->getDefaultConfig(); $relationId = $config->get( 'WBQualityConstraintsRelationId' ); $this->assertThrowsConstraintParameterException( 'parseRelationParameter', [ [ $relationId => [ $this->getSnakSerializer()->serialize( new PropertyNoValueSnak( new PropertyId( $relationId ) ) ) ] ], 'Q21503250' ], 'wbqc-violation-message-parameter-value' ); } public function testParseRelationParameter_StringValue() { $config = $this->getDefaultConfig(); $relationId = $config->get( 'WBQualityConstraintsRelationId' ); $this->assertThrowsConstraintParameterException( 'parseRelationParameter', [ [ $relationId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( new PropertyId( $relationId ), new StringValue( 'instance' ) ) ) ] ], 'Q21503250' ], 'wbqc-violation-message-parameter-entity' ); } public function testParseRelationParameter_MultiValue() { $config = $this->getDefaultConfig(); $relationId = $config->get( 'WBQualityConstraintsRelationId' ); $instanceOfId = $config->get( 'WBQualityConstraintsInstanceOfRelationId' ); $subclassOfId = $config->get( 'WBQualityConstraintsSubclassOfRelationId' ); $this->assertThrowsConstraintParameterException( 'parseRelationParameter', [ [ $relationId => [ $this->serializeItemId( $instanceOfId ), $this->serializeItemId( $subclassOfId ) ] ], 'Q21503250' ], 'wbqc-violation-message-parameter-single' ); } public function testParseRelationParameter_WrongValue() { $config = $this->getDefaultConfig(); $relationId = $config->get( 'WBQualityConstraintsRelationId' ); $this->assertThrowsConstraintParameterException( 'parseRelationParameter', [ [ $relationId => [ $this->serializeItemId( 'Q1' ) ] ], 'Q21503250' ], 'wbqc-violation-message-parameter-oneof' ); } public function testParsePropertyParameter() { $config = $this->getDefaultConfig(); $propertyId = $config->get( 'WBQualityConstraintsPropertyId' ); $parsed = $this->getConstraintParameterParser()->parsePropertyParameter( [ $propertyId => [ $this->serializePropertyId( 'P100' ) ] ], 'Q21510856' ); $this->assertEquals( new PropertyId( 'P100' ), $parsed ); } public function testParsePropertyParameter_Missing() { $this->assertThrowsConstraintParameterException( 'parsePropertyParameter', [ [], 'Q21510856' ], 'wbqc-violation-message-parameter-needed' ); } public function testParsePropertyParameter_NoValue() { $config = $this->getDefaultConfig(); $propertyId = $config->get( 'WBQualityConstraintsPropertyId' ); $this->assertThrowsConstraintParameterException( 'parsePropertyParameter', [ [ $propertyId => [ $this->getSnakSerializer()->serialize( new PropertyNoValueSnak( new PropertyId( $propertyId ) ) ) ] ], 'Q21510856' ], 'wbqc-violation-message-parameter-value' ); } public function testParsePropertyParameter_StringValue() { $config = $this->getDefaultConfig(); $propertyId = $config->get( 'WBQualityConstraintsPropertyId' ); $this->assertThrowsConstraintParameterException( 'parsePropertyParameter', [ [ $propertyId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( new PropertyId( $propertyId ), new StringValue( 'P1' ) ) ) ] ], 'Q21510856' ], 'wbqc-violation-message-parameter-property' ); } public function testParsePropertyParameter_ItemId() { $config = $this->getDefaultConfig(); $propertyId = $config->get( 'WBQualityConstraintsPropertyId' ); $this->assertThrowsConstraintParameterException( 'parsePropertyParameter', [ [ $propertyId => [ $this->serializeItemId( 'Q100' ) ] ], 'Q21510856' ], 'wbqc-violation-message-parameter-property' ); } public function testParsePropertyParameter_MultiValue() { $config = $this->getDefaultConfig(); $propertyId = $config->get( 'WBQualityConstraintsPropertyId' ); $this->assertThrowsConstraintParameterException( 'parsePropertyParameter', [ [ $propertyId => [ $this->serializePropertyId( 'P100' ), $this->serializePropertyId( 'P101' ) ] ], 'Q21510856' ], 'wbqc-violation-message-parameter-single' ); } public function testParseItemsParameter() { $config = $this->getDefaultConfig(); $qualifierId = $config->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); $parsed = $this->getConstraintParameterParser()->parseItemsParameter( [ $qualifierId => [ $this->serializeItemId( 'Q100' ), $this->serializeItemId( 'Q101' ), $this->getSnakSerializer()->serialize( new PropertySomeValueSnak( new PropertyId( 'P1' ) ) ), $this->getSnakSerializer()->serialize( new PropertyNoValueSnak( new PropertyId( 'P1' ) ) ) ] ], 'Q21510859', false ); $expected = [ ItemIdSnakValue::fromItemId( new ItemId( 'Q100' ) ), ItemIdSnakValue::fromItemId( new ItemId( 'Q101' ) ), ItemIdSnakValue::someValue(), ItemIdSnakValue::noValue() ]; $this->assertEquals( $expected, $parsed ); } public function testParseItemsParameter_Required() { $this->assertThrowsConstraintParameterException( 'parseItemsParameter', [ [], 'Q21510859', true ], 'wbqc-violation-message-parameter-needed' ); } public function testParseItemsParameter_NotRequired() { $parsed = $this->getConstraintParameterParser()->parseItemsParameter( [], 'Q21510859', false ); $this->assertEquals( [], $parsed ); } public function testParseItemsParameter_StringValue() { $config = $this->getDefaultConfig(); $qualifierId = $config->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); $this->assertThrowsConstraintParameterException( 'parseItemsParameter', [ [ $qualifierId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( new PropertyId( 'P1' ), new StringValue( 'Q100' ) ) ) ] ], 'Q21510859', true ], 'wbqc-violation-message-parameter-item' ); } public function testParseItemsParameter_PropertyId() { $config = $this->getDefaultConfig(); $qualifierId = $config->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); $this->assertThrowsConstraintParameterException( 'parseItemsParameter', [ [ $qualifierId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( new PropertyId( 'P1' ), new EntityIdValue( new PropertyId( 'P100' ) ) ) ) ] ], 'Q21510859', true ], 'wbqc-violation-message-parameter-item' ); } public function testParsePropertiesParameter() { $config = $this->getDefaultConfig(); $propertyId = $config->get( 'WBQualityConstraintsPropertyId' ); $parsed = $this->getConstraintParameterParser()->parsePropertiesParameter( [ $propertyId => [ $this->serializePropertyId( 'P100' ), $this->serializePropertyId( 'P101' ) ] ], 'Q21510851' ); $this->assertEquals( [ new PropertyId( 'P100' ), new PropertyId( 'P101' ) ], $parsed ); } public function testParsePropertiesParameter_Missing() { $this->assertThrowsConstraintParameterException( 'parsePropertiesParameter', [ [], 'Q21510851' ], 'wbqc-violation-message-parameter-needed' ); } public function testParsePropertiesParameter_NoValue() { $config = $this->getDefaultConfig(); $propertyId = $config->get( 'WBQualityConstraintsPropertyId' ); $parsed = $this->getConstraintParameterParser()->parsePropertiesParameter( [ $propertyId => [ $this->getSnakSerializer()->serialize( new PropertyNoValueSnak( new PropertyId( $propertyId ) ) ) ] ], 'Q21510851' ); $this->assertEquals( [], $parsed ); } public function testParseQuantityRange_Bounded() { $config = $this->getDefaultConfig(); $minimumId = $config->get( 'WBQualityConstraintsMinimumQuantityId' ); $maximumId = $config->get( 'WBQualityConstraintsMaximumQuantityId' ); $propertyId = new PropertyId( 'P1' ); $min = UnboundedQuantityValue::newFromNumber( 13.37 ); $max = UnboundedQuantityValue::newFromNumber( 42 ); $parsed = $this->getConstraintParameterParser()->parseQuantityRangeParameter( [ $minimumId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( $propertyId, $min ) ) ], $maximumId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( $propertyId, $max ) ) ] ], 'Q21510860' ); $this->assertEquals( [ $min, $max ], $parsed ); } public function testParseQuantityRange_LeftOpen() { $config = $this->getDefaultConfig(); $minimumId = $config->get( 'WBQualityConstraintsMinimumQuantityId' ); $maximumId = $config->get( 'WBQualityConstraintsMaximumQuantityId' ); $propertyId = new PropertyId( 'P1' ); $max = UnboundedQuantityValue::newFromNumber( 42 ); $parsed = $this->getConstraintParameterParser()->parseQuantityRangeParameter( [ $minimumId => [ $this->getSnakSerializer()->serialize( new PropertyNoValueSnak( $propertyId ) ) ], $maximumId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( $propertyId, $max ) ) ] ], 'Q21510860' ); $this->assertEquals( [ null, $max ], $parsed ); } public function testParseQuantityRange_RightOpen() { $config = $this->getDefaultConfig(); $minimumId = $config->get( 'WBQualityConstraintsMinimumQuantityId' ); $maximumId = $config->get( 'WBQualityConstraintsMaximumQuantityId' ); $propertyId = new PropertyId( 'P1' ); $min = UnboundedQuantityValue::newFromNumber( 13.37 ); $parsed = $this->getConstraintParameterParser()->parseQuantityRangeParameter( [ $minimumId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( $propertyId, $min ) ) ], $maximumId => [ $this->getSnakSerializer()->serialize( new PropertyNoValueSnak( $propertyId ) ) ] ], 'Q21510860' ); $this->assertEquals( [ $min, null ], $parsed ); } public function testParseQuantityRange_FullyOpen() { $config = $this->getDefaultConfig(); $minimumId = $config->get( 'WBQualityConstraintsMinimumQuantityId' ); $maximumId = $config->get( 'WBQualityConstraintsMaximumQuantityId' ); $propertyId = new PropertyId( 'P1' ); $this->assertThrowsConstraintParameterException( 'parseQuantityRangeParameter', [ [ $minimumId => [ $this->getSnakSerializer()->serialize( new PropertyNoValueSnak( $propertyId ) ) ], $maximumId => [ $this->getSnakSerializer()->serialize( new PropertyNoValueSnak( $propertyId ) ) ] ], 'Q21510860' ], 'wbqc-violation-message-range-parameters-same' ); } public function testParseQuantityRange_SomeValue() { $config = $this->getDefaultConfig(); $minimumId = $config->get( 'WBQualityConstraintsMinimumQuantityId' ); $maximumId = $config->get( 'WBQualityConstraintsMaximumQuantityId' ); $propertyId = new PropertyId( 'P1' ); $this->assertThrowsConstraintParameterException( 'parseQuantityRangeParameter', [ [ $minimumId => [ $this->getSnakSerializer()->serialize( new PropertySomeValueSnak( $propertyId ) ) ], $maximumId => [ $this->getSnakSerializer()->serialize( new PropertySomeValueSnak( $propertyId ) ) ] ], 'Q21510860' ], 'wbqc-violation-message-parameter-value-or-novalue' ); } public function testParseQuantityRange_Same() { $config = $this->getDefaultConfig(); $minimumId = $config->get( 'WBQualityConstraintsMinimumQuantityId' ); $maximumId = $config->get( 'WBQualityConstraintsMaximumQuantityId' ); $propertyId = new PropertyId( 'P1' ); $quantity = UnboundedQuantityValue::newFromNumber( 13.37 ); $this->assertThrowsConstraintParameterException( 'parseQuantityRangeParameter', [ [ $minimumId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( $propertyId, $quantity ) ) ], $maximumId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( $propertyId, $quantity ) ) ] ], 'Q21510860' ], 'wbqc-violation-message-range-parameters-same' ); } public function testParseTimeRange_Bounded() { $config = $this->getDefaultConfig(); $minimumId = $config->get( 'WBQualityConstraintsMinimumDateId' ); $maximumId = $config->get( 'WBQualityConstraintsMaximumDateId' ); $propertyId = new PropertyId( 'P1' ); $calendar = TimeValue::CALENDAR_GREGORIAN; $min = new TimeValue( '+1789-05-08T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_YEAR, $calendar ); $max = new TimeValue( '+1955-02-05T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_YEAR, $calendar ); $parsed = $this->getConstraintParameterParser()->parseTimeRangeParameter( [ $minimumId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( $propertyId, $min ) ) ], $maximumId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( $propertyId, $max ) ) ] ], 'Q21510860' ); $this->assertEquals( [ $min, $max ], $parsed ); } public function testParseTimeRange_Past() { $config = $this->getDefaultConfig(); $minimumId = $config->get( 'WBQualityConstraintsMinimumDateId' ); $maximumId = $config->get( 'WBQualityConstraintsMaximumDateId' ); $propertyId = new PropertyId( 'P1' ); $parsed = $this->getConstraintParameterParser()->parseTimeRangeParameter( [ $minimumId => [ $this->getSnakSerializer()->serialize( new PropertyNoValueSnak( $propertyId ) ) ], $maximumId => [ $this->getSnakSerializer()->serialize( new PropertySomeValueSnak( $propertyId ) ) ] ], 'Q21510860' ); $this->assertEquals( [ null, new NowValue() ], $parsed ); } public function testParseTimeRange_Future() { $config = $this->getDefaultConfig(); $minimumId = $config->get( 'WBQualityConstraintsMinimumDateId' ); $maximumId = $config->get( 'WBQualityConstraintsMaximumDateId' ); $propertyId = new PropertyId( 'P1' ); $parsed = $this->getConstraintParameterParser()->parseTimeRangeParameter( [ $minimumId => [ $this->getSnakSerializer()->serialize( new PropertySomeValueSnak( $propertyId ) ) ], $maximumId => [ $this->getSnakSerializer()->serialize( new PropertyNoValueSnak( $propertyId ) ) ] ], 'Q21510860' ); $this->assertEquals( [ new NowValue(), null ], $parsed ); } public function testParseTimeRange_BothNow() { $config = $this->getDefaultConfig(); $minimumId = $config->get( 'WBQualityConstraintsMinimumDateId' ); $maximumId = $config->get( 'WBQualityConstraintsMaximumDateId' ); $propertyId = new PropertyId( 'P1' ); $this->assertThrowsConstraintParameterException( 'parseTimeRangeParameter', [ [ $minimumId => [ $this->getSnakSerializer()->serialize( new PropertySomeValueSnak( $propertyId ) ) ], $maximumId => [ $this->getSnakSerializer()->serialize( new PropertySomeValueSnak( $propertyId ) ) ] ], 'Q21510860' ], 'wbqc-violation-message-range-parameters-same' ); } public function testParseTimeRange_Wikidata() { // range: from the inception of Wikidata until now // (NowValue uses the same date internally, but that should not result in a “same range endpoints” error) $config = $this->getDefaultConfig(); $minimumId = $config->get( 'WBQualityConstraintsMinimumDateId' ); $maximumId = $config->get( 'WBQualityConstraintsMaximumDateId' ); $propertyId = new PropertyId( 'P1' ); $wikidataInception = new TimeValue( '+2012-10-29T00:00:00Z', 0, 0, 0, TimeValue::PRECISION_SECOND, TimeValue::CALENDAR_GREGORIAN ); $parsed = $this->getConstraintParameterParser()->parseTimeRangeParameter( [ $minimumId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( $propertyId, $wikidataInception ) ) ], $maximumId => [ $this->getSnakSerializer()->serialize( new PropertySomeValueSnak( $propertyId ) ) ] ], 'Q21510860' ); $this->assertEquals( [ $wikidataInception, new NowValue() ], $parsed ); } public function testParseQuantityRange_OneYear() { $config = $this->getDefaultConfig(); $minimumId = $config->get( 'WBQualityConstraintsMinimumQuantityId' ); $maximumId = $config->get( 'WBQualityConstraintsMaximumQuantityId' ); $yearUnit = $config->get( 'WBQualityConstraintsYearUnit' ); $min = UnboundedQuantityValue::newFromNumber( 0, 'other unit than ' . $yearUnit ); $max = UnboundedQuantityValue::newFromNumber( 150, $yearUnit ); $minSnak = new PropertyValueSnak( new PropertyId( $minimumId ), $min ); $maxSnak = new PropertyValueSnak( new PropertyId( $maximumId ), $max ); $this->assertThrowsConstraintParameterException( 'parseQuantityRangeParameter', [ [ $minimumId => [ $this->getSnakSerializer()->serialize( $minSnak ) ], $maximumId => [ $this->getSnakSerializer()->serialize( $maxSnak ) ] ], 'Q21510860' ], 'wbqc-violation-message-range-parameters-one-year' ); } public function testParseQuantityRange_OneYear_LeftOpen() { $config = $this->getDefaultConfig(); $minimumId = $config->get( 'WBQualityConstraintsMinimumQuantityId' ); $maximumId = $config->get( 'WBQualityConstraintsMaximumQuantityId' ); $yearUnit = $config->get( 'WBQualityConstraintsYearUnit' ); $max = UnboundedQuantityValue::newFromNumber( 150, $yearUnit ); $minSnak = new PropertyNoValueSnak( new PropertyId( $minimumId ) ); $maxSnak = new PropertyValueSnak( new PropertyId( $maximumId ), $max ); $parsed = $this->getConstraintParameterParser()->parseQuantityRangeParameter( [ $minimumId => [ $this->getSnakSerializer()->serialize( $minSnak ) ], $maximumId => [ $this->getSnakSerializer()->serialize( $maxSnak ) ] ], 'Q21510860' ); $this->assertEquals( [ null, $max ], $parsed ); } public function testParseRange_MissingParameters() { foreach ( [ 'parseQuantityRangeParameter', 'parseTimeRangeParameter' ] as $method ) { $this->assertThrowsConstraintParameterException( $method, [ [], 'Q21510860' ], 'wbqc-violation-message-range-parameters-needed' ); } } public function testParseNamespaceParameter() { $namespaceId = $this->getDefaultConfig()->get( 'WBQualityConstraintsNamespaceId' ); $value = new StringValue( 'File' ); $snak = new PropertyValueSnak( new PropertyId( 'P1' ), $value ); $parsed = $this->getConstraintParameterParser()->parseNamespaceParameter( [ $namespaceId => [ $this->getSnakSerializer()->serialize( $snak ) ] ], 'Q21510852' ); $this->assertEquals( 'File', $parsed ); } public function testParseNamespaceParameter_Missing() { $parsed = $this->getConstraintParameterParser()->parseNamespaceParameter( [], 'Q21510852' ); $this->assertEquals( '', $parsed ); } public function testParseNamespaceParameter_ItemId() { $namespaceId = $this->getDefaultConfig()->get( 'WBQualityConstraintsNamespaceId' ); $value = new EntityIdValue( new ItemId( 'Q1' ) ); $snak = new PropertyValueSnak( new PropertyId( 'P1' ), $value ); $this->assertThrowsConstraintParameterException( 'parseNamespaceParameter', [ [ $namespaceId => [ $this->getSnakSerializer()->serialize( $snak ) ] ], 'Q21510852' ], 'wbqc-violation-message-parameter-string' ); } public function testParseNamespaceParameter_Multiple() { $namespaceId = $this->getDefaultConfig()->get( 'WBQualityConstraintsNamespaceId' ); $value1 = new StringValue( 'File' ); $snak1 = new PropertyValueSnak( new PropertyId( 'P1' ), $value1 ); $value2 = new StringValue( 'Category' ); $snak2 = new PropertyValueSnak( new PropertyId( 'P1' ), $value2 ); $this->assertThrowsConstraintParameterException( 'parseNamespaceParameter', [ [ $namespaceId => [ $this->getSnakSerializer()->serialize( $snak1 ), $this->getSnakSerializer()->serialize( $snak2 ) ] ], 'Q21510852' ], 'wbqc-violation-message-parameter-single' ); } public function testParseFormatParameter() { $formatId = $this->getDefaultConfig()->get( 'WBQualityConstraintsFormatAsARegularExpressionId' ); $value = new StringValue( '\d\.(\d{1,2}|-{1})\.(\d{1,2}|-{1})\.(\d{1,3}|-{1})' ); $snak = new PropertyValueSnak( new PropertyId( 'P1' ), $value ); $parsed = $this->getConstraintParameterParser()->parseFormatParameter( [ $formatId => [ $this->getSnakSerializer()->serialize( $snak ) ] ], 'Q21502404' ); $this->assertEquals( '\d\.(\d{1,2}|-{1})\.(\d{1,2}|-{1})\.(\d{1,3}|-{1})', $parsed ); } public function testParseFormatParameter_Missing() { $this->assertThrowsConstraintParameterException( 'parseFormatParameter', [ [], 'Q21502404' ], 'wbqc-violation-message-parameter-needed' ); } public function testParseFormatParameter_ItemId() { $formatId = $this->getDefaultConfig()->get( 'WBQualityConstraintsFormatAsARegularExpressionId' ); $value = new EntityIdValue( new ItemId( 'Q1' ) ); $snak = new PropertyValueSnak( new PropertyId( 'P1' ), $value ); $this->assertThrowsConstraintParameterException( 'parseFormatParameter', [ [ $formatId => [ $this->getSnakSerializer()->serialize( $snak ) ] ], 'Q21502404' ], 'wbqc-violation-message-parameter-string' ); } public function testParseFormatParameter_Multiple() { $formatId = $this->getDefaultConfig()->get( 'WBQualityConstraintsFormatAsARegularExpressionId' ); $value1 = new StringValue( '\d\.(\d{1,2}|-{1})\.(\d{1,2}|-{1})\.(\d{1,3}|-{1})' ); $snak1 = new PropertyValueSnak( new PropertyId( 'P1' ), $value1 ); $value2 = new StringValue( '\d+' ); $snak2 = new PropertyValueSnak( new PropertyId( 'P1' ), $value2 ); $this->assertThrowsConstraintParameterException( 'parseFormatParameter', [ [ $formatId => [ $this->getSnakSerializer()->serialize( $snak1 ), $this->getSnakSerializer()->serialize( $snak2 ) ] ], 'Q21502404' ], 'wbqc-violation-message-parameter-single' ); } public function testParseExceptionParameter() { $exceptionId = $this->getDefaultConfig()->get( 'WBQualityConstraintsExceptionToConstraintId' ); $entityId1 = new ItemId( 'Q100' ); $entityId2 = new PropertyId( 'P100' ); $snak1 = new PropertyValueSnak( new PropertyId( $exceptionId ), new EntityIdValue( $entityId1 ) ); $snak2 = new PropertyValueSnak( new PropertyId( $exceptionId ), new EntityIdValue( $entityId2 ) ); $parsed = $this->getConstraintParameterParser()->parseExceptionParameter( [ $exceptionId => [ $this->getSnakSerializer()->serialize( $snak1 ), $this->getSnakSerializer()->serialize( $snak2 ), ] ] ); $this->assertEquals( [ $entityId1, $entityId2 ], $parsed ); } public function testParseExceptionParameter_Missing() { $parsed = $this->getConstraintParameterParser()->parseExceptionParameter( [] ); $this->assertEquals( [], $parsed ); } public function testParseExceptionParameter_String() { $exceptionId = $this->getDefaultConfig()->get( 'WBQualityConstraintsExceptionToConstraintId' ); $snak = new PropertyValueSnak( new PropertyId( $exceptionId ), new StringValue( 'Q100' ) ); $this->assertThrowsConstraintParameterException( 'parseExceptionParameter', [ [ $exceptionId => [ $this->getSnakSerializer()->serialize( $snak ) ] ] ], 'wbqc-violation-message-parameter-entity' ); } public function testParseExceptionParameter_TooLong() { $this->assertThrowsConstraintParameterException( 'parseExceptionParameter', [ [ '@error' => [ 'toolong' => true ] ] ], 'wbqc-violation-message-parameters-error-toolong' ); } public function testParseConstraintStatusParameter_mandatory() { $constraintStatusId = $this->getDefaultConfig()->get( 'WBQualityConstraintsConstraintStatusId' ); $mandatoryId = $this->getDefaultConfig()->get( 'WBQualityConstraintsMandatoryConstraintId' ); $snak = new PropertyValueSnak( new PropertyId( $constraintStatusId ), new EntityIdValue( new ItemId( $mandatoryId ) ) ); $parsed = $this->getConstraintParameterParser()->parseConstraintStatusParameter( [ $constraintStatusId => [ $this->getSnakSerializer()->serialize( $snak ) ] ] ); $this->assertEquals( 'mandatory', $parsed ); } public function testParseConstraintStatusParameter_suggestion_disabled() { $constraintStatusId = $this->getDefaultConfig()->get( 'WBQualityConstraintsConstraintStatusId' ); $suggestionId = $this->getDefaultConfig()->get( 'WBQualityConstraintsSuggestionConstraintId' ); $snak = new PropertyValueSnak( new PropertyId( $constraintStatusId ), new EntityIdValue( new ItemId( $suggestionId ) ) ); $this->assertThrowsConstraintParameterException( 'parseConstraintStatusParameter', [ [ $constraintStatusId => [ $this->getSnakSerializer()->serialize( $snak ) ] ] ], 'wbqc-violation-message-parameter-oneof' ); } public function testParseConstraintStatusParameter_suggestion_enabled() { $constraintStatusId = $this->getDefaultConfig()->get( 'WBQualityConstraintsConstraintStatusId' ); $suggestionId = $this->getDefaultConfig()->get( 'WBQualityConstraintsSuggestionConstraintId' ); $snak = new PropertyValueSnak( new PropertyId( $constraintStatusId ), new EntityIdValue( new ItemId( $suggestionId ) ) ); $constraintParameterParser = new ConstraintParameterParser( new MultiConfig( [ new HashConfig( [ 'WBQualityConstraintsEnableSuggestionConstraintStatus' => true ] ), $this->getDefaultConfig() ] ), WikibaseRepo::getDefaultInstance()->getBaseDataModelDeserializerFactory(), - [ '' => 'http://wikibase.example/entity/' ] + 'http://wikibase.example/entity/' ); $parsed = $constraintParameterParser->parseConstraintStatusParameter( [ $constraintStatusId => [ $this->getSnakSerializer()->serialize( $snak ) ] ] ); $this->assertEquals( 'suggestion', $parsed ); } public function testParseConstraintStatusParameter_Missing() { $parsed = $this->getConstraintParameterParser()->parseConstraintStatusParameter( [] ); $this->assertNull( $parsed ); } public function testParseConstraintStatusParameter_Invalid() { $constraintStatusId = $this->getDefaultConfig()->get( 'WBQualityConstraintsConstraintStatusId' ); $snak = new PropertyValueSnak( new PropertyId( $constraintStatusId ), new EntityIdValue( new ItemId( 'Q1' ) ) ); $this->assertThrowsConstraintParameterException( 'parseConstraintStatusParameter', [ [ $constraintStatusId => [ $this->getSnakSerializer()->serialize( $snak ) ] ] ], 'wbqc-violation-message-parameter-oneof' ); } public function testParseConstraintStatusParameter_UnknownError() { $this->assertThrowsConstraintParameterException( 'parseExceptionParameter', [ [ '@error' => [] ] ], 'wbqc-violation-message-parameters-error-unknown' ); } public function testParseSyntaxClarificationParameter_SingleClarification() { $syntaxClarificationId = $this->getDefaultConfig()->get( 'WBQualityConstraintsSyntaxClarificationId' ); $value = new MonolingualTextValue( 'en', 'explanation' ); $snak = new PropertyValueSnak( new PropertyId( $syntaxClarificationId ), $value ); $parsed = $this->getConstraintParameterParser()->parseSyntaxClarificationParameter( [ $syntaxClarificationId => [ $this->getSnakSerializer()->serialize( $snak ) ] ] ); $this->assertEquals( new MultilingualTextValue( [ $value ] ), $parsed ); } public function testParseSyntaxClarificationParameter_MultipleClarifications() { $syntaxClarificationId = $this->getDefaultConfig()->get( 'WBQualityConstraintsSyntaxClarificationId' ); $value1 = new MonolingualTextValue( 'en', 'explanation' ); $snak1 = new PropertyValueSnak( new PropertyId( $syntaxClarificationId ), $value1 ); $value2 = new MonolingualTextValue( 'de', 'Erklärung' ); $snak2 = new PropertyValueSnak( new PropertyId( $syntaxClarificationId ), $value2 ); $value3 = new MonolingualTextValue( 'pt', 'explicação' ); $snak3 = new PropertyValueSnak( new PropertyId( $syntaxClarificationId ), $value3 ); $parsed = $this->getConstraintParameterParser()->parseSyntaxClarificationParameter( [ $syntaxClarificationId => [ $this->getSnakSerializer()->serialize( $snak1 ), $this->getSnakSerializer()->serialize( $snak2 ), $this->getSnakSerializer()->serialize( $snak3 ), ] ] ); $this->assertEquals( new MultilingualTextValue( [ $value1, $value2, $value3 ] ), $parsed ); } public function testParseSyntaxClarificationParameter_NoClarifications() { $parsed = $this->getConstraintParameterParser()->parseSyntaxClarificationParameter( [] ); $this->assertEquals( new MultilingualTextValue( [] ), $parsed ); } public function testParseSyntaxClarificationParameter_Invalid_MultipleValuesForLanguage() { $syntaxClarificationId = $this->getDefaultConfig()->get( 'WBQualityConstraintsSyntaxClarificationId' ); $value1 = new MonolingualTextValue( 'en', 'explanation' ); $snak1 = new PropertyValueSnak( new PropertyId( $syntaxClarificationId ), $value1 ); $value2 = new MonolingualTextValue( 'en', 'better explanation' ); $snak2 = new PropertyValueSnak( new PropertyId( $syntaxClarificationId ), $value2 ); $this->assertThrowsConstraintParameterException( 'parseSyntaxClarificationParameter', [ [ $syntaxClarificationId => [ $this->getSnakSerializer()->serialize( $snak1 ), $this->getSnakSerializer()->serialize( $snak2 ), ] ] ], 'wbqc-violation-message-parameter-single-per-language' ); } public function testParseSyntaxClarificationParameter_Invalid_String() { $syntaxClarificationId = $this->getDefaultConfig()->get( 'WBQualityConstraintsSyntaxClarificationId' ); $value = new StringValue( 'explanation' ); $snak = new PropertyValueSnak( new PropertyId( $syntaxClarificationId ), $value ); $this->assertThrowsConstraintParameterException( 'parseSyntaxClarificationParameter', [ [ $syntaxClarificationId => [ $this->getSnakSerializer()->serialize( $snak ) ] ] ], 'wbqc-violation-message-parameter-monolingualtext' ); } public function testParseSyntaxClarificationParameter_Invalid_Novalue() { $syntaxClarificationId = $this->getDefaultConfig()->get( 'WBQualityConstraintsSyntaxClarificationId' ); $snak = new PropertyNoValueSnak( new PropertyId( $syntaxClarificationId ) ); $this->assertThrowsConstraintParameterException( 'parseSyntaxClarificationParameter', [ [ $syntaxClarificationId => [ $this->getSnakSerializer()->serialize( $snak ) ] ] ], 'wbqc-violation-message-parameter-value' ); } public function testParseConstraintScopeParameter_MainSnak() { $constraintScopeId = $this->getDefaultConfig()->get( 'WBQualityConstraintsConstraintScopeId' ); $mainSnakId = new ItemId( $this->getDefaultConfig()->get( 'WBQualityConstraintsConstraintCheckedOnMainValueId' ) ); $snak = new PropertyValueSnak( new PropertyId( $constraintScopeId ), new EntityIdValue( $mainSnakId ) ); $parsed = $this->getConstraintParameterParser()->parseConstraintScopeParameter( [ $constraintScopeId => [ $this->getSnakSerializer()->serialize( $snak ), ] ], 'Q21502838' ); $this->assertSame( [ Context::TYPE_STATEMENT ], $parsed ); } public function testParseConstraintScopeParameter_NotMainSnak() { $constraintScopeId = $this->getDefaultConfig()->get( 'WBQualityConstraintsConstraintScopeId' ); $qualifiersId = new ItemId( $this->getDefaultConfig()->get( 'WBQualityConstraintsConstraintCheckedOnQualifiersId' ) ); $referencesId = new ItemId( $this->getDefaultConfig()->get( 'WBQualityConstraintsConstraintCheckedOnReferencesId' ) ); $snak1 = new PropertyValueSnak( new PropertyId( $constraintScopeId ), new EntityIdValue( $qualifiersId ) ); $snak2 = new PropertyValueSnak( new PropertyId( $constraintScopeId ), new EntityIdValue( $referencesId ) ); $parsed = $this->getConstraintParameterParser()->parseConstraintScopeParameter( [ $constraintScopeId => [ $this->getSnakSerializer()->serialize( $snak1 ), $this->getSnakSerializer()->serialize( $snak2 ), ] ], 'Q21502838' ); $this->assertSame( [ Context::TYPE_QUALIFIER, Context::TYPE_REFERENCE ], $parsed ); } public function testParseConstraintScopeParameter_Missing() { $parsed = $this->getConstraintParameterParser()->parseConstraintScopeParameter( [], 'Q21502838' ); $this->assertNull( $parsed ); } public function testParseConstraintParameter_ValidScope() { $constraintScopeId = $this->getDefaultConfig()->get( 'WBQualityConstraintsConstraintScopeId' ); $qualifiersId = new ItemId( $this->getDefaultConfig()->get( 'WBQualityConstraintsConstraintCheckedOnQualifiersId' ) ); $snak = new PropertyValueSnak( new PropertyId( $constraintScopeId ), new EntityIdValue( $qualifiersId ) ); $parsed = $this->getConstraintParameterParser()->parseConstraintScopeParameter( [ $constraintScopeId => [ $this->getSnakSerializer()->serialize( $snak ), ] ], 'Q21502838', [ Context::TYPE_STATEMENT, Context::TYPE_QUALIFIER ] ); $this->assertSame( [ Context::TYPE_QUALIFIER ], $parsed ); } public function testParseConstraintParameter_InvalidScope() { $constraintScopeId = $this->getDefaultConfig()->get( 'WBQualityConstraintsConstraintScopeId' ); $referencesId = new ItemId( $this->getDefaultConfig()->get( 'WBQualityConstraintsConstraintCheckedOnReferencesId' ) ); $snak = new PropertyValueSnak( new PropertyId( $constraintScopeId ), new EntityIdValue( $referencesId ) ); $this->assertThrowsConstraintParameterException( 'parseConstraintScopeParameter', [ [ $constraintScopeId => [ $this->getSnakSerializer()->serialize( $snak ), ] ], 'Q21502838', [ Context::TYPE_STATEMENT, Context::TYPE_QUALIFIER ] ], 'wbqc-violation-message-invalid-scope' ); } public function testParseConstraintScopeParameter_UnknownScope() { $constraintScopeId = $this->getDefaultConfig()->get( 'WBQualityConstraintsConstraintScopeId' ); $qualifiersId = new ItemId( $this->getDefaultConfig()->get( 'WBQualityConstraintsConstraintCheckedOnQualifiersId' ) ); $otherScopeId = new ItemId( 'Q1' ); $snak1 = new PropertyValueSnak( new PropertyId( $constraintScopeId ), new EntityIdValue( $qualifiersId ) ); $snak2 = new PropertyValueSnak( new PropertyId( $constraintScopeId ), new EntityIdValue( $otherScopeId ) ); $this->assertThrowsConstraintParameterException( 'parseConstraintScopeParameter', [ [ $constraintScopeId => [ $this->getSnakSerializer()->serialize( $snak1 ), $this->getSnakSerializer()->serialize( $snak2 ), ] ], 'Q21502838' ], 'wbqc-violation-message-parameter-oneof' ); } public function testParseUnitsParameter_NoUnitsAllowed() { $qualifierId = $this->getDefaultConfig()->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); $snak = new PropertyNoValueSnak( new PropertyId( $qualifierId ) ); $unitsParameter = $this->getConstraintParameterParser() ->parseUnitsParameter( [ $qualifierId => [ $this->getSnakSerializer()->serialize( $snak ), ] ], 'Q21514353' ); $this->assertEmpty( $unitsParameter->getUnitItemIds() ); $this->assertEmpty( $unitsParameter->getUnitQuantities() ); $this->assertTrue( $unitsParameter->getUnitlessAllowed() ); } public function testParseUnitsParameter_SomeUnitsAllowed() { $qualifierId = $this->getDefaultConfig()->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); $pid = new PropertyId( $qualifierId ); $unitId1 = new ItemId( 'Q11573' ); $unitId2 = new ItemId( 'Q37110097' ); $unit1 = 'http://wikibase.example/entity/Q11573'; $unit2 = 'http://wikibase.example/entity/Q37110097'; $snak1 = new PropertyValueSnak( $pid, new EntityIdValue( $unitId1 ) ); $snak2 = new PropertyValueSnak( $pid, new EntityIdValue( $unitId2 ) ); $unitsParameter = $this->getConstraintParameterParser() ->parseUnitsParameter( [ $qualifierId => [ $this->getSnakSerializer()->serialize( $snak1 ), $this->getSnakSerializer()->serialize( $snak2 ), ] ], 'Q21514353' ); $this->assertEquals( [ $unitId1, $unitId2 ], $unitsParameter->getUnitItemIds() ); $unitQuantities = $unitsParameter->getUnitQuantities(); $this->assertCount( 2, $unitQuantities ); $this->assertSame( $unit1, $unitQuantities[0]->getUnit() ); $this->assertSame( $unit2, $unitQuantities[1]->getUnit() ); $this->assertFalse( $unitsParameter->getUnitlessAllowed() ); } public function testParseUnitsParameter_SomeUnitsAndUnitlessAllowed() { $qualifierId = $this->getDefaultConfig()->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); $pid = new PropertyId( $qualifierId ); $unitId1 = new ItemId( 'Q11573' ); $unitId2 = new ItemId( 'Q37110097' ); $unit1 = 'http://wikibase.example/entity/Q11573'; $unit2 = 'http://wikibase.example/entity/Q37110097'; $snak1 = new PropertyValueSnak( $pid, new EntityIdValue( $unitId1 ) ); $snak2 = new PropertyValueSnak( $pid, new EntityIdValue( $unitId2 ) ); $snak3 = new PropertyNoValueSnak( $pid ); $unitsParameter = $this->getConstraintParameterParser() ->parseUnitsParameter( [ $qualifierId => [ $this->getSnakSerializer()->serialize( $snak1 ), $this->getSnakSerializer()->serialize( $snak2 ), $this->getSnakSerializer()->serialize( $snak3 ), ] ], 'Q21514353' ); $this->assertEquals( [ $unitId1, $unitId2 ], $unitsParameter->getUnitItemIds() ); $unitQuantities = $unitsParameter->getUnitQuantities(); $this->assertCount( 2, $unitQuantities ); $this->assertSame( $unit1, $unitQuantities[0]->getUnit() ); $this->assertSame( $unit2, $unitQuantities[1]->getUnit() ); $this->assertTrue( $unitsParameter->getUnitlessAllowed() ); } public function testParseEntityTypesParameter_Item() { $qualifierId = $this->getDefaultConfig()->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); $itemId = new ItemId( $this->getDefaultConfig()->get( 'WBQualityConstraintsWikibaseItemId' ) ); $snak = new PropertyValueSnak( new PropertyId( $qualifierId ), new EntityIdValue( $itemId ) ); $entityTypesParameter = $this->getConstraintParameterParser() ->parseEntityTypesParameter( [ $qualifierId => [ $this->getSnakSerializer()->serialize( $snak ), ] ], 'Q52004125' ); $this->assertSame( [ 'item' ], $entityTypesParameter->getEntityTypes() ); $this->assertEquals( [ $itemId ], $entityTypesParameter->getEntityTypeItemIds() ); } public function testParseEntityTypesParameter_Missing() { $this->assertThrowsConstraintParameterException( 'parseEntityTypesParameter', [ [], 'Q52004125', ], 'wbqc-violation-message-parameter-needed' ); } public function testParseEntityTypesParameter_UnknownItem() { $qualifierId = $this->getDefaultConfig()->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); $itemId = new ItemId( 'Q1' ); $snak = new PropertyValueSnak( new PropertyId( $qualifierId ), new EntityIdValue( $itemId ) ); $this->assertThrowsConstraintParameterException( 'parseEntityTypesParameter', [ [ $qualifierId => [ $this->getSnakSerializer()->serialize( $snak ), ] ], 'Q52004125', ], 'wbqc-violation-message-parameter-oneof' ); } public function testParseSeparatorsParameter_NoSeparators() { $separatorsParameter = $this->getConstraintParameterParser() ->parseSeparatorsParameter( [] ); $this->assertEmpty( $separatorsParameter ); } public function testParseSeparatorsParameter_ThreeSeparators() { $separatorId = $this->getDefaultConfig()->get( 'WBQualityConstraintsSeparatorId' ); $separatorsParameter = $this->getConstraintParameterParser() ->parseSeparatorsParameter( [ $separatorId => [ $this->serializePropertyId( 'P1' ), $this->serializePropertyId( 'P2' ), $this->serializePropertyId( 'P4' ), ] ] ); $expected = [ new PropertyId( 'P1' ), new PropertyId( 'P2' ), new PropertyId( 'P4' ), ]; $this->assertEquals( $expected, $separatorsParameter ); } /** * @dataProvider provideContextTypeCombinations */ public function testParsePropertyScopeParameter( array $contextTypes ) { $scope = $this->getConstraintParameterParser() ->parsePropertyScopeParameter( $this->propertyScopeParameter( $contextTypes ), 'Q1' ); $this->assertSame( $contextTypes, $scope ); } public function provideContextTypeCombinations() { return [ [ [ Context::TYPE_STATEMENT ] ], [ [ Context::TYPE_QUALIFIER ] ], [ [ Context::TYPE_REFERENCE ] ], [ [ Context::TYPE_QUALIFIER, Context::TYPE_REFERENCE ] ], ]; } public function testParsePropertyScopeParameter_missing() { $this->assertThrowsConstraintParameterException( 'parsePropertyScopeParameter', [ [], 'Q1' ], 'wbqc-violation-message-parameter-needed' ); } public function testParsePropertyScopeParameter_unknown() { $parameterId = $this->getDefaultConfig()->get( 'WBQualityConstraintsPropertyScopeId' ); $constraintParameters = [ $parameterId => [ $this->getSnakSerializer()->serialize( new PropertyValueSnak( new PropertyId( $parameterId ), new EntityIdValue( new ItemId( 'Q1' ) ) ) ) ], ]; $this->assertThrowsConstraintParameterException( 'parsePropertyScopeParameter', [ $constraintParameters, 'Q1' ], 'wbqc-violation-message-parameter-oneof' ); } }