Dominic.sauer has submitted this change and it was merged.

Change subject: Added ViolationContext for cross-check violation for violations 
special page.
......................................................................


Added ViolationContext for cross-check violation for violations special page.

Refactored top-level factory so that injection of Wikibase services is 
possible. Now guard clauses are used in ImportContext. Moved 
CheckResultTranslator to new Violations namespace.

Change-Id: I0fce5c48797de6ff01f840339bad07872642f855
---
M WikibaseQualityExternalValidation.php
M i18n/en.json
M i18n/qqq.json
M includes/ExternalValidationFactory.php
M includes/UpdateTable/ImportContext.php
R includes/Violations/CrossCheckResultToViolationTranslator.php
A includes/Violations/CrossCheckViolationContext.php
M maintenance/UpdateTable.php
M specials/SpecialCrossCheck.php
M tests/phpunit/ExternalValidationFactoryTest.php
M tests/phpunit/UpdateTable/ImportContextTest.php
R tests/phpunit/Violations/CrossCheckResultToViolationTranslatorTest.php
A tests/phpunit/Violations/CrossCheckViolationContextTest.php
A tests/phpunit/Violations/testdata/Q1.json
14 files changed, 769 insertions(+), 65 deletions(-)

Approvals:
  Dominic.sauer: Verified; Looks good to me, approved



diff --git a/WikibaseQualityExternalValidation.php 
b/WikibaseQualityExternalValidation.php
index d1637eb..7d0d500 100755
--- a/WikibaseQualityExternalValidation.php
+++ b/WikibaseQualityExternalValidation.php
@@ -33,7 +33,7 @@
 
        // Define modules
        $GLOBALS['wgResourceModules']['SpecialCrossCheckPage'] = array (
-               'styles' => 
'/modules/ext.WikibaseExternalValidation.SpecialCrossCheckPage.css',
+               'styles' => 
'modules/ext.WikibaseExternalValidation.SpecialCrossCheckPage.css',
                'localBasePath' => __DIR__,
                'remoteExtPath' => 'WikibaseQualityExternalValidation'
        );
@@ -46,4 +46,7 @@
        // Jobs
        $GLOBALS['wgDebugLogGroups']['wbq_evaluation'] = 
'/var/log/mediawiki/wbq_evaluation.log';
        $GLOBALS['wgJobClasses']['evaluateCrossCheckJob'] = 
'WikibaseQuality\ExternalValidation\EvaluateCrossCheckJob';
+
+    // Register violation context
+    $GLOBALS['wbqViolationContexts'][] = function() { return 
WikibaseQuality\ExternalValidation\ExternalValidationFactory::getDefaultInstance()->getViolationContext();
 };
 } );
\ No newline at end of file
diff --git a/i18n/en.json b/i18n/en.json
index 02468fe..e3399c0 100755
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -43,5 +43,11 @@
   "apihelp-wbqevcrosscheck-examples-2": "Run cross-check for all statements of 
item with ID Q76 and item with ID Q567.",
   "apihelp-wbqevcrosscheck-examples-3": "Run cross-check for all statements of 
item with ID Q76 and item with ID Q567, that uses property with ID P19.",
   "apihelp-wbqevcrosscheck-examples-4": "Run cross-check for all statements of 
item with ID Q76 and item with ID Q567, that uses property with ID P19 or P31.",
-  "apihelp-wbqevcrosscheck-examples-5": "Run cross-check for claim with GUID 
of Q42$D8404CDA-25E4-4334-AF13-A3290BCD9C0F."
+  "apihelp-wbqevcrosscheck-examples-5": "Run cross-check for claim with GUID 
of Q42$D8404CDA-25E4-4334-AF13-A3290BCD9C0F.",
+
+  "wbqev-violations-group": "External Validation",
+  "wbqev-violation-header-external-source": "External source:",
+  "wbqev-violation-header-local-value": "Wikidata value:",
+  "wbqev-violation-header-external-values": "External values:",
+  "wbqev-violation-message": "Cross-Check with $1 has pointed out a violation. 
Please click on icon for further information."
 }
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 2d35052..c48eb84 100755
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -26,6 +26,7 @@
        "wbqev-crosscheck-status-mismatch": "Status for claims that have a 
mismatch with any external data.",
        "wbqev-crosscheck-status-references-missing": "Status for claims for 
which references are missing.",
        "wbqev-crosscheck-status-references-stated": "Status for claims for 
which references are stated.",
+
        "wbqev-externaldbs": "{{doc-special|ExternalDbs}}",
        "wbqev-externaldbs-instructions": "Purpose of SpecialPage:ExternalDbs",
        "wbqev-externaldbs-overview-headline": "Headline that appears above the 
overview of external databases.\n{{Identical|Database}}",
@@ -37,6 +38,7 @@
        "wbqev-externaldbs-size": "Name for size from the external 
source.\n{{Identical|Size}}",
        "wbqev-externaldbs-license": "Name for license from the external 
source.\n{{Identical|License}}",
        "wbqev-externaldbs-no-databases": "Message that appears when no 
external databases exist.",
+
        "apihelp-wbqevcrosscheck-description": 
"{{doc-apihelp-description|wblinktitles}}",
        "apihelp-wbqevcrosscheck-param-entities": 
"{{doc-apihelp-param|wbqevcrosscheck|entities}}",
        "apihelp-wbqevcrosscheck-param-properties": 
"{{doc-apihelp-param|wbqevcrosscheck|properties}}",
@@ -45,5 +47,11 @@
        "apihelp-wbqevcrosscheck-examples-2": 
"{{doc-apihelp-example|wbqevcrosscheck}}",
        "apihelp-wbqevcrosscheck-examples-3": 
"{{doc-apihelp-example|wbqevcrosscheck}}",
        "apihelp-wbqevcrosscheck-examples-4": 
"{{doc-apihelp-example|wbqevcrosscheck}}",
-       "apihelp-wbqevcrosscheck-examples-5": 
"{{doc-apihelp-example|wbqevcrosscheck}}"
+       "apihelp-wbqevcrosscheck-examples-5": 
"{{doc-apihelp-example|wbqevcrosscheck}}",
+
+    "wbqev-violations-group": "Name of the group of external validation 
violations. Is shown in special page that lists all the violations.",
+    "wbqev-violation-header-external-source": "Header for section in 
violations special page that displays the name of the external source.",
+    "wbqev-violation-header-local-value": "Header for section in violations 
special page that displays the data values stored in external databases.",
+    "wbqev-violation-header-external-values": "Header for section in 
violations special page that displays the name of the external source.",
+    "wbqev-violation-message": "Message that is shown for violated claims on 
item page. First parameter is name of the data source."
 }
