1<?php 2 3use MediaWiki\MediaWikiServices; 4use MediaWiki\Revision\RevisionRecord; 5use MediaWiki\Tests\Maintenance\DumpAsserter; 6 7/** 8 * Import/export round trip test. 9 * 10 * @group Database 11 * @covers WikiExporter 12 * @covers WikiImporter 13 */ 14class ImportExportTest extends MediaWikiLangTestCase { 15 16 public function setUp(): void { 17 parent::setUp(); 18 19 $slotRoleRegistry = MediaWikiServices::getInstance()->getSlotRoleRegistry(); 20 21 if ( !$slotRoleRegistry->isDefinedRole( 'ImportExportTest' ) ) { 22 $slotRoleRegistry->defineRoleWithModel( 'ImportExportTest', CONTENT_MODEL_WIKITEXT ); 23 } 24 } 25 26 /** 27 * @param string $schemaVersion 28 * 29 * @return WikiExporter 30 */ 31 private function getExporter( string $schemaVersion ) { 32 $exporter = new WikiExporter( $this->db, WikiExporter::FULL ); 33 $exporter->setSchemaVersion( $schemaVersion ); 34 return $exporter; 35 } 36 37 /** 38 * @param ImportSource $source 39 * 40 * @return WikiImporter 41 */ 42 private function getImporter( ImportSource $source ) { 43 $config = new HashConfig( [ 44 'CommandLineMode' => true, 45 ] ); 46 $importer = new WikiImporter( $source, $config ); 47 return $importer; 48 } 49 50 /** 51 * @param string $testName 52 * 53 * @return string[] 54 */ 55 private function getFilesToImport( string $testName ) { 56 return glob( __DIR__ . "/../../data/import/$testName.import-*.xml" ); 57 } 58 59 /** 60 * @param string $name 61 * @param string $schemaVersion 62 * 63 * @return string path of the dump file 64 */ 65 protected function getDumpTemplatePath( $name, $schemaVersion ) { 66 return __DIR__ . "/../../data/import/$name.$schemaVersion.xml"; 67 } 68 69 /** 70 * @param string $prefix 71 * @param string[] $keys 72 * 73 * @return string[] 74 */ 75 private function getUniqueNames( string $prefix, array $keys ) { 76 $names = []; 77 78 foreach ( $keys as $k ) { 79 $names[$k] = "$prefix-$k-" . wfRandomString( 6 ); 80 } 81 82 return $names; 83 } 84 85 /** 86 * @param string $xmlData 87 * @param string[] $pageTitles 88 * 89 * @return string 90 */ 91 private function injectPageTitles( string $xmlData, array $pageTitles ) { 92 $keys = array_map( static function ( $name ) { 93 return "{{{$name}_title}}"; 94 }, array_keys( $pageTitles ) ); 95 96 return str_replace( 97 $keys, 98 array_values( $pageTitles ), 99 $xmlData 100 ); 101 } 102 103 /** 104 * @param Title $title 105 * 106 * @return RevisionRecord[] 107 */ 108 private function getRevisions( Title $title ) { 109 $store = MediaWikiServices::getInstance()->getRevisionStore(); 110 $qi = $store->getQueryInfo(); 111 112 $conds = [ 'rev_page' => $title->getArticleID() ]; 113 $opt = [ 'ORDER BY' => 'rev_id ASC' ]; 114 115 $rows = $this->db->select( 116 $qi['tables'], 117 $qi['fields'], 118 $conds, 119 __METHOD__, 120 $opt, 121 $qi['joins'] 122 ); 123 124 $status = $store->newRevisionsFromBatch( $rows ); 125 return $status->getValue(); 126 } 127 128 /** 129 * @param string[] $pageTitles 130 * 131 * @return string[] 132 */ 133 private function getPageInfoVars( array $pageTitles ) { 134 $vars = []; 135 foreach ( $pageTitles as $name => $page ) { 136 $title = Title::newFromText( $page ); 137 138 if ( !$title->exists( Title::READ_LATEST ) ) { 139 // map only existing pages, since only they can appear in a dump 140 continue; 141 } 142 143 $vars[ $name . '_pageid' ] = $title->getArticleID(); 144 $vars[ $name . '_title' ] = $title->getPrefixedDBkey(); 145 $vars[ $name . '_namespace' ] = $title->getNamespace(); 146 147 $n = 1; 148 $revisions = $this->getRevisions( $title ); 149 foreach ( $revisions as $i => $rev ) { 150 $revkey = "{$name}_rev" . $n++; 151 152 $vars[ $revkey . '_id' ] = $rev->getId(); 153 $vars[ $revkey . '_userid' ] = $rev->getUser()->getId(); 154 } 155 } 156 157 return $vars; 158 } 159 160 /** 161 * @param string $schemaVersion 162 * @return string[] 163 */ 164 private function getSiteVars( $schemaVersion ) { 165 global $wgSitename, $wgDBname, $wgVersion, $wgCapitalLinks; 166 167 $vars = []; 168 $vars['mw_version'] = $wgVersion; 169 $vars['schema_version'] = $schemaVersion; 170 171 $vars['site_name'] = $wgSitename; 172 $vars['project_namespace'] = 173 MediaWikiServices::getInstance()->getTitleFormatter()->getNamespaceName( 174 NS_PROJECT, 175 'Dummy' 176 ); 177 $vars['site_db'] = $wgDBname; 178 $vars['site_case'] = $wgCapitalLinks ? 'first-letter' : 'case-sensitive'; 179 $vars['site_base'] = Title::newMainPage()->getCanonicalURL(); 180 $vars['site_language'] = MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode(); 181 182 return $vars; 183 } 184 185 public function provideImportExport() { 186 foreach ( XmlDumpWriter::$supportedSchemas as $schemaVersion ) { 187 yield [ 'Basic', $schemaVersion ]; 188 yield [ 'Dupes', $schemaVersion ]; 189 yield [ 'Slots', $schemaVersion ]; 190 yield [ 'Interleaved', $schemaVersion ]; 191 yield [ 'InterleavedMulti', $schemaVersion ]; 192 yield [ 'MissingMainContentModel', $schemaVersion ]; 193 yield [ 'MissingSlotContentModel', $schemaVersion ]; 194 } 195 } 196 197 /** 198 * @dataProvider provideImportExport 199 */ 200 public function testImportExport( $testName, $schemaVersion ) { 201 $asserter = new DumpAsserter( $schemaVersion ); 202 203 $filesToImport = $this->getFilesToImport( $testName ); 204 $fileToExpect = $this->getDumpTemplatePath( "$testName.expected", $schemaVersion ); 205 $siteInfoExpect = $this->getDumpTemplatePath( 'SiteInfo', $schemaVersion ); 206 207 $pageKeys = [ 'page1', 'page2', 'page3', 'page4', ]; 208 $pageTitles = $this->getUniqueNames( $testName, $pageKeys ); 209 210 // import each file 211 foreach ( $filesToImport as $fileName ) { 212 $xmlData = file_get_contents( $fileName ); 213 $xmlData = $this->injectPageTitles( $xmlData, $pageTitles ); 214 215 $source = new ImportStringSource( $xmlData ); 216 $importer = $this->getImporter( $source ); 217 $importer->doImport(); 218 } 219 220 // write dump 221 $exporter = $this->getExporter( $schemaVersion ); 222 223 $tmpFile = $this->getNewTempFile(); 224 $buffer = new DumpFileOutput( $tmpFile ); 225 226 $exporter->setOutputSink( $buffer ); 227 $exporter->openStream(); 228 $exporter->pagesByName( $pageTitles ); 229 $exporter->closeStream(); 230 231 // determine expected variable values 232 $vars = array_merge( 233 $this->getSiteVars( $schemaVersion ), 234 $this->getPageInfoVars( $pageTitles ) 235 ); 236 237 foreach ( $vars as $key => $value ) { 238 $asserter->setVarMapping( $key, $value ); 239 } 240 241 // sanity check 242 $dumpData = file_get_contents( $tmpFile ); 243 $this->assertNotEmpty( $dumpData, 'Dump XML' ); 244 245 // check dump content 246 $asserter->assertDumpStart( $tmpFile, $siteInfoExpect ); 247 $asserter->assertDOM( $fileToExpect ); 248 $asserter->assertDumpEnd(); 249 } 250 251} 252