Completed
Push — master ( a2db4a...affb95 )
by
unknown
07:39 queued 10s
created

assertTitlesRedacted()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
cc 2
nc 2
nop 2
1
<?php
2
3
namespace Wikibase\Client\Tests\ChangeModification;
4
5
use Language;
6
use MediaWiki\MediaWikiServices;
7
use MediaWikiIntegrationTestCase;
8
use RecentChange;
9
use Title;
10
use Wikibase\Client\ChangeModification\ChangeVisibilityNotificationJob;
11
use Wikibase\Client\RecentChanges\RecentChangeFactory;
12
use Wikibase\Client\RecentChanges\SiteLinkCommentCreator;
13
use Wikibase\DataModel\Entity\EntityIdParser;
14
use Wikibase\DataModel\Entity\PropertyId;
15
use Wikibase\DataModel\Services\Diff\EntityDiffer;
16
use Wikibase\Lib\Changes\EntityChangeFactory;
17
use Wikibase\Lib\Changes\RepoRevisionIdentifier;
18
19
/**
20
 * @covers \Wikibase\Client\ChangeModification\ChangeVisibilityNotificationJob
21
 *
22
 * @group Wikibase
23
 * @group WikibaseChange
24
 *
25
 * @license GPL-2.0-or-later
26
 * @author Marius Hoch
27
 */