diff --git a/includes/ExternalValidationFactory.php 
b/includes/ExternalValidationFactory.php
index e2a8335..bdad970 100755
--- a/includes/ExternalValidationFactory.php
+++ b/includes/ExternalValidationFactory.php
@@ -2,7 +2,22 @@
 
 namespace WikibaseQuality\ExternalValidation;
 
+use RequestContext;
+use DataValues\Deserializers\DataValueDeserializer;
 use DataValues\Serializers\DataValueSerializer;
+use ValueFormatters\FormatterOptions;
+use ValueFormatters\ValueFormatter;
+use Wikibase\DataModel\Claim\ClaimGuidParser;
+use Wikibase\DataModel\Entity\EntityIdParser;
+use Wikibase\Lib\EntityIdHtmlLinkFormatter;
+use Wikibase\Lib\LanguageNameLookup;
+use Wikibase\Lib\OutputFormatValueFormatterFactory;
+use Wikibase\Lib\SnakFormatter;
+use Wikibase\Lib\Store\EntityLookup;
+use Wikibase\Lib\Store\EntityRevisionLookup;
+use Wikibase\Lib\Store\EntityTitleLookup;
+use Wikibase\Lib\Store\LanguageLabelDescriptionLookup;
+use Wikibase\Lib\Store\TermLookup;
 use Wikibase\Repo\WikibaseRepo;
 use 
WikibaseQuality\ExternalValidation\CrossCheck\Comparer\DataValueComparerFactory;
 use WikibaseQuality\ExternalValidation\CrossCheck\CrossChecker;
@@ -10,7 +25,9 @@
 use WikibaseQuality\ExternalValidation\CrossCheck\ReferenceHandler;
 use 
WikibaseQuality\ExternalValidation\DumpMetaInformation\DumpMetaInformationRepo;
 use WikibaseQuality\ExternalValidation\Serializer\SerializerFactory;
