CommentStoreTest.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783
  1. <?php
  2. use MediaWiki\MediaWikiServices;
  3. use Wikimedia\ScopedCallback;
  4. use Wikimedia\TestingAccessWrapper;
  5. /**
  6. * @group Database
  7. * @covers CommentStore
  8. * @covers CommentStoreComment
  9. */
  10. class CommentStoreTest extends MediaWikiLangTestCase {
  11. protected $tablesUsed = [
  12. 'revision',
  13. 'revision_comment_temp',
  14. 'ipblocks',
  15. 'comment',
  16. ];
  17. /**
  18. * Create a store for a particular stage
  19. * @param int $stage
  20. * @return CommentStore
  21. */
  22. protected function makeStore( $stage ) {
  23. $store = new CommentStore( MediaWikiServices::getInstance()->getContentLanguage(), $stage );
  24. return $store;
  25. }
  26. /**
  27. * Create a store for a particular stage and key (for testing deprecated behaviour)
  28. * @param int $stage
  29. * @param string $key
  30. * @return CommentStore
  31. */
  32. protected function makeStoreWithKey( $stage, $key ) {
  33. $this->hideDeprecated( 'CommentStore::newKey' );
  34. $store = CommentStore::newKey( $key );
  35. TestingAccessWrapper::newFromObject( $store )->stage = $stage;
  36. return $store;
  37. }
  38. /**
  39. * @dataProvider provideGetFields
  40. * @param int $stage
  41. * @param string $key
  42. * @param array $expect
  43. */
  44. public function testGetFields_withKeyConstruction( $stage, $key, $expect ) {
  45. $store = $this->makeStoreWithKey( $stage, $key );
  46. $result = $store->getFields();
  47. $this->assertEquals( $expect, $result );
  48. }
  49. /**
  50. * @dataProvider provideGetFields
  51. * @param int $stage
  52. * @param string $key
  53. * @param array $expect
  54. */
  55. public function testGetFields( $stage, $key, $expect ) {
  56. $store = $this->makeStore( $stage );
  57. $result = $store->getFields( $key );
  58. $this->assertEquals( $expect, $result );
  59. }
  60. public static function provideGetFields() {
  61. return [
  62. 'Simple table, old' => [
  63. MIGRATION_OLD, 'ipb_reason',
  64. [ 'ipb_reason_text' => 'ipb_reason', 'ipb_reason_data' => 'NULL', 'ipb_reason_cid' => 'NULL' ],
  65. ],
  66. 'Simple table, write-both' => [
  67. MIGRATION_WRITE_BOTH, 'ipb_reason',
  68. [ 'ipb_reason_old' => 'ipb_reason', 'ipb_reason_id' => 'ipb_reason_id' ],
  69. ],
  70. 'Simple table, write-new' => [
  71. MIGRATION_WRITE_NEW, 'ipb_reason',
  72. [ 'ipb_reason_old' => 'ipb_reason', 'ipb_reason_id' => 'ipb_reason_id' ],
  73. ],
  74. 'Simple table, new' => [
  75. MIGRATION_NEW, 'ipb_reason',
  76. [ 'ipb_reason_id' => 'ipb_reason_id' ],
  77. ],
  78. 'Revision, old' => [
  79. MIGRATION_OLD, 'rev_comment',
  80. [
  81. 'rev_comment_text' => 'rev_comment',
  82. 'rev_comment_data' => 'NULL',
  83. 'rev_comment_cid' => 'NULL',
  84. ],
  85. ],
  86. 'Revision, write-both' => [
  87. MIGRATION_WRITE_BOTH, 'rev_comment',
  88. [ 'rev_comment_old' => 'rev_comment', 'rev_comment_pk' => 'rev_id' ],
  89. ],
  90. 'Revision, write-new' => [
  91. MIGRATION_WRITE_NEW, 'rev_comment',
  92. [ 'rev_comment_old' => 'rev_comment', 'rev_comment_pk' => 'rev_id' ],
  93. ],
  94. 'Revision, new' => [
  95. MIGRATION_NEW, 'rev_comment',
  96. [ 'rev_comment_pk' => 'rev_id' ],
  97. ],
  98. 'Image, old' => [
  99. MIGRATION_OLD, 'img_description',
  100. [
  101. 'img_description_text' => 'img_description',
  102. 'img_description_data' => 'NULL',
  103. 'img_description_cid' => 'NULL',
  104. ],
  105. ],
  106. 'Image, write-both' => [
  107. MIGRATION_WRITE_BOTH, 'img_description',
  108. [ 'img_description_old' => 'img_description', 'img_description_pk' => 'img_name' ],
  109. ],
  110. 'Image, write-new' => [
  111. MIGRATION_WRITE_NEW, 'img_description',
  112. [ 'img_description_old' => 'img_description', 'img_description_pk' => 'img_name' ],
  113. ],
  114. 'Image, new' => [
  115. MIGRATION_NEW, 'img_description',
  116. [ 'img_description_pk' => 'img_name' ],
  117. ],
  118. ];
  119. }
  120. /**
  121. * @dataProvider provideGetJoin
  122. * @param int $stage
  123. * @param string $key
  124. * @param array $expect
  125. */
  126. public function testGetJoin_withKeyConstruction( $stage, $key, $expect ) {
  127. $store = $this->makeStoreWithKey( $stage, $key );
  128. $result = $store->getJoin();
  129. $this->assertEquals( $expect, $result );
  130. }
  131. /**
  132. * @dataProvider provideGetJoin
  133. * @param int $stage
  134. * @param string $key
  135. * @param array $expect
  136. */
  137. public function testGetJoin( $stage, $key, $expect ) {
  138. $store = $this->makeStore( $stage );
  139. $result = $store->getJoin( $key );
  140. $this->assertEquals( $expect, $result );
  141. }
  142. public static function provideGetJoin() {
  143. return [
  144. 'Simple table, old' => [
  145. MIGRATION_OLD, 'ipb_reason', [
  146. 'tables' => [],
  147. 'fields' => [
  148. 'ipb_reason_text' => 'ipb_reason',
  149. 'ipb_reason_data' => 'NULL',
  150. 'ipb_reason_cid' => 'NULL',
  151. ],
  152. 'joins' => [],
  153. ],
  154. ],
  155. 'Simple table, write-both' => [
  156. MIGRATION_WRITE_BOTH, 'ipb_reason', [
  157. 'tables' => [ 'comment_ipb_reason' => 'comment' ],
  158. 'fields' => [
  159. 'ipb_reason_text' => 'COALESCE( comment_ipb_reason.comment_text, ipb_reason )',
  160. 'ipb_reason_data' => 'comment_ipb_reason.comment_data',
  161. 'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
  162. ],
  163. 'joins' => [
  164. 'comment_ipb_reason' => [ 'LEFT JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
  165. ],
  166. ],
  167. ],
  168. 'Simple table, write-new' => [
  169. MIGRATION_WRITE_NEW, 'ipb_reason', [
  170. 'tables' => [ 'comment_ipb_reason' => 'comment' ],
  171. 'fields' => [
  172. 'ipb_reason_text' => 'COALESCE( comment_ipb_reason.comment_text, ipb_reason )',
  173. 'ipb_reason_data' => 'comment_ipb_reason.comment_data',
  174. 'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
  175. ],
  176. 'joins' => [
  177. 'comment_ipb_reason' => [ 'LEFT JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
  178. ],
  179. ],
  180. ],
  181. 'Simple table, new' => [
  182. MIGRATION_NEW, 'ipb_reason', [
  183. 'tables' => [ 'comment_ipb_reason' => 'comment' ],
  184. 'fields' => [
  185. 'ipb_reason_text' => 'comment_ipb_reason.comment_text',
  186. 'ipb_reason_data' => 'comment_ipb_reason.comment_data',
  187. 'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
  188. ],
  189. 'joins' => [
  190. 'comment_ipb_reason' => [ 'JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
  191. ],
  192. ],
  193. ],
  194. 'Revision, old' => [
  195. MIGRATION_OLD, 'rev_comment', [
  196. 'tables' => [],
  197. 'fields' => [
  198. 'rev_comment_text' => 'rev_comment',
  199. 'rev_comment_data' => 'NULL',
  200. 'rev_comment_cid' => 'NULL',
  201. ],
  202. 'joins' => [],
  203. ],
  204. ],
  205. 'Revision, write-both' => [
  206. MIGRATION_WRITE_BOTH, 'rev_comment', [
  207. 'tables' => [
  208. 'temp_rev_comment' => 'revision_comment_temp',
  209. 'comment_rev_comment' => 'comment',
  210. ],
  211. 'fields' => [
  212. 'rev_comment_text' => 'COALESCE( comment_rev_comment.comment_text, rev_comment )',
  213. 'rev_comment_data' => 'comment_rev_comment.comment_data',
  214. 'rev_comment_cid' => 'comment_rev_comment.comment_id',
  215. ],
  216. 'joins' => [
  217. 'temp_rev_comment' => [ 'LEFT JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
  218. 'comment_rev_comment' => [ 'LEFT JOIN',
  219. 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
  220. ],
  221. ],
  222. ],
  223. 'Revision, write-new' => [
  224. MIGRATION_WRITE_NEW, 'rev_comment', [
  225. 'tables' => [
  226. 'temp_rev_comment' => 'revision_comment_temp',
  227. 'comment_rev_comment' => 'comment',
  228. ],
  229. 'fields' => [
  230. 'rev_comment_text' => 'COALESCE( comment_rev_comment.comment_text, rev_comment )',
  231. 'rev_comment_data' => 'comment_rev_comment.comment_data',
  232. 'rev_comment_cid' => 'comment_rev_comment.comment_id',
  233. ],
  234. 'joins' => [
  235. 'temp_rev_comment' => [ 'LEFT JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
  236. 'comment_rev_comment' => [ 'LEFT JOIN',
  237. 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
  238. ],
  239. ],
  240. ],
  241. 'Revision, new' => [
  242. MIGRATION_NEW, 'rev_comment', [
  243. 'tables' => [
  244. 'temp_rev_comment' => 'revision_comment_temp',
  245. 'comment_rev_comment' => 'comment',
  246. ],
  247. 'fields' => [
  248. 'rev_comment_text' => 'comment_rev_comment.comment_text',
  249. 'rev_comment_data' => 'comment_rev_comment.comment_data',
  250. 'rev_comment_cid' => 'comment_rev_comment.comment_id',
  251. ],
  252. 'joins' => [
  253. 'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
  254. 'comment_rev_comment' => [ 'JOIN',
  255. 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
  256. ],
  257. ],
  258. ],
  259. 'Image, old' => [
  260. MIGRATION_OLD, 'img_description', [
  261. 'tables' => [],
  262. 'fields' => [
  263. 'img_description_text' => 'img_description',
  264. 'img_description_data' => 'NULL',
  265. 'img_description_cid' => 'NULL',
  266. ],
  267. 'joins' => [],
  268. ],
  269. ],
  270. 'Image, write-both' => [
  271. MIGRATION_WRITE_BOTH, 'img_description', [
  272. 'tables' => [
  273. 'temp_img_description' => 'image_comment_temp',
  274. 'comment_img_description' => 'comment',
  275. ],
  276. 'fields' => [
  277. 'img_description_text' => 'COALESCE( comment_img_description.comment_text, img_description )',
  278. 'img_description_data' => 'comment_img_description.comment_data',
  279. 'img_description_cid' => 'comment_img_description.comment_id',
  280. ],
  281. 'joins' => [
  282. 'temp_img_description' => [ 'LEFT JOIN', 'temp_img_description.imgcomment_name = img_name' ],
  283. 'comment_img_description' => [ 'LEFT JOIN',
  284. 'comment_img_description.comment_id = temp_img_description.imgcomment_description_id' ],
  285. ],
  286. ],
  287. ],
  288. 'Image, write-new' => [
  289. MIGRATION_WRITE_NEW, 'img_description', [
  290. 'tables' => [
  291. 'temp_img_description' => 'image_comment_temp',
  292. 'comment_img_description' => 'comment',
  293. ],
  294. 'fields' => [
  295. 'img_description_text' => 'COALESCE( comment_img_description.comment_text, img_description )',
  296. 'img_description_data' => 'comment_img_description.comment_data',
  297. 'img_description_cid' => 'comment_img_description.comment_id',
  298. ],
  299. 'joins' => [
  300. 'temp_img_description' => [ 'LEFT JOIN', 'temp_img_description.imgcomment_name = img_name' ],
  301. 'comment_img_description' => [ 'LEFT JOIN',
  302. 'comment_img_description.comment_id = temp_img_description.imgcomment_description_id' ],
  303. ],
  304. ],
  305. ],
  306. 'Image, new' => [
  307. MIGRATION_NEW, 'img_description', [
  308. 'tables' => [
  309. 'temp_img_description' => 'image_comment_temp',
  310. 'comment_img_description' => 'comment',
  311. ],
  312. 'fields' => [
  313. 'img_description_text' => 'comment_img_description.comment_text',
  314. 'img_description_data' => 'comment_img_description.comment_data',
  315. 'img_description_cid' => 'comment_img_description.comment_id',
  316. ],
  317. 'joins' => [
  318. 'temp_img_description' => [ 'JOIN', 'temp_img_description.imgcomment_name = img_name' ],
  319. 'comment_img_description' => [ 'JOIN',
  320. 'comment_img_description.comment_id = temp_img_description.imgcomment_description_id' ],
  321. ],
  322. ],
  323. ],
  324. ];
  325. }
  326. private function assertComment( $expect, $actual, $from ) {
  327. $this->assertSame( $expect['text'], $actual->text, "text $from" );
  328. $this->assertInstanceOf( get_class( $expect['message'] ), $actual->message,
  329. "message class $from" );
  330. $this->assertSame( $expect['message']->getKeysToTry(), $actual->message->getKeysToTry(),
  331. "message keys $from" );
  332. $this->assertEquals( $expect['message']->text(), $actual->message->text(),
  333. "message rendering $from" );
  334. $this->assertEquals( $expect['data'], $actual->data, "data $from" );
  335. }
  336. /**
  337. * @dataProvider provideInsertRoundTrip
  338. * @param string $table
  339. * @param string $key
  340. * @param string $pk
  341. * @param string $extraFields
  342. * @param string|Message $comment
  343. * @param array|null $data
  344. * @param array $expect
  345. */
  346. public function testInsertRoundTrip( $table, $key, $pk, $extraFields, $comment, $data, $expect ) {
  347. $expectOld = [
  348. 'text' => $expect['text'],
  349. 'message' => new RawMessage( '$1', [ $expect['text'] ] ),
  350. 'data' => null,
  351. ];
  352. $stages = [
  353. MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
  354. MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW,
  355. MIGRATION_NEW ],
  356. MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
  357. MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
  358. ];
  359. foreach ( $stages as $writeStage => $possibleReadStages ) {
  360. if ( $key === 'ipb_reason' ) {
  361. $extraFields['ipb_address'] = __CLASS__ . "#$writeStage";
  362. }
  363. $wstore = $this->makeStore( $writeStage );
  364. $usesTemp = $key === 'rev_comment';
  365. if ( $usesTemp ) {
  366. list( $fields, $callback ) = $wstore->insertWithTempTable(
  367. $this->db, $key, $comment, $data
  368. );
  369. } else {
  370. $fields = $wstore->insert( $this->db, $key, $comment, $data );
  371. }
  372. if ( $writeStage <= MIGRATION_WRITE_BOTH ) {
  373. $this->assertSame( $expect['text'], $fields[$key], "old field, stage=$writeStage" );
  374. } else {
  375. $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
  376. }
  377. if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) {
  378. $this->assertArrayHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
  379. } else {
  380. $this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
  381. }
  382. $this->db->insert( $table, $extraFields + $fields, __METHOD__ );
  383. $id = $this->db->insertId();
  384. if ( $usesTemp ) {
  385. $callback( $id );
  386. }
  387. foreach ( $possibleReadStages as $readStage ) {
  388. $rstore = $this->makeStore( $readStage );
  389. $fieldRow = $this->db->selectRow(
  390. $table,
  391. $rstore->getFields( $key ),
  392. [ $pk => $id ],
  393. __METHOD__
  394. );
  395. $queryInfo = $rstore->getJoin( $key );
  396. $joinRow = $this->db->selectRow(
  397. [ $table ] + $queryInfo['tables'],
  398. $queryInfo['fields'],
  399. [ $pk => $id ],
  400. __METHOD__,
  401. [],
  402. $queryInfo['joins']
  403. );
  404. $this->assertComment(
  405. $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
  406. $rstore->getCommentLegacy( $this->db, $key, $fieldRow ),
  407. "w=$writeStage, r=$readStage, from getFields()"
  408. );
  409. $this->assertComment(
  410. $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
  411. $rstore->getComment( $key, $joinRow ),
  412. "w=$writeStage, r=$readStage, from getJoin()"
  413. );
  414. }
  415. }
  416. }
  417. /**
  418. * @dataProvider provideInsertRoundTrip
  419. * @param string $table
  420. * @param string $key
  421. * @param string $pk
  422. * @param string $extraFields
  423. * @param string|Message $comment
  424. * @param array|null $data
  425. * @param array $expect
  426. */
  427. public function testInsertRoundTrip_withKeyConstruction(
  428. $table, $key, $pk, $extraFields, $comment, $data, $expect
  429. ) {
  430. $expectOld = [
  431. 'text' => $expect['text'],
  432. 'message' => new RawMessage( '$1', [ $expect['text'] ] ),
  433. 'data' => null,
  434. ];
  435. $stages = [
  436. MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
  437. MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW,
  438. MIGRATION_NEW ],
  439. MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
  440. MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
  441. ];
  442. foreach ( $stages as $writeStage => $possibleReadStages ) {
  443. if ( $key === 'ipb_reason' ) {
  444. $extraFields['ipb_address'] = __CLASS__ . "#$writeStage";
  445. }
  446. $wstore = $this->makeStoreWithKey( $writeStage, $key );
  447. $usesTemp = $key === 'rev_comment';
  448. if ( $usesTemp ) {
  449. list( $fields, $callback ) = $wstore->insertWithTempTable(
  450. $this->db, $comment, $data
  451. );
  452. } else {
  453. $fields = $wstore->insert( $this->db, $comment, $data );
  454. }
  455. if ( $writeStage <= MIGRATION_WRITE_BOTH ) {
  456. $this->assertSame( $expect['text'], $fields[$key], "old field, stage=$writeStage" );
  457. } else {
  458. $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
  459. }
  460. if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) {
  461. $this->assertArrayHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
  462. } else {
  463. $this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
  464. }
  465. $this->db->insert( $table, $extraFields + $fields, __METHOD__ );
  466. $id = $this->db->insertId();
  467. if ( $usesTemp ) {
  468. $callback( $id );
  469. }
  470. foreach ( $possibleReadStages as $readStage ) {
  471. $rstore = $this->makeStoreWithKey( $readStage, $key );
  472. $fieldRow = $this->db->selectRow(
  473. $table,
  474. $rstore->getFields(),
  475. [ $pk => $id ],
  476. __METHOD__
  477. );
  478. $queryInfo = $rstore->getJoin();
  479. $joinRow = $this->db->selectRow(
  480. [ $table ] + $queryInfo['tables'],
  481. $queryInfo['fields'],
  482. [ $pk => $id ],
  483. __METHOD__,
  484. [],
  485. $queryInfo['joins']
  486. );
  487. $this->assertComment(
  488. $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
  489. $rstore->getCommentLegacy( $this->db, $fieldRow ),
  490. "w=$writeStage, r=$readStage, from getFields()"
  491. );
  492. $this->assertComment(
  493. $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
  494. $rstore->getComment( $joinRow ),
  495. "w=$writeStage, r=$readStage, from getJoin()"
  496. );
  497. }
  498. }
  499. }
  500. public static function provideInsertRoundTrip() {
  501. $db = wfGetDB( DB_REPLICA ); // for timestamps
  502. $msgComment = new Message( 'parentheses', [ 'message comment' ] );
  503. $textCommentMsg = new RawMessage( '$1', [ 'text comment' ] );
  504. $nestedMsgComment = new Message( [ 'parentheses', 'rawmessage' ], [ new Message( 'mainpage' ) ] );
  505. $ipbfields = [
  506. 'ipb_range_start' => '',
  507. 'ipb_range_end' => '',
  508. 'ipb_timestamp' => $db->timestamp(),
  509. 'ipb_expiry' => $db->getInfinity(),
  510. ];
  511. $revfields = [
  512. 'rev_page' => 42,
  513. 'rev_text_id' => 42,
  514. 'rev_len' => 0,
  515. 'rev_timestamp' => $db->timestamp(),
  516. ];
  517. $comStoreComment = new CommentStoreComment(
  518. null, 'comment store comment', null, [ 'foo' => 'bar' ]
  519. );
  520. return [
  521. 'Simple table, text comment' => [
  522. 'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, 'text comment', null, [
  523. 'text' => 'text comment',
  524. 'message' => $textCommentMsg,
  525. 'data' => null,
  526. ]
  527. ],
  528. 'Simple table, text comment with data' => [
  529. 'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, 'text comment', [ 'message' => 42 ], [
  530. 'text' => 'text comment',
  531. 'message' => $textCommentMsg,
  532. 'data' => [ 'message' => 42 ],
  533. ]
  534. ],
  535. 'Simple table, message comment' => [
  536. 'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, $msgComment, null, [
  537. 'text' => '(message comment)',
  538. 'message' => $msgComment,
  539. 'data' => null,
  540. ]
  541. ],
  542. 'Simple table, message comment with data' => [
  543. 'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, $msgComment, [ 'message' => 42 ], [
  544. 'text' => '(message comment)',
  545. 'message' => $msgComment,
  546. 'data' => [ 'message' => 42 ],
  547. ]
  548. ],
  549. 'Simple table, nested message comment' => [
  550. 'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, $nestedMsgComment, null, [
  551. 'text' => '(Main Page)',
  552. 'message' => $nestedMsgComment,
  553. 'data' => null,
  554. ]
  555. ],
  556. 'Simple table, CommentStoreComment' => [
  557. 'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, clone $comStoreComment, [ 'baz' => 'baz' ], [
  558. 'text' => 'comment store comment',
  559. 'message' => $comStoreComment->message,
  560. 'data' => [ 'foo' => 'bar' ],
  561. ]
  562. ],
  563. 'Revision, text comment' => [
  564. 'revision', 'rev_comment', 'rev_id', $revfields, 'text comment', null, [
  565. 'text' => 'text comment',
  566. 'message' => $textCommentMsg,
  567. 'data' => null,
  568. ]
  569. ],
  570. 'Revision, text comment with data' => [
  571. 'revision', 'rev_comment', 'rev_id', $revfields, 'text comment', [ 'message' => 42 ], [
  572. 'text' => 'text comment',
  573. 'message' => $textCommentMsg,
  574. 'data' => [ 'message' => 42 ],
  575. ]
  576. ],
  577. 'Revision, message comment' => [
  578. 'revision', 'rev_comment', 'rev_id', $revfields, $msgComment, null, [
  579. 'text' => '(message comment)',
  580. 'message' => $msgComment,
  581. 'data' => null,
  582. ]
  583. ],
  584. 'Revision, message comment with data' => [
  585. 'revision', 'rev_comment', 'rev_id', $revfields, $msgComment, [ 'message' => 42 ], [
  586. 'text' => '(message comment)',
  587. 'message' => $msgComment,
  588. 'data' => [ 'message' => 42 ],
  589. ]
  590. ],
  591. 'Revision, nested message comment' => [
  592. 'revision', 'rev_comment', 'rev_id', $revfields, $nestedMsgComment, null, [
  593. 'text' => '(Main Page)',
  594. 'message' => $nestedMsgComment,
  595. 'data' => null,
  596. ]
  597. ],
  598. 'Revision, CommentStoreComment' => [
  599. 'revision', 'rev_comment', 'rev_id', $revfields, clone $comStoreComment, [ 'baz' => 'baz' ], [
  600. 'text' => 'comment store comment',
  601. 'message' => $comStoreComment->message,
  602. 'data' => [ 'foo' => 'bar' ],
  603. ]
  604. ],
  605. ];
  606. }
  607. public function testGetCommentErrors() {
  608. Wikimedia\suppressWarnings();
  609. $reset = new ScopedCallback( 'Wikimedia\restoreWarnings' );
  610. $store = $this->makeStore( MIGRATION_OLD );
  611. $res = $store->getComment( 'dummy', [ 'dummy' => 'comment' ] );
  612. $this->assertSame( '', $res->text );
  613. $res = $store->getComment( 'dummy', [ 'dummy' => 'comment' ], true );
  614. $this->assertSame( 'comment', $res->text );
  615. $store = $this->makeStore( MIGRATION_NEW );
  616. try {
  617. $store->getComment( 'dummy', [ 'dummy' => 'comment' ] );
  618. $this->fail( 'Expected exception not thrown' );
  619. } catch ( InvalidArgumentException $ex ) {
  620. $this->assertSame( '$row does not contain fields needed for comment dummy', $ex->getMessage() );
  621. }
  622. $res = $store->getComment( 'dummy', [ 'dummy' => 'comment' ], true );
  623. $this->assertSame( 'comment', $res->text );
  624. try {
  625. $store->getComment( 'dummy', [ 'dummy_id' => 1 ] );
  626. $this->fail( 'Expected exception not thrown' );
  627. } catch ( InvalidArgumentException $ex ) {
  628. $this->assertSame(
  629. '$row does not contain fields needed for comment dummy and getComment(), '
  630. . 'but does have fields for getCommentLegacy()',
  631. $ex->getMessage()
  632. );
  633. }
  634. $store = $this->makeStore( MIGRATION_NEW );
  635. try {
  636. $store->getComment( 'rev_comment', [ 'rev_comment' => 'comment' ] );
  637. $this->fail( 'Expected exception not thrown' );
  638. } catch ( InvalidArgumentException $ex ) {
  639. $this->assertSame(
  640. '$row does not contain fields needed for comment rev_comment', $ex->getMessage()
  641. );
  642. }
  643. $res = $store->getComment( 'rev_comment', [ 'rev_comment' => 'comment' ], true );
  644. $this->assertSame( 'comment', $res->text );
  645. try {
  646. $store->getComment( 'rev_comment', [ 'rev_comment_pk' => 1 ] );
  647. $this->fail( 'Expected exception not thrown' );
  648. } catch ( InvalidArgumentException $ex ) {
  649. $this->assertSame(
  650. '$row does not contain fields needed for comment rev_comment and getComment(), '
  651. . 'but does have fields for getCommentLegacy()',
  652. $ex->getMessage()
  653. );
  654. }
  655. }
  656. public static function provideStages() {
  657. return [
  658. 'MIGRATION_OLD' => [ MIGRATION_OLD ],
  659. 'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH ],
  660. 'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW ],
  661. 'MIGRATION_NEW' => [ MIGRATION_NEW ],
  662. ];
  663. }
  664. /**
  665. * @dataProvider provideStages
  666. * @param int $stage
  667. * @expectedException InvalidArgumentException
  668. * @expectedExceptionMessage Must use insertWithTempTable() for rev_comment
  669. */
  670. public function testInsertWrong( $stage ) {
  671. $store = $this->makeStore( $stage );
  672. $store->insert( $this->db, 'rev_comment', 'foo' );
  673. }
  674. /**
  675. * @dataProvider provideStages
  676. * @param int $stage
  677. * @expectedException InvalidArgumentException
  678. * @expectedExceptionMessage Must use insert() for ipb_reason
  679. */
  680. public function testInsertWithTempTableWrong( $stage ) {
  681. $store = $this->makeStore( $stage );
  682. $store->insertWithTempTable( $this->db, 'ipb_reason', 'foo' );
  683. }
  684. /**
  685. * @dataProvider provideStages
  686. * @param int $stage
  687. */
  688. public function testInsertWithTempTableDeprecated( $stage ) {
  689. $wrap = TestingAccessWrapper::newFromClass( CommentStore::class );
  690. $wrap->formerTempTables += [ 'ipb_reason' => '1.30' ];
  691. $this->hideDeprecated( 'CommentStore::insertWithTempTable for ipb_reason' );
  692. $store = $this->makeStore( $stage );
  693. list( $fields, $callback ) = $store->insertWithTempTable( $this->db, 'ipb_reason', 'foo' );
  694. $this->assertTrue( is_callable( $callback ) );
  695. }
  696. public function testInsertTruncation() {
  697. $comment = str_repeat( '💣', 16400 );
  698. $truncated1 = str_repeat( '💣', 63 ) . '...';
  699. $truncated2 = str_repeat( '💣', CommentStore::COMMENT_CHARACTER_LIMIT - 3 ) . '...';
  700. $store = $this->makeStore( MIGRATION_WRITE_BOTH );
  701. $fields = $store->insert( $this->db, 'ipb_reason', $comment );
  702. $this->assertSame( $truncated1, $fields['ipb_reason'] );
  703. $stored = $this->db->selectField(
  704. 'comment', 'comment_text', [ 'comment_id' => $fields['ipb_reason_id'] ], __METHOD__
  705. );
  706. $this->assertSame( $truncated2, $stored );
  707. }
  708. /**
  709. * @expectedException OverflowException
  710. * @expectedExceptionMessage Comment data is too long (65611 bytes, maximum is 65535)
  711. */
  712. public function testInsertTooMuchData() {
  713. $store = $this->makeStore( MIGRATION_WRITE_BOTH );
  714. $store->insert( $this->db, 'ipb_reason', 'foo', [
  715. 'long' => str_repeat( '💣', 16400 )
  716. ] );
  717. }
  718. public function testGetStore() {
  719. $this->assertInstanceOf( CommentStore::class, CommentStore::getStore() );
  720. }
  721. public function testNewKey() {
  722. $this->hideDeprecated( 'CommentStore::newKey' );
  723. $this->assertInstanceOf( CommentStore::class, CommentStore::newKey( 'dummy' ) );
  724. }
  725. }