28
class ChangeVisibilityNotificationJobTest extends MediaWikiIntegrationTestCase {
29
30
	protected function setUp(): void {
31
		parent::setUp();
32
33
		$this->tablesUsed[] = 'recentchanges';
34
	}
35
36
	public function visibilityNotificationProvider() {
37
		return [
38
			'redact nothing' => [
39
				[],
40
				[
41
					new RepoRevisionIdentifier( 'Q404', '20111111111111', 1002, 1001 ),
0 ignored issues
show
Unused Code introduced by
The call to RepoRevisionIdentifier::__construct() has too many arguments starting with 1001.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
42
				],
43
				6,
44
			],
45
			'redact one entry with one RepoRevisionIdentifier' => [
46
				[ 'UNIQ-001' ],
47
				[
48
					new RepoRevisionIdentifier( 'Q42', '20111111111111', 1002, 1001 ),
0 ignored issues
show
Unused Code introduced by
The call to RepoRevisionIdentifier::__construct() has too many arguments starting with 1001.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
49
				],
50
				6,
51
			],
52
			'redact one entry with two RepoRevisionIdentifiers' => [
53
				[ 'UNIQ-001' ],
54
				[
55
					new RepoRevisionIdentifier( 'Q42', '20111111111111', 1002, 1001 ),
0 ignored issues
show
Unused Code introduced by
The call to RepoRevisionIdentifier::__construct() has too many arguments starting with 1001.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
56
					new RepoRevisionIdentifier( 'Q45345', '20111111111111', 1002, 1001 ),
0 ignored issues
show
Unused Code introduced by
The call to RepoRevisionIdentifier::__construct() has too many arguments starting with 1001.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
57
				],
58
				6,
59
			],
60
			'redact two entries with one RepoRevisionIdentifier' => [
61
				[ 'UNIQ-005', 'UNIQ-006' ],
62
				[
63
					new RepoRevisionIdentifier( 'Q2013', '20111111111115', 2013, 2014 ),
0 ignored issues
show
Unused Code introduced by
The call to RepoRevisionIdentifier::__construct() has too many arguments starting with 2014.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
64
				],
65
				12,
66
			],
67
			'redact multiple entries with multiple RepoRevisionIdentifiers' => [
68
				[ 'UNIQ-001', 'UNIQ-005', 'UNIQ-006' ],
69
				[
70
					new RepoRevisionIdentifier( 'Q42', '20111111111111', 1002, 1001 ),
0 ignored issues
show
Unused Code introduced by
The call to RepoRevisionIdentifier::__construct() has too many arguments starting with 1001.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
71
					new RepoRevisionIdentifier( 'Q45345', '20111111111111', 1002, 1001 ),
0 ignored issues
show
Unused Code introduced by
The call to RepoRevisionIdentifier::__construct() has too many arguments starting with 1001.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
72
					new RepoRevisionIdentifier( 'Q2013', '20111111111115', 2013, 2014 ),
0 ignored issues
show
Unused Code introduced by
The call to RepoRevisionIdentifier::__construct() has too many arguments starting with 2014.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
73
				],
74
				4,
75
			],
76
		];
77
	}
78
79
	/**
80
	 * @dataProvider visibilityNotificationProvider
81
	 */
82
	public function testRun( array $expectedRedactedTitles, array $revisionIdentifiers, $visibilityBitFlag ) {
83
		$this->initRecentChanges();
84
85
		$job = new ChangeVisibilityNotificationJob(
86
			MediaWikiServices::getInstance()->getDBLoadBalancerFactory(),
87
			MediaWikiServices::getInstance()->getMainConfig()->get( 'UpdateRowsPerQuery' ),
88
			[
89
				'revisionIdentifiersJson' => $this->revisionIdentifiersToJson( $revisionIdentifiers ),
90
				'visibilityBitFlag' => $visibilityBitFlag
91
			]
92
		);
93
		$job->run();
94
95
		$this->assertTitlesRedacted( $expectedRedactedTitles, $visibilityBitFlag );
96
		$this->assertOtherTitlesUntouched( $expectedRedactedTitles );
97
	}
98
99
	/**
100
	 * JSON encode the given RepoRevisionIdentifiers
101
	 *
102
	 * @param RepoRevisionIdentifier[] $revisionIdentifiers
103
	 *
104
	 * @return string JSON
105
	 */
106
	private function revisionIdentifiersToJson( array $revisionIdentifiers ): string {
107
		return json_encode(
108
			array_map(
109
				function ( RepoRevisionIdentifier $revisionIdentifier ) {
110
					return $revisionIdentifier->toArray();
111
				},
112
				$revisionIdentifiers
113
			)
114
		);
115
	}
116
117
	public function testRun_recentChangeFactoryRoundtrip() {
118
		$this->initRecentChanges();
119
120
		$recentChangeFactory = new RecentChangeFactory(
121
			Language::factory( 'qqx' ),
122
			$this->createMock( SiteLinkCommentCreator::class )
123
		);
124
		$entityChangeFactory = new EntityChangeFactory(
125
			$this->createMock( EntityDiffer::class ),
126
			$this->createMock( EntityIdParser::class ),
127
			[]
128
		);
129
130
		$entityChange = $entityChangeFactory->newForEntity(
131
			'blah',
132
			new PropertyId( 'P42' ),
133
			[ 'user_text' => 'a-nice-user' ]
134
		);
135
		$entityChange->setTimestamp( '20161111111111' );
136
		$entityChange->setMetadata( [
137
			'rev_id' => 342,
138
			'parent_id' => 343
139
		] );
140
		$additionalRecentChange = $recentChangeFactory->newRecentChange(
141
			$entityChange,
142
			Title::newFromText( 'UNIQ-FROM-RecentChangeFactory' )
143
		);
144
		$additionalRecentChange->save();
145
146
		$job = new ChangeVisibilityNotificationJob(
147
			MediaWikiServices::getInstance()->getDBLoadBalancerFactory(),
148
			MediaWikiServices::getInstance()->getMainConfig()->get( 'UpdateRowsPerQuery' ),
149
			[
150
				'revisionIdentifiersJson' => $this->revisionIdentifiersToJson( [
151
					new RepoRevisionIdentifier( 'P42', '20161111111111', 342, 343 ),
0 ignored issues
show
Unused Code introduced by
The call to RepoRevisionIdentifier::__construct() has too many arguments starting with 343.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
152
				] ),
153
				'visibilityBitFlag' => 16
154
			]
155
		);
156
		$job->run();
157
158
		$this->assertTitlesRedacted( [ 'UNIQ-FROM-RecentChangeFactory' ], 16 );
159
		$this->assertOtherTitlesUntouched( [ 'UNIQ-FROM-RecentChangeFactory' ] );
160
	}
161
162
	/**
163
	 * Make sure rows for titles that are in $expectedRedactedTitles correctly got changed.
164
	 */
165
	private function assertTitlesRedacted( array $expectedRedactedTitles, $visibilityBitFlag ) {
166
		if ( !$expectedRedactedTitles ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $expectedRedactedTitles of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
167
			$this->assertTrue( (bool)"Nothing to do" );
168
			return;
169
		}
170
171
		$dbr = wfGetDB( DB_REPLICA );
172
173
		$rcRedactedCount = $dbr->selectRowCount(
174
			'recentchanges',
175
			'rc_deleted',
176
			[
177
				'rc_title' => $expectedRedactedTitles,
178
				'rc_deleted' => $visibilityBitFlag
179
			],
180
			__METHOD__
181
		);
182
		$this->assertSame( count( $expectedRedactedTitles ), $rcRedactedCount, 'Missing rc_deleted changes.' );
183
	}
184
185
	/**
186
	 * Make sure rows for titles that are not in $expectedRedactedTitles didn't get changed.
187
	 */
188
	private function assertOtherTitlesUntouched( array $expectedRedactedTitles ) {
189
		$dbr = wfGetDB( DB_REPLICA );
190
191
		// Count the rows that have rc_deleted set that are not in $expectedRedactedTitles
192
		$where = [ 'rc_deleted > 0' ];
193
		if ( $expectedRedactedTitles ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $expectedRedactedTitles of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
194
			$where[] = 'rc_title NOT IN (' . $dbr->makeList( $expectedRedactedTitles ) . ')';
195
		}
196
197
		$rcFalsePositiveCount = $dbr->selectRowCount(
198
			'recentchanges',
199
			'rc_deleted',
200
			$where,
201
			__METHOD__
202
		);
203
204
		$this->assertSame( 0, $rcFalsePositiveCount, 'Unexpected rc_deleted changes.' );
205
	}
206
207
	public function testToString() {
208
		$job = new ChangeVisibilityNotificationJob(
209
			MediaWikiServices::getInstance()->getDBLoadBalancerFactory(),
210
			MediaWikiServices::getInstance()->getMainConfig()->get( 'UpdateRowsPerQuery' ),
211
			[
212
				'revisionIdentifiersJson' => $this->revisionIdentifiersToJson( [
213
					new RepoRevisionIdentifier( 'Q1', '1', 1, 1 ),
0 ignored issues
show
Unused Code introduced by
The call to RepoRevisionIdentifier::__construct() has too many arguments starting with 1.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
214
				] ),
215
				'visibilityBitFlag' => 6
216
			]
217
		);
218
219
		$this->assertRegExp( '/^ChangeVisibilityNotification/', $job->toString() );
220
	}
221
222
	private function newChange( array $changeData ) {
223
		if ( isset( $changeData['rc_params'] ) && !is_string( $changeData['rc_params'] ) ) {
224
			$changeData['rc_params'] = serialize( $changeData['rc_params'] );
225
		}
226
227
		$defaults = [
228
			'rc_id' => 0,
229
			'rc_timestamp' => '20000000000000',
230
			'rc_user' => 0,
231
			'rc_user_text' => '',
232
			'rc_namespace' => 0,
233
			'rc_title' => '',
234
			'rc_comment' => '',
235
			'rc_minor' => false,
236
			'rc_bot' => false,
237
			'rc_new' => false,
238
			'rc_cur_id' => 0,
239
			'rc_this_oldid' => 0,
240
			'rc_last_oldid' => 0,
241
			'rc_type' => RC_EXTERNAL,
242
			'rc_source' => RecentChangeFactory::SRC_WIKIBASE,
243
			'rc_patrolled' => 0,
244
			'rc_ip' => '127.0.0.1',
245
			'rc_old_len' => 0,
246
			'rc_new_len' => 0,
247
			'rc_deleted' => false,
248
			'rc_logid' => 0,
249
			'rc_log_type' => null,
250
			'rc_log_action' => '',
251
			'rc_params' => '',
252
		];
253
254
		$changeData = array_merge( $defaults, $changeData );
255
256
		// The faked-up RecentChange row needs to have the proper fields for
257
		// MediaWiki core change Ic3a434c0. And can't have them without that
258
		// patch or the ->save() in initRecentChanges() will fail.
259
		$changeData += [
260
			'rc_comment_text' => $changeData['rc_comment'],
261
			'rc_comment_data' => null,
262
		];
263
264
		$change = RecentChange::newFromRow( (object)$changeData );
265
		$change->setExtra( [
266
			'pageStatus' => 'changed'
267
		] );
268
269
		return $change;
270
	}
271
272
	/**
273
	 * Empty the recentchanges table and put some changes in there.
274
	 *
275
	 * All changes have a unique rc_title value to make them easy to identify.
276
	 */
277
	private function initRecentChanges() {
278
		wfGetDB( DB_MASTER )->delete( 'recentchanges', '*' );
279
280
		$change = $this->newChange( [
281
			'rc_timestamp' => '20111111111111',
282
			'rc_user' => 23,
283
			'rc_user_text' => 'Test',
284
			'rc_namespace' => 0,
285
			'rc_title' => 'UNIQ-001',
286
			'rc_comment' => 'Testing',
287
			'rc_type' => RC_EXTERNAL,
288
			'rc_source' => RecentChangeFactory::SRC_WIKIBASE,
289
			'rc_last_oldid' => 11,
290
			'rc_this_oldid' => 12,
291
			'rc_params' => [
292
				'wikibase-repo-change' => [
293
					'parent_id' => 1001,
294
					'rev_id' => 1002,
295
					'object_id' => 'Q42'
296
				]
297
			]
298
		] );
299
		$change->save();
300
301
		$change = $this->newChange( [
302
			'rc_timestamp' => '20111111111111',
303
			'rc_user' => 23,
304
			'rc_user_text' => 'Test',
305
			'rc_namespace' => 0,
306
			'rc_title' => 'UNIQ-002',
307
			'rc_comment' => 'Testing',
308
			'rc_type' => RC_EXTERNAL,
309
			'rc_source' => RecentChangeFactory::SRC_WIKIBASE,
310
			'rc_last_oldid' => 11,
311
			'rc_this_oldid' => 12,
312
			'rc_params' => [
313
				'wikibase-repo-change' => [
314
					'parent_id' => 1001,
315
					'rev_id' => 1002,
316
					'object_id' => 'Q24'
317
				]
318
			]
319
		] );
320
		$change->save();
321
322
		$change = $this->newChange( [
323
			'rc_timestamp' => '20121212121212',
324
			'rc_user' => 23,
325
			'rc_user_text' => 'Test',
326
			'rc_namespace' => 0,
327
			'rc_title' => 'UNIQ-003',
328
			'rc_comment' => 'Testing',
329
			'rc_type' => RC_EXTERNAL,
330
			'rc_source' => 'foo', // different source, will always be ignored
331
			'rc_last_oldid' => 11,
332
			'rc_this_oldid' => 12,
333
			'rc_params' => [
334
				'wikibase-repo-change' => [
335
					'parent_id' => 1001,
336
					'rev_id' => 1002,
337
					'object_id' => 'Q42'
338
				]
339
			]
340
		] );
341
		$change->save();
342
343
		$change = $this->newChange( [
344
			'rc_timestamp' => '20111111111111',
345
			'rc_user' => 23,
346
			'rc_user_text' => 'Test',
347
			'rc_namespace' => 0,
348
			'rc_title' => 'UNIQ-004',
349
			'rc_comment' => 'Testing',
350
			'rc_type' => RC_EXTERNAL,
351
			'rc_source' => RecentChangeFactory::SRC_WIKIBASE,
352
			'rc_last_oldid' => 11,
353
			'rc_this_oldid' => 12,
354
			'rc_params' => [
355
				'wikibase-repo-change' => [
356
					'parent_id' => 1,
357
					'rev_id' => 2,
358
					'object_id' => 'Q42'
359
				]
360
			]
361
		] );
362
		$change->save();
363
364
		$change = $this->newChange( [
365
			'rc_timestamp' => '20111111111115',
366
			'rc_user' => 23,
367
			'rc_user_text' => 'Test',
368
			'rc_namespace' => 0,
369
			'rc_title' => 'UNIQ-005',
370
			'rc_comment' => 'Testing',
371
			'rc_type' => RC_EXTERNAL,
372
			'rc_source' => RecentChangeFactory::SRC_WIKIBASE,
373
			'rc_last_oldid' => 11,
374
			'rc_this_oldid' => 12,
375
			'rc_params' => [
376
				'wikibase-repo-change' => [
377
					'parent_id' => 2014,
378
					'rev_id' => 2013,
379
					'object_id' => 'Q2013'
380
				]
381
			]
382
		] );
383
		$change->save();
384
385
		$change = $this->newChange( [
386
			'rc_timestamp' => '20111111111115',
387
			'rc_user' => 23,
388
			'rc_user_text' => 'Test',
389
			'rc_namespace' => 0,
390
			'rc_title' => 'UNIQ-006',
391
			'rc_comment' => 'Testing',
392
			'rc_type' => RC_EXTERNAL,
393
			'rc_source' => RecentChangeFactory::SRC_WIKIBASE,
394
			'rc_last_oldid' => 11,
395
			'rc_this_oldid' => 12,
396
			'rc_params' => [
397
				'wikibase-repo-change' => [
398
					'parent_id' => 2014,
399
					'rev_id' => 2013,
400
					'object_id' => 'Q2013'
401
				]
402
			]
403
		] );
404
		$change->save();
405
	}
406
407
}
408