-use 
WikibaseQuality\ExternalValidation\CrossCheck\Result\CrossCheckResultToViolationTranslator;
+use WikibaseQuality\Violations\ViolationContext;
+use 
WikibaseQuality\ExternalValidation\Violations\CrossCheckResultToViolationTranslator;
+use WikibaseQuality\ExternalValidation\Violations\CrossCheckViolationContext;
 
 
 /**
@@ -21,6 +38,41 @@
  * @license GNU GPL v2+
  */
 class ExternalValidationFactory {
+
+    /**
+     * @var EntityLookup
+     */
+    private $entityLookup;
+
+    /**
+     * @var EntityRevisionLookup
+     */
+    private $entityRevisionLookup;
+
+    /**
+     * @var TermLookup
+     */
+    private $termLookup;
+
+    /**
+     * @var EntityTitleLookup
+     */
+    private $entityTitleLookup;
+
+    /**
+     * @var EntityIdParser
+     */
+    private $entityIdParser;
+
+    /**
+     * @var ClaimGuidParser
+     */
+    private $claimGuidParser;
+
+    /**
+     * @var ValueFormatter
+     */
+    private $valueFormatterFactory;
 
     /**
      * @var CrossChecker
@@ -58,6 +110,28 @@
     private $crossCheckResultToViolationTranslator;
 
     /**
+     * @param EntityLookup $entityLookup
+     * @param EntityRevisionLookup $entityRevisionLookup
+     * @param TermLookup $termLookup
+     * @param EntityTitleLookup $entityTitleLookup
+     * @param EntityIdParser $entityIdParser
+     * @param ClaimGuidParser $claimGuidParser
+     * @param OutputFormatValueFormatterFactory $valueFormatterFactory
+     */
+    public function __construct( EntityLookup $entityLookup, 
EntityRevisionLookup $entityRevisionLookup,
+                                 TermLookup $termLookup, EntityTitleLookup 
$entityTitleLookup,
+                                 EntityIdParser $entityIdParser, 
ClaimGuidParser $claimGuidParser,
+                                 OutputFormatValueFormatterFactory 
$valueFormatterFactory ) {
+        $this->entityLookup = $entityLookup;
+        $this->entityRevisionLookup = $entityRevisionLookup;
+        $this->termLookup = $termLookup;
+        $this->entityTitleLookup = $entityTitleLookup;
+        $this->entityIdParser = $entityIdParser;
+        $this->claimGuidParser = $claimGuidParser;
+        $this->valueFormatterFactory = $valueFormatterFactory;
+    }
+
+    /**
      * Returns the default instance.
      * IMPORTANT: Use only when it is not feasible to inject an instance 
properly.
      *
@@ -67,7 +141,16 @@
         static $instance = null;
 
         if ( $instance === null ) {
-            $instance = new self();
+            $repo = WikibaseRepo::getDefaultInstance()->getDefaultInstance();
+            $instance = new self(
+                $repo->getEntityLookup(),
+                $repo->getEntityRevisionLookup(),
+                $repo->getTermLookup(),
+                $repo->getEntityTitleLookup(),
+                $repo->getEntityIdParser(),
+                $repo->getClaimGuidParser(),
+                $repo->getValueFormatterFactory()
+            );
         }
 
         return $instance;
@@ -95,11 +178,9 @@
      */
     public function getCrossCheckInteractor() {
         if ( $this->crossCheckInteractor === null ) {
-            $entityLookup = 
WikibaseRepo::getDefaultInstance()->getEntityLookup();
-            $claimGuidParser = 
WikibaseRepo::getDefaultInstance()->getClaimGuidParser();
             $this->crossCheckInteractor = new CrossCheckInteractor(
-                $entityLookup,
-                $claimGuidParser,
+                $this->entityLookup,
+                $this->claimGuidParser,
                 $this->getCrossChecker() );
         }
 
@@ -111,8 +192,7 @@
      */
     public function getDataValueComparerFactory() {
         if ( $this->dataValueComparerFactory === null ) {
-            $entityLookup = 
WikibaseRepo::getDefaultInstance()->getEntityLookup();
-            $this->dataValueComparerFactory = new DataValueComparerFactory( 
$entityLookup );
+            $this->dataValueComparerFactory = new DataValueComparerFactory( 
$this->entityLookup );
         }
 
         return $this->dataValueComparerFactory;
@@ -126,7 +206,7 @@
             $this->dumpMetaInformationRepo = new DumpMetaInformationRepo(
                 DUMP_META_TABLE,
                 DUMP_IDENTIFIER_PROPERTIES_TABLE,
-                WikibaseRepo::getDefaultInstance()->getEntityIdParser()
+                $this->entityIdParser
             );
         }
 
@@ -164,10 +244,48 @@
      * @return CrossCheckResultToViolationTranslator
      */
     public function getCrossCheckResultToViolationTranslator() {
-        if( $this->crossCheckResultToViolationTranslator === null ) {
-            $this->crossCheckResultToViolationTranslator = new 
CrossCheckResultToViolationTranslator( 
WikibaseRepo::getDefaultInstance()->getEntityRevisionLookup() );
+        if ( $this->crossCheckResultToViolationTranslator === null ) {
+            $this->crossCheckResultToViolationTranslator = new 
CrossCheckResultToViolationTranslator( $this->entityRevisionLookup );
         }
 
         return $this->crossCheckResultToViolationTranslator;
     }
+
+    /**
+     * @return ViolationContext
+     */
+    public function getViolationContext() {
+        $languageCode = RequestContext::getMain()->getLanguage()->getCode();
+
+        $dataValueDeserializer = new DataValueDeserializer(
+            array (
+                'boolean' => 'DataValues\BooleanValue',
+                'number' => 'DataValues\NumberValue',
+                'string' => 'DataValues\StringValue',
+                'unknown' => 'DataValues\UnknownValue',
+                'globecoordinate' => 'DataValues\GlobeCoordinateValue',
+                'monolingualtext' => 'DataValues\MonolingualTextValue',
+                'multilingualtext' => 'DataValues\MultilingualTextValue',
+                'quantity' => 'DataValues\QuantityValue',
+                'time' => 'DataValues\TimeValue',
+                'wikibase-entityid' => 
'Wikibase\DataModel\Entity\EntityIdValue',
+            )
+        );
+        $labelLookup = new LanguageLabelDescriptionLookup( $this->termLookup, 
$languageCode );
+        $entityIdLinkFormatter = new EntityIdHtmlLinkFormatter(
+            $labelLookup,
+            $this->entityTitleLookup,
+            new LanguageNameLookup()
+        );
+        $formatterOptions = new FormatterOptions();
+        $formatterOptions->setOption( SnakFormatter::OPT_LANG, $languageCode );
+        $dataValueFormatter = $this->valueFormatterFactory->getValueFormatter( 
SnakFormatter::FORMAT_HTML, $formatterOptions );
+
+        return new CrossCheckViolationContext(
+            $dataValueDeserializer,
+            $entityIdLinkFormatter,
+            $dataValueFormatter,
+            $this->getDumpMetaInformationRepo()
+        );
+    }
 }
\ No newline at end of file
diff --git a/includes/UpdateTable/ImportContext.php 
b/includes/UpdateTable/ImportContext.php
index f67fc85..22c5775 100755
--- a/includes/UpdateTable/ImportContext.php
+++ b/includes/UpdateTable/ImportContext.php
@@ -62,11 +62,10 @@
         * @param int $batchSize
         */
        public function setBatchSize( $batchSize ) {
-               if ( is_int( $batchSize ) ) {
-                       $this->batchSize = $batchSize;
-               } else {
-                       throw new InvalidArgumentException( '$batchSize must be 
of type int.' );
+               if ( !is_int( $batchSize ) ) {
+            throw new InvalidArgumentException( '$batchSize must be of type 
int.' );
                }
+        $this->batchSize = $batchSize;
        }
 
        /**
@@ -80,21 +79,19 @@
         * @param boolean $quiet
         */
        public function setQuiet( $quiet ) {
-               if ( is_bool( $quiet ) ) {
-                       $this->quiet = $quiet;
-               } else {
-                       throw new InvalidArgumentException( '$quiet must be of 
type bool.' );
+               if ( !is_bool( $quiet ) ) {
+            throw new InvalidArgumentException( '$quiet must be of type bool.' 
);
                }
+        $this->quiet = $quiet;
        }
 
        /**
         * @param string $tarFilePath
         */
        public function setTarFilePath( $tarFilePath ) {
-               if ( is_string( $tarFilePath ) ) {
-                       $this->tarFilePath = $tarFilePath;
-               } else {
-                       throw new InvalidArgumentException( '$entitiesFilePath 
must be of type string.' );
+               if ( !is_string( $tarFilePath ) ) {
+            throw new InvalidArgumentException( '$entitiesFilePath must be of 
type string.' );
                }
+        $this->tarFilePath = $tarFilePath;
        }
 }
\ No newline at end of file
diff --git 
a/includes/CrossCheck/Result/CrossCheckResultToViolationTranslator.php 
b/includes/Violations/CrossCheckResultToViolationTranslator.php
similarity index 77%
rename from includes/CrossCheck/Result/CrossCheckResultToViolationTranslator.php
rename to includes/Violations/CrossCheckResultToViolationTranslator.php
index 926985d..d427b13 100755
--- a/includes/CrossCheck/Result/CrossCheckResultToViolationTranslator.php
+++ b/includes/Violations/CrossCheckResultToViolationTranslator.php
@@ -1,10 +1,13 @@
 <?php
 
-namespace WikibaseQuality\ExternalValidation\CrossCheck\Result;
+namespace WikibaseQuality\ExternalValidation\Violations;
 
 use DataValues\Serializers\DataValueSerializer;
 use Wikibase\DataModel\Entity\Entity;
 use Wikibase\Lib\Store\EntityRevisionLookup;
+use WikibaseQuality\ExternalValidation\CrossCheck\Result\CompareResult;
+use WikibaseQuality\ExternalValidation\CrossCheck\Result\CrossCheckResult;
+use WikibaseQuality\ExternalValidation\CrossCheck\Result\CrossCheckResultList;
 use WikibaseQuality\Violations\Violation;
 
 
@@ -41,9 +44,10 @@
             //TODO: Use real ClaimGuid and TypeEntityId
             $constraintTypeEntityId = 
$crossCheckResult->getDumpMetaInformation()->getSourceItemId();
             $constraintId = md5( 
$crossCheckResult->getDumpMetaInformation()->getImportDate()->format( 'YmdHis' 
) . $constraintTypeEntityId );
+            $constraintId = CrossCheckViolationContext::CONTEXT_ID . 
Violation::CONSTRAINT_ID_DELIMITER . $constraintId;
             $revisionId = $this->entityRevisionLookup->getLatestRevisionId( 
$entityId );
-            $status = CompareResult::STATUS_MISMATCH;
-            $additionalInformation = $this->setAdditionalInformationJson( 
$crossCheckResult );
+            $status = Violation::STATUS_VIOLATION;
+            $additionalInformation = $this->buildAdditionalInformation( 
$crossCheckResult );
 
             $violationArray[] = new Violation( $entityId, $propertyId, 
$claimGuid, $constraintId, $constraintTypeEntityId, $revisionId, $status, 
$additionalInformation);
         }
@@ -51,7 +55,7 @@
         return $violationArray;
     }
 
-    private function setAdditionalInformationJson( CrossCheckResult 
$crossCheckResult  ){
+    private function buildAdditionalInformation( CrossCheckResult 
$crossCheckResult  ){
 
         $serializer = new DataValueSerializer();
         $externalValues = 
$crossCheckResult->getCompareResult()->getExternalValues();
@@ -64,6 +68,6 @@
             'external_values' => $externalValuesSerialized
         );
 
-        return json_encode( $additionalInformation );
+        return $additionalInformation;
     }
 }
diff --git a/includes/Violations/CrossCheckViolationContext.php 
b/includes/Violations/CrossCheckViolationContext.php
new file mode 100644
index 0000000..783f8df
--- /dev/null
+++ b/includes/Violations/CrossCheckViolationContext.php
@@ -0,0 +1,200 @@
+<?php
+
+namespace WikibaseQuality\ExternalValidation\Violations;
+
+
+use Deserializers\Deserializer;
+use Doctrine\Instantiator\Exception\InvalidArgumentException;
+use Html;
+use ValueFormatters\ValueFormatter;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\Lib\EntityIdFormatter;
+use WikibaseQuality\ExternalValidation\DumpMetaInformation\DumpMetaInformation;
+use 
WikibaseQuality\ExternalValidation\DumpMetaInformation\DumpMetaInformationRepo;
+use WikibaseQuality\Violations\Violation;
+use WikibaseQuality\Violations\ViolationContext;
+
+
+class CrossCheckViolationContext implements ViolationContext {
+
+    const CONTEXT_ID = 'wbqev';
+
+    /**
+     * @var Deserializer
+     */
+    private $dataValueDeserializer;
+
+    /**
+     * @var EntityIdFormatter
+     */
+    private $entityIdFormatter;
+
+    /**
+     * @var ValueFormatter
+     */
+    private $valueFormatter;
+
+    /**
+     * @var DumpMetaInformationRepo
+     */
+    private $dumpMetaInformationRepo;
+
+    /**
+     * @param Deserializer $dataValueDeserializer
+     * @param EntityIdFormatter $entityIdFormatter
+     * @param ValueFormatter $valueFormatter
+     * @param DumpMetaInformationRepo $dumpMetaInformationRepo
+     */
+    public function __construct( Deserializer $dataValueDeserializer, 
EntityIdFormatter $entityIdFormatter, ValueFormatter $valueFormatter, 
DumpMetaInformationRepo $dumpMetaInformationRepo ) {
+        $this->dataValueDeserializer = $dataValueDeserializer;
+        $this->entityIdFormatter = $entityIdFormatter;
+        $this->valueFormatter = $valueFormatter;
+        $this->dumpMetaInformationRepo = $dumpMetaInformationRepo;
+    }
+
+    /**
+     * @see ViolationContext::getId
+     * @codeCoverageIgnore
+     *
+     * @return string
+     */
+    public function getId() {
+        return $this::CONTEXT_ID;
+    }
+
+    /**
+     * @see ViolationContext::getName
+     * @codeCoverageIgnore
+     *
+     * @return string
+     */
+    public function getName() {
+        return 'wbqev-violations-group';
+    }
+
+    /**
+     * @see ViolationContext::getTypes
+     *
+     * @return array
+     */
+    public function getTypes() {
+        $types = array();
+        $dumpMetaInformation = $this->dumpMetaInformationRepo->getALl();
+        foreach ( $dumpMetaInformation as $dump ) {
+            $type = $dump->getSourceItemId()->getSerialization(); //TODO: 
return EntityId instead of serialization
+            if( !in_array( $type, $types ) ) {
+                $types[] = $type;
+            }
+        }
+
+        return $types;
+    }
+
+    /**
+     * @see ViolationContext::isContextFor
+     *
+     * @param Violation $violation
+     * @return bool
+     */
+    public function isContextFor( Violation $violation ) {
+        $prefix = explode( Violation::CONSTRAINT_ID_DELIMITER, 
$violation->getConstraintId() )[0];
+
+        return $prefix === $this->getId();
+    }
+
+    /**
+     * @see ViolationContext::formatAdditionalInformation
+     *
+     * @param Violation $violation
+     * @return string
+     */
+    public function formatAdditionalInformation( Violation $violation ) {
+        if ( !$this->isContextFor( $violation ) ) {
+            throw new InvalidArgumentException( 'Given violation is not part 
of current context.' );
+        }
+
+        $additionalInfo = $violation->getAdditionalInfo();
+        $output = $this->formatDataSource( $additionalInfo );
+        $output .= $this->formatExternalValues( $additionalInfo );
+
+        return $output;
+    }
+
+    /**
+     * @param array $additionalInformation
+     * @return string
+     */
+    private function formatDataSource( array $additionalInformation ) {
+        if ( array_key_exists( 'dump_id', $additionalInformation ) ) {
+            $dumpId = $additionalInformation['dump_id'];
+            $dumpMetaInformation = $this->dumpMetaInformationRepo->getWithId( 
$dumpId );
+            $dataSourceEntityId = $dumpMetaInformation->getSourceItemId();
+            $dataSource = $this->entityIdFormatter->formatEntityId( 
$dataSourceEntityId );
+
+            return $this->buildSection(
+                'wbqev-violation-header-external-source',
+                $dataSource
+            );
+        }
+    }
+
+    /**
+     * @param array $additionalInformation
+     * @return string
+     */
+    private function formatExternalValues( array $additionalInformation ) {
+        if ( array_key_exists( 'external_values', $additionalInformation ) ) {
+            $externalValues = array_map(
+                function ( $serializedDataValue ) {
+                    $dataValue = $this->dataValueDeserializer->deserialize( 
$serializedDataValue );
+                    return $this->valueFormatter->format( $dataValue );
+                },
+                $additionalInformation['external_values']
+            );
+
+            return $this->buildSection(
+                'wbqev-violation-header-external-values',
+                implode( Html::element( 'br' ), $externalValues )
+            );
+        }
+    }
+
+    /**
+     * Build section for additional information output.
+     *
+     * @param string $headlineMessage
+     * @param string $content
+     * @return string
+     */
+    private function buildSection( $headlineMessage, $content ) {
+        return
+            Html::openElement( 'p' )
+            . Html::element(
+                'span',
+                array(
+                    'class' => 'wbq-violations-additional-information-header'
+                ),
+                wfMessage( $headlineMessage )->text()
+            )
+            . Html::element( 'br' )
+            . $content
+            . Html::closeElement( 'p' );
+    }
+
+    /**
+     * @param Violation $violation
+     * @return string
+     */
+    public function getMessage( Violation $violation ) {
+        if ( !$this->isContextFor( $violation ) ) {
+            throw new InvalidArgumentException( 'Formatting of given violation 
is not supported by current formatter.' );
+        }
+
+        $dataSourceEntityId = new ItemId( 
$violation->getConstraintTypeEntityId() );
+        $dataSource = $this->entityIdFormatter->formatEntityId( 
$dataSourceEntityId );
+
+        return wfMessage( 'wbqev-violation-message' )
+            ->params( $dataSource )
+            ->text();
+    }
+}
\ No newline at end of file
diff --git a/maintenance/UpdateTable.php b/maintenance/UpdateTable.php
index 5b01b55..8d263c9 100755
--- a/maintenance/UpdateTable.php
+++ b/maintenance/UpdateTable.php
@@ -47,6 +47,6 @@
 }
 
 // @codeCoverageIgnoreStart
-$maintClass = 'WikidataQuality\ExternalValidation\Maintenance\UpdateTable';
+$maintClass = 'WikibaseQuality\ExternalValidation\Maintenance\UpdateTable';
 require_once RUN_MAINTENANCE_IF_MAIN;
 // @codeCoverageIgnoreEnd
diff --git a/specials/SpecialCrossCheck.php b/specials/SpecialCrossCheck.php
index 7c26f07..dfdf1d8 100755
--- a/specials/SpecialCrossCheck.php
+++ b/specials/SpecialCrossCheck.php
@@ -5,6 +5,7 @@
 use SpecialPage;
 use ValueFormatters\FormatterOptions;
 use ValueFormatters\ValueFormatter;
+use Wikibase\DataModel\Entity\Entity;
 use Wikibase\DataModel\Entity\EntityIdParser;
 use Wikibase\Lib\EntityIdLabelFormatter;
 use Wikibase\Lib\EntityIdHtmlLinkFormatter;
@@ -32,7 +33,9 @@
 use WikibaseQuality\ExternalValidation\ExternalValidationFactory;
 use WikibaseQuality\Html\HtmlTable;
 use WikibaseQuality\Html\HtmlTableHeader;
+use WikibaseQuality\Violations\ViolationRepo;
 use WikibaseQuality\Violations\ViolationStore;
+use WikibaseQuality\WikibaseQualityFactory;
 
 
 class SpecialCrossCheck extends SpecialPage {
@@ -78,6 +81,11 @@
     private $crossCheckResultToViolationTranslator;
 
     /**
+     * @var ViolationRepo
+     */
+    private $violationRepo;
+
+    /**
      * @param string $name
      * @param string $restriction
      * @param bool $listed
@@ -109,6 +117,7 @@
 
         $this->crossCheckInteractor = 
ExternalValidationFactory::getDefaultInstance()->getCrossCheckInteractor();
         $this->crossCheckResultToViolationTranslator = 
ExternalValidationFactory::getDefaultInstance()->getCrossCheckResultToViolationTranslator();
+        $this->violationRepo = 
WikibaseQualityFactory::getDefaultInstance()->getViolationRepo();
     }
 
 
@@ -453,8 +462,9 @@
     */
     protected function saveResultsInViolationsTable( $entity, $results ) {
         $violations = 
$this->crossCheckResultToViolationTranslator->translateToViolation( $entity, 
$results );
-        $violationStore = new ViolationStore();
-        $violationStore->insertViolations( $violations );
+        foreach( $violations as $violation ) {
+            $this->violationRepo->save( $violation );
+        }
     }
 
     private function doEvaluation( $entity, $results ) {
@@ -465,6 +475,6 @@
         $jobs[] = EvaluateCrossCheckJob::newInsertNow( 
$entity->getId()->getSerialization(), $checkTimeStamp, $results );
         $jobs[] = EvaluateCrossCheckJob::newInsertDeferred( 
$entity->getId()->getSerialization(), $checkTimeStamp, 10*60 );
         $jobs[] = EvaluateCrossCheckJob::newInsertDeferred( 
$entity->getId()->getSerialization(), $checkTimeStamp, 60*60 );
-        JobQueueGroup::singleton()->push( $jobs );
+        //JobQueueGroup::singleton()->push( $jobs );
     }
 }
diff --git a/tests/phpunit/ExternalValidationFactoryTest.php 
b/tests/phpunit/ExternalValidationFactoryTest.php
index 5785462..1e5b2ce 100755
--- a/tests/phpunit/ExternalValidationFactoryTest.php
+++ b/tests/phpunit/ExternalValidationFactoryTest.php
@@ -70,12 +70,41 @@
         $crossCheckResultToViolationTranslator = 
$this->getFactory()->getCrossCheckResultToViolationTranslator();
 
         $this->assertInstanceOf(
-            
'WikibaseQuality\ExternalValidation\CrossCheck\Result\CrossCheckResultToViolationTranslator',
+            
'WikibaseQuality\ExternalValidation\Violations\CrossCheckResultToViolationTranslator',
             $crossCheckResultToViolationTranslator
         );
     }
 
+    public function testGetViolationContext() {
+        $violationContext = $this->getFactory()->getViolationContext();
+
+        $this->assertInstanceOf(
+            'WikibaseQuality\Violations\ViolationContext',
+            $violationContext
+        );
+    }
+
     private function getFactory() {
-        return new ExternalValidationFactory();
+        return new ExternalValidationFactory(
+            $this->getMockForAbstractClass( 'Wikibase\Lib\Store\EntityLookup' 
),
+            $this->getMockForAbstractClass( 
'Wikibase\Lib\Store\EntityRevisionLookup' ),
+            $this->getMockForAbstractClass( 'Wikibase\Lib\Store\TermLookup' ),
+            $this->getMockForAbstractClass( 
'Wikibase\Lib\Store\EntityTitleLookup' ),
+            $this->getMockForAbstractClass( 
'Wikibase\DataModel\Entity\EntityIdParser' ),
+            $this->getMockBuilder( 'Wikibase\DataModel\Claim\ClaimGuidParser' 
)->disableOriginalConstructor()->getMock(),
+            $this->getValueFormatterFactoryMock()
+        );
+    }
+
+    private function getValueFormatterFactoryMock() {
+        $mock = $this->getMockBuilder( 
'Wikibase\Lib\OutputFormatValueFormatterFactory' )
+            ->setMethods( array( 'getValueFormatter' ) )
+            ->disableOriginalConstructor()
+            ->getMock();
+        $mock->expects( $this->any() )
+            ->method( 'getValueFormatter' )
+            ->willReturn( $this->getMockForAbstractClass( 
'ValueFormatters\ValueFormatter' ) );
+
+        return $mock;
     }
 }
diff --git a/tests/phpunit/UpdateTable/ImportContextTest.php 
b/tests/phpunit/UpdateTable/ImportContextTest.php
index ba2eb8f..67227fd 100755
--- a/tests/phpunit/UpdateTable/ImportContextTest.php
+++ b/tests/phpunit/UpdateTable/ImportContextTest.php
@@ -20,34 +20,28 @@
         *
         * @dataProvider provideInvalidArguments()
         */
-       public function testConstructWithInvalidArguments( $loadBalancer, 
$batchSize, $quiet, $tarFilePath ) {
+       public function testConstructWithInvalidArguments( $batchSize, $quiet, 
$tarFilePath ) {
                $this->setExpectedException( 'InvalidArgumentException' );
-               new ImportContext( $loadBalancer, $batchSize, $quiet, 
$tarFilePath );
+               new ImportContext( $batchSize, $quiet, $tarFilePath );
        }
 
        public function provideInvalidArguments() {
-               $loadBalancer = $this->getMockBuilder( 'LoadBalancer' )
-                       ->disableOriginalConstructor()
-                       ->getMock();
                return array(
                        array(
-                               $loadBalancer,
                                'invalidBatchSize',
                                true,
                                'entitiesFilePath'
                        ),
                        array(
-                               $loadBalancer,
                                1234,
                                'invalidQuiet',
                                'entitiesFilePath'
                        ),
-                       array(
-                               $loadBalancer,
-                               1234,
-                               true,
-                               1234
-                       )
+            array(
+                1234,
+                true,
+                1234
+            )
                );
        }
 }
diff --git 
a/tests/phpunit/CrossCheck/Result/CrossCheckResultToViolationTranslatorTest.php 
b/tests/phpunit/Violations/CrossCheckResultToViolationTranslatorTest.php
similarity index 82%
rename from 
tests/phpunit/CrossCheck/Result/CrossCheckResultToViolationTranslatorTest.php
rename to tests/phpunit/Violations/CrossCheckResultToViolationTranslatorTest.php
index 584a6e7..e87c8bc 100755
--- 
a/tests/phpunit/CrossCheck/Result/CrossCheckResultToViolationTranslatorTest.php
+++ b/tests/phpunit/Violations/CrossCheckResultToViolationTranslatorTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace WikibaseQuality\ExternalValidation\Test\CrossCheck\Result;
+namespace WikibaseQuality\ExternalValidation\Test\Violations;
 
 use DataValues\StringValue;
 use Wikibase\DataModel\Entity\Entity;
@@ -9,27 +9,27 @@
 use Wikibase\DataModel\Entity\PropertyId;
 use Wikibase\DataModel\Entity\ItemId;
 use DateTime;
+use Wikibase\Repo\WikibaseRepo;
 use WikibaseQuality\ExternalValidation\CrossCheck\Result\CompareResult;
-use WikibaseQuality\ExternalValidation\CrossCheck\Result\ReferenceResult;
 use WikibaseQuality\ExternalValidation\CrossCheck\Result\CrossCheckResult;
 use WikibaseQuality\ExternalValidation\CrossCheck\Result\CrossCheckResultList;
-use 
WikibaseQuality\ExternalValidation\CrossCheck\Result\CrossCheckResultToViolationTranslator;
+use WikibaseQuality\ExternalValidation\CrossCheck\Result\ReferenceResult;
 use WikibaseQuality\ExternalValidation\DumpMetaInformation\DumpMetaInformation;
-use Wikibase\Repo\WikibaseRepo;
+use 
WikibaseQuality\ExternalValidation\Violations\CrossCheckResultToViolationTranslator;
 
 
 /**
- * @covers  
WikibaseQuality\ExternalValidation\CrossCheck\Result\CrossCheckResultToViolationTranslator
+ * @covers 
WikibaseQuality\ExternalValidation\Violations\CrossCheckResultToViolationTranslator
  *
- * @group   WikibaseQualityExternalValidation
- * @group   Database
- * @group   medium
+ * @group WikibaseQualityExternalValidation
+ * @group Database
+ * @group medium
  *
- * @uses    
WikibaseQuality\ExternalValidation\DumpMetaInformation\DumpMetaInformation
- * @uses    WikibaseQuality\ExternalValidation\CrossCheck\Result\CompareResult
- * @uses    
WikibaseQuality\ExternalValidation\CrossCheck\Result\ReferenceResult
- * @uses    
WikibaseQuality\ExternalValidation\CrossCheck\Result\CrossCheckResult
- * @uses    
WikibaseQuality\ExternalValidation\CrossCheck\Result\CrossCheckResultList
+ * @uses   
WikibaseQuality\ExternalValidation\DumpMetaInformation\DumpMetaInformation
+ * @uses   WikibaseQuality\ExternalValidation\CrossCheck\Result\CompareResult
+ * @uses   WikibaseQuality\ExternalValidation\CrossCheck\Result\ReferenceResult
+ * @uses   
WikibaseQuality\ExternalValidation\CrossCheck\Result\CrossCheckResult
+ * @uses   
WikibaseQuality\ExternalValidation\CrossCheck\Result\CrossCheckResultList
  *
  * @author BP2014N1
  * @license GNU GPL v2+
@@ -138,9 +138,9 @@
         $this->assertEquals( self::$idMap[ 'Q1' ], $violation->getEntityId() );
         $this->assertEquals( 'P1', 
$violation->getPropertyId()->getSerialization() );
         $this->assertEquals( $this->claimGuid, $violation->getClaimGuid() );
-        $this->assertEquals( md5( 
$this->dumpMetaInformation->getImportDate()->format( 'YmdHis' ) . 
$this->dumpMetaInformation->getSourceItemId() ), $violation->getConstraintId() 
);
+        $this->assertEquals( 'wbqev|93346d9b0ea699c5387d1d2bf56322ff', 
$violation->getConstraintId() );
         $this->assertEquals( $this->dumpMetaInformation->getSourceItemId(), 
$violation->getConstraintTypeEntityId() );
-        $this->assertEquals( 
'{"dump_id":"foo","external_values":[{"value":"test","type":"string"}]}', 
$violation->getAdditionalInfo() );
+        $this->assertEquals( array( 'external_values' => array( array( 'value' 
=> 'test', 'type' => 'string' ) ), 'dump_id' => 'foo' ), 
$violation->getAdditionalInfo() );
         $this->assertEquals( 42, $violation->getRevisionId() );
 
     }
diff --git a/tests/phpunit/Violations/CrossCheckViolationContextTest.php 
b/tests/phpunit/Violations/CrossCheckViolationContextTest.php
new file mode 100644
index 0000000..146cae0
--- /dev/null
+++ b/tests/phpunit/Violations/CrossCheckViolationContextTest.php
@@ -0,0 +1,313 @@
+<?php
+
+namespace WikibaseQuality\ExternalValidation\Test\Violations\Result;
+
+use DataValues\StringValue;
+use Language;
+use DataValues\DataValue;
+use Wikibase\DataModel\Entity\EntityId;
+use Wikibase\DataModel\Entity\ItemId;
+use WikibaseQuality\ExternalValidation\Violations\CrossCheckViolationContext;
+use WikibaseQuality\Tests\Helper\JsonFileEntityLookup;
+
+
+/**
+ * @covers 
WikibaseQuality\ExternalValidation\Violations\CrossCheckViolationContext
+ *
+ * @group WikibaseQualityExternalValidation
+ *
+ * @author BP2014N1
+ * @license GNU GPL v2+
+ */
+class CrossCheckViolationContextTest extends \MediaWikiTestCase {
+
+    /**
+     * @var CrossCheckViolationContext
+     */
+    private $violationContext;
+
+    public function setUp() {
+        parent::setUp();
+
+        $dumpMetaInformation = array(
+            $this->getDumpMetaInformationMock( new ItemId( 'Q21' ) ),
+            $this->getDumpMetaInformationMock( new ItemId( 'Q42' ) ),
+            $this->getDumpMetaInformationMock( new ItemId( 'Q84' ) )
+        );
+
+        $this->violationContext = new CrossCheckViolationContext(
+            $this->getDataValueDeserializerMock(),
+            $this->getEntityIdFormatterMock(),
+            $this->getValueFormatterMock(),
+            $this->getDumpMetaInformationRepoMock( $dumpMetaInformation ),
+            new JsonFileEntityLookup( __DIR__ . '/testdata' )
+        );
+    }
+
+    public function tearDown() {
+        unset( $this->violationContext );
+
+        parent::tearDown();
+    }
+
+
+    public function testGetTypes() {
+        $actualResult = $this->violationContext->getTypes();
+        $expectedResult = array(
+            new ItemId( 'Q21' ),
+            new ItemId( 'Q42' ),
+            new ItemId( 'Q84' )
+        );
+
+        $this->assertArrayEquals( $expectedResult, $actualResult );
+    }
+
+
+    /**
+     * @dataProvider isContextForDataProvider
+     */
+    public function testIsContextFor( $expectedResult, $violation ) {
+        $actualResult = $this->violationContext->isContextFor( $violation );
+
+        $this->assertEquals( $expectedResult, $actualResult );
+    }
+
+    /**
+     * Test cases for testIsContextFor
+     * @return array
+     */
+    public function isContextForDataProvider() {
+        return array(
+            array(
+                true,
+                $this->getViolationMock( new ItemId( 'Q42' ), 'foobar', 
'wbqev|foobar' )
+            ),
+            array(
+                false,
+                $this->getViolationMock( new ItemId( 'Q42' ), 'foobar', 
'wbqc|foobar' )
+            ),
+            array(
+                false,
+                $this->getViolationMock( new ItemId( 'Q42' ), 'foobar', 
'foobar' )
+            )
+        );
+    }
+
+
+    /**
+     * @dataProvider formatAdditionalInformationDataProvider
+     */
+    public function testFormatAdditionalInformation( $expectedResult, 
$violation, $expectedException = null ) {
+        $this->setExpectedException( $expectedException );
+
+        global $wgLang;
+        $wgLang = Language::factory( 'qqx' );
+        $actualResult = $this->violationContext->formatAdditionalInformation( 
$violation );
+
+        $this->assertEquals( $expectedResult, $actualResult );
+    }
+
+    /**
+     * Test cases for testFormatAdditionalInformation
+     * @return array
+     */
+    public function formatAdditionalInformationDataProvider() {
+        return array(
+            array(
+                '<p><span 
class="wbq-violations-additional-information-header">(wbqev-violation-header-external-source)</span><br
 />Q42</p><p><span 
class="wbq-violations-additional-information-header">(wbqev-violation-header-external-values)</span><br
 />foo</p>',
+                $this->getViolationMock(
+                    new ItemId( 'Q1' ),
+                    'Q1$c0f25a6f-9e33-41c8-be34-c86a730ff30b',
+                    'wbqev|foobar',
+                    'Q42',
+                    array(
+                        'dump_id' => 'foobar',
+                        'external_values' => array( 'foo' )
+                    )
+                )
+            ),
+            array(
+                '<p><span 
class="wbq-violations-additional-information-header">(wbqev-violation-header-external-source)</span><br
 />Q42</p><p><span 
class="wbq-violations-additional-information-header">(wbqev-violation-header-external-values)</span><br
 />foo<br />bar</p>',
+                $this->getViolationMock(
+                    new ItemId( 'Q1' ),
+                    'Q1$c0f25a6f-9e33-41c8-be34-c86a730ff30b',
+                    'wbqev|foobar',
+                    'Q42',
+                    array(
+                        'dump_id' => 'foobar',
+                        'external_values' => array( 'foo', 'bar' )
+                    )
+                )
+            ),
+            array(
+                null,
+                $this->getViolationMock(
+                    new ItemId( 'Q1' ),
+                    'Q1$c0f25a6f-9e33-41c8-be34-c86a730ff30b',
+                    'wbqev|foobar',
+                    'Q42',
+                    array()
+                )
+            ),
+            array(
+                null,
+                $this->getViolationMock(
+                    new ItemId( 'Q1' ),
+                    'Q1$c0f25a6f-9e33-41c8-be34-c86a730ff30b',
+                    'wbqc|foobar',
+                    'Q42',
+                    array()
+                ),
+                'InvalidArgumentException'
+            )
+        );
+    }
+
+
+    /**
+     * @dataProvider getMessageDataProvider
+     */
+    public function testGetMessage( $expectedResult, $violation, 
$expectedException = null ) {
+        $this->setExpectedException( $expectedException );
+
+        global $wgLang;
+        $wgLang = Language::factory( 'qqx' );
+        $actualResult = $this->violationContext->getMessage( $violation );
+
+        $this->assertEquals( $expectedResult, $actualResult );
+    }
+
+    /**
+     * Test cases for testGetMessage
+     * @return array
+     */
+    public function getMessageDataProvider() {
+        return array(
+            array(
+                '(wbqev-violation-message: Q42)',
+                $this->getViolationMock(
+                    new ItemId( 'Q1' ),
+                    'foobar',
+                    'wbqev|foobar',
+                    'Q42'
+                )
+            ),
+            array(
+                '(wbqev-violation-message: Q84)',
+                $this->getViolationMock(
+                    new ItemId( 'Q1' ),
+                    'foobar',
+                    'wbqev|foobar',
+                    'Q84'
+                )
+            ),
+            array(
+                null,
+                $this->getViolationMock(
+                    new ItemId( 'Q1' ),
+                    'foobar',
+                    'wbqc|foobar',
+                    'Q84'
+                ),
+                'InvalidArgumentException'
+            )
+        );
+    }
+
+
+    private function getDataValueDeserializerMock() {
+        $mock = $this->getMockBuilder( 'Deserializers\Deserializer' )
+            ->setMethods( array( 'deserialize' ) )
+            ->getMockForAbstractClass();
+        $mock->expects( $this->any() )
+            ->method( 'deserialize' )
+            ->willReturnCallback(
+                function( $value ) {
+                    return new StringValue( $value );
+                }
+            );
+
+        return $mock;
+    }
+
+    private function getEntityIdFormatterMock() {
+        $mock = $this->getMockBuilder( 'Wikibase\Lib\EntityIdFormatter' )
+            ->setMethods( array( 'formatEntityId' ) )
+            ->getMockForAbstractClass();
+        $mock->expects( $this->any() )
+            ->method( 'formatEntityId' )
+            ->willReturnCallback(
+                function( EntityId $entityId ) {
+                    return $entityId->getSerialization();
+                }
+            );
+
+        return $mock;
+    }
+
+    private function getValueFormatterMock() {
+        $mock = $this->getMockBuilder( 'ValueFormatters\ValueFormatter' )
+            ->setMethods( array( 'format' ) )
+            ->getMockForAbstractClass();
+        $mock->expects( $this->any() )
+            ->method( 'format' )
+            ->willReturnCallback(
+                function( DataValue $dataValue ) {
+                    return $dataValue->getValue();
+                }
+            );
+
+        return $mock;
+    }
+
+    private function getDumpMetaInformationRepoMock( array 
$dumpMetaInformation ) {
+        $mock = $this->getMockBuilder( 
'WikibaseQuality\ExternalValidation\DumpMetaInformation\DumpMetaInformationRepo'
 )
+            ->setMethods( array( 'getWithId', 'getAll' ) )
+            ->disableOriginalConstructor()
+            ->getMock();
+        $mock->expects( $this->any() )
+            ->method( 'getAll' )
+            ->willReturn( $dumpMetaInformation );
+        $mock->expects( $this->any() )
+            ->method( 'getWithId' )
+            ->willReturn( $this->getDumpMetaInformationMock( new ItemId( 'Q42' 
) ) );
+
+        return $mock;
+    }
+
+    private function getDumpMetaInformationMock( EntityId $sourceItemId ) {
+        $mock = $this->getMockBuilder( 
'WikibaseQuality\ExternalValidation\DumpMetaInformation\DumpMetaInformation' )
+            ->setMethods( array( 'getSourceItemId' ) )
+            ->disableOriginalConstructor()
+            ->getMock();
+        $mock->expects( $this->any() )
+            ->method( 'getSourceItemId' )
+            ->willReturn( $sourceItemId );
+
+        return $mock;
+    }
+
+    private function getViolationMock( $entityId = null, $claimGuid = null, 
$constraintId = null, $constraintTypeEntityId = null, $additionalInformation = 
null ) {
+        $mock = $this->getMockBuilder( 'WikibaseQuality\Violations\Violation' )
+            ->setMethods( array( 'getEntityId', 'getClaimGuid', 
'getConstraintId', 'getConstraintTypeEntityId', 'getAdditionalInfo' ) )
+            ->disableOriginalConstructor()
+            ->getMock();
+        $mock->expects( $this->any() )
+            ->method( 'getEntityId' )
+            ->willReturn( $entityId );
+        $mock->expects( $this->any() )
+            ->method( 'getClaimGuid' )
+            ->willReturn( $claimGuid );
+        $mock->expects( $this->any() )
+            ->method( 'getConstraintId' )
+            ->willReturn( $constraintId );
+        $mock->expects( $this->any() )
+            ->method( 'getConstraintTypeEntityId' )
+            ->willReturn( $constraintTypeEntityId );
+        $mock->expects( $this->any() )
+            ->method( 'getAdditionalInfo' )
+            ->willReturn( $additionalInformation );
+
+        return $mock;
+    }
+}
diff --git a/tests/phpunit/Violations/testdata/Q1.json 
b/tests/phpunit/Violations/testdata/Q1.json
new file mode 100644
index 0000000..942a8a7
--- /dev/null
+++ b/tests/phpunit/Violations/testdata/Q1.json
@@ -0,0 +1,22 @@
+{
+  "id": "Q1",
+  "type": "item",
+  "claims": {
+    "P1": [
+      {
+        "id": "Q1$c0f25a6f-9e33-41c8-be34-c86a730ff30b",
+        "mainsnak": {
+          "snaktype": "value",
+          "property": "P1",
+          "datatype": "string",
+          "datavalue": {
+            "value": "foo",
+            "type": "string"
+          }
+        },
+        "type": "statement",
+        "rank": "normal"
+      }
+    ]
+  }
+}
\ No newline at end of file

-- 
To view, visit https://gerrit.wikimedia.org/r/213820
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I0fce5c48797de6ff01f840339bad07872642f855
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/WikidataQualityExternalValidation
Gerrit-Branch: master
Gerrit-Owner: Soeren.oldag <[email protected]>
Gerrit-Reviewer: Dominic.sauer <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to