EditEntityTest::testEditEntity()   C
last analyzed

Complexity

Conditions 17
Paths 96

Size

Total Lines 79

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 79
rs 5.2166
c 0
b 0
f 0
cc 17
nc 96
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare( strict_types = 1 );
4
5
namespace Wikibase\Repo\Tests\Api;
6
7
use ApiUsageException;
8
use MediaWiki\MediaWikiServices;
9
use ReadOnlyError;
10
use User;
11
use Wikibase\DataModel\Entity\EntityDocument;
12
use Wikibase\DataModel\Entity\Item;
13
use Wikibase\DataModel\Entity\Property;
14
use Wikibase\Repo\WikibaseRepo;
15
16
/**
17
 * @covers \Wikibase\Repo\Api\EditEntity
18
 * @covers \Wikibase\Repo\Api\ModifyEntity
19
 *
20
 * @license GPL-2.0-or-later
21
 * @author Addshore
22
 * @author Michal Lazowik
23
 *
24
 * @group API
25
 * @group Wikibase
26
 * @group WikibaseAPI
27
 * @group BreakingTheSlownessBarrier
28
 * @group Database
29
 * @group medium
30
 */
31
class EditEntityTest extends WikibaseApiTestCase {
32
33
	/**
34
	 * @var string[]
35
	 */
36
	private static $idMap;
37
38
	/**
39
	 * @var bool
40
	 */
41
	private static $hasSetup;
42
43
	protected function setUp(): void {
44
		parent::setUp();
45
46
		$wikibaseRepo = WikibaseRepo::getDefaultInstance();
47
48
		// XXX: This test doesn't mark tablesUsed so things created here will remain through all tests in the class.
49
		if ( !isset( self::$hasSetup ) ) {
50
			$store = $wikibaseRepo->getEntityStore();
51
52
			$prop = Property::newFromType( 'string' );
53
			$store->saveEntity( $prop, 'EditEntityTestP56', $this->user, EDIT_NEW );
54
			self::$idMap['%P56%'] = $prop->getId()->getSerialization();
55
			self::$idMap['%StringProp%'] = $prop->getId()->getSerialization();
56
57
			$prop = Property::newFromType( 'string' );
58
			$store->saveEntity( $prop, 'EditEntityTestP72', $this->user, EDIT_NEW );
59
			self::$idMap['%P72%'] = $prop->getId()->getSerialization();
60
61
			$this->initTestEntities( [ 'Berlin' ], self::$idMap );
62
			self::$idMap['%Berlin%'] = EntityTestHelper::getId( 'Berlin' );
63
64
			$p56 = self::$idMap['%P56%'];
65
			$berlinData = EntityTestHelper::getEntityOutput( 'Berlin' );
66
			self::$idMap['%BerlinP56%'] = $berlinData['claims'][$p56][0]['id'];
67
68
			$badge = new Item();
69
			$store->saveEntity( $badge, 'EditEntityTestQ42', $this->user, EDIT_NEW );
70
			self::$idMap['%Q42%'] = $badge->getId()->getSerialization();
71
72
			$badge = new Item();
73
			$store->saveEntity( $badge, 'EditEntityTestQ149', $this->user, EDIT_NEW );
74
			self::$idMap['%Q149%'] = $badge->getId()->getSerialization();
75
76
			$badge = new Item();
77
			$store->saveEntity( $badge, 'EditEntityTestQ32', $this->user, EDIT_NEW );
78
			self::$idMap['%Q32%'] = $badge->getId()->getSerialization();
79
		}
80
81
		$wikibaseRepo->getSettings()->setSetting( 'badgeItems', [
82
			self::$idMap['%Q42%'] => '',
83
			self::$idMap['%Q149%'] => '',
84
			'Q99999' => '', // Just in case we have a wrong config
85
		] );
86
87
		self::$hasSetup = true;
88
	}
89
90
	/**
91
	 * Provide data for a sequence of requests that will work when run in order
92
	 */
93
	public function provideData() {
94
		return [
95
			'new item' => [
96
				'p' => [ 'new' => 'item', 'data' => '{}' ],
97
				'e' => [ 'type' => 'item' ] ],
98
			'new property' => [ // make sure if we pass in a valid type it is accepted
99
				'p' => [ 'new' => 'property', 'data' => '{"datatype":"string"}' ],
100
				'e' => [ 'type' => 'property' ] ],
101
			'new property with data' => [ // this is our current example in the api doc
102
				'p' => [
103
					'new' => 'property',
104
					'data' => '{"labels":{"en-gb":{"language":"en-gb","value":"Propertylabel"}},'
105
						. '"descriptions":{"en-gb":{"language":"en-gb","value":"Propertydescription"}},'
106
						. '"datatype":"string"}'
107
				],
108
				'e' => [ 'type' => 'property' ] ],
109
			'add a sitelink..' => [ // make sure if we pass in a valid id it is accepted
110
				'p' => [
111
					'data' => '{"sitelinks":{"dewiki":{"site":"dewiki",'
112
						. '"title":"TestPage!","badges":["%Q42%","%Q149%"]}}}'
113
				],
114
				'e' => [
115
					'sitelinks' => [
116
						[
117
							'site' => 'dewiki',
118
							'title' => 'TestPage!',
119
							'badges' => [ '%Q42%', '%Q149%' ]
120
						]
121
					]
122
				]
123
			],
124
			'add a label, (making sure some data fields are ignored)' => [
125
				'p' => [
126
					'data' => [
127
						'labels' => [ 'en' => [ 'language' => 'en', 'value' => 'A Label' ] ],
128
						'length' => 'ignoreme!',
129
						'count' => 'ignoreme!',
130
						'touched' => 'ignoreme!',
131
						'modified' => 'ignoreme!',
132
					],
133
				],
134
				'e' => [
135
					'sitelinks' => [
136
						[
137
							'site' => 'dewiki',
138
							'title' => 'TestPage!',
139
							'badges' => [ '%Q42%', '%Q149%' ]
140
						]
141
					],
142
					'labels' => [ 'en' => 'A Label' ]
143
				]
144
			],
145
			'add a description..' => [
146
				'p' => [ 'data' => '{"descriptions":{"en":{"language":"en","value":"DESC"}}}' ],
147
				'e' => [
148
					'sitelinks' => [
149
						[
150
							'site' => 'dewiki',
151
							'title' => 'TestPage!',
152
							'badges' => [ '%Q42%', '%Q149%' ]
153
						]
154
					],
155
					'labels' => [ 'en' => 'A Label' ],
156
					'descriptions' => [ 'en' => 'DESC' ]
157
				]
158
			],
159
			'remove a sitelink..' => [
160
				'p' => [ 'data' => '{"sitelinks":{"dewiki":{"site":"dewiki","title":""}}}' ],
161
				'e' => [
162
					'labels' => [ 'en' => 'A Label' ],
163
					'descriptions' => [ 'en' => 'DESC' ] ]
164
			],
165
			'remove a label..' => [
166
				'p' => [ 'data' => '{"labels":{"en":{"language":"en","value":""}}}' ],
167
				'e' => [ 'descriptions' => [ 'en' => 'DESC' ] ] ],
168
			'remove a description..' => [
169
				'p' => [ 'data' => '{"descriptions":{"en":{"language":"en","value":""}}}' ],
170
				'e' => [ 'type' => 'item' ] ],
171
			'clear an item with some new value' => [
172
				'p' => [
173
					'data' => '{"sitelinks":{"dewiki":{"site":"dewiki","title":"page"}}}',
174
					'clear' => ''
175
				],
176
				'e' => [
177
					'type' => 'item',
178
					'sitelinks' => [
179
						[
180
							'site' => 'dewiki',
181
							'title' => 'Page',
182
							'badges' => []
183
						]
184
					]
185
				]
186
			],
187
			'clear an item with no value' => [
188
				'p' => [ 'data' => '{}', 'clear' => '' ],
189
				'e' => [ 'type' => 'item' ] ],
190
			'add 2 labels' => [
191
				'p' => [ 'data' => '{"labels":{"en":{"language":"en","value":"A Label"},'
192
					. '"sv":{"language":"sv","value":"SVLabel"}}}' ],
193
				'e' => [ 'labels' => [ 'en' => 'A Label', 'sv' => 'SVLabel' ] ] ],
194
			'remove a label with remove' => [
195
				'p' => [ 'data' => '{"labels":{"en":{"language":"en","remove":true}}}' ],
196
				'e' => [ 'labels' => [ 'sv' => 'SVLabel' ] ] ],
197
			'override and add 2 descriptions' => [
198
				'p' => [ 'clear' => '', 'data' => '{"descriptions":{'
199
					. '"en":{"language":"en","value":"DESC1"},'
200
					. '"de":{"language":"de","value":"DESC2"}}}' ],
201
				'e' => [ 'descriptions' => [ 'en' => 'DESC1', 'de' => 'DESC2' ] ] ],
202
			'remove a description with remove' => [
203
				'p' => [ 'data' => '{"descriptions":{"en":{"language":"en","remove":true}}}' ],
204
				'e' => [ 'descriptions' => [ 'de' => 'DESC2' ] ] ],
205
			'override and add 2 sitelinks..' => [
206
				'p' => [ 'data' => '{"sitelinks":{'
207
					. '"dewiki":{"site":"dewiki","title":"BAA"},'
208
					. '"svwiki":{"site":"svwiki","title":"FOO"}}}' ],
209
				'e' => [
210
					'type' => 'item',
211
					'sitelinks' => [
212
						[
213
							'site' => 'dewiki',
214
							'title' => 'BAA',
215
							'badges' => []
216
						],
217
						[
218
							'site' => 'svwiki',
219
							'title' => 'FOO',
220
							'badges' => []
221
						]
222
					]
223
				]
224
			],
225
			'unset a sitelink using the other sitelink' => [
226
				'p' => [
227
					'site' => 'svwiki',
228
					'title' => 'FOO',
229
					'data' => '{"sitelinks":{"dewiki":{"site":"dewiki","title":""}}}'
230
				],
231
				'e' => [
232
					'type' => 'item',
233
					'sitelinks' => [
234
						[
235
							'site' => 'svwiki',
236
							'title' => 'FOO',
237
							'badges' => []
238
						]
239
					]
240
				]
241
			],
242
			'set badges for a existing sitelink, title intact' => [
243
				'p' => [
244
					'data' => '{"sitelinks":{"svwiki":{"site":"svwiki","badges":["%Q149%","%Q42%"]}}}'
245
				],
246
				'e' => [
247
					'type' => 'item',
248
					'sitelinks' => [
249
						[
250
							'site' => 'svwiki',
251
							'title' => 'FOO',
252
							'badges' => [ "%Q149%", "%Q42%" ]
253
						]
254
					]
255
				]
256
			],
257
			'set title for a existing sitelink, badges intact' => [
258
				'p' => [ 'data' => '{"sitelinks":{"svwiki":{"site":"svwiki","title":"FOO2"}}}' ],
259
				'e' => [
260
					'type' => 'item',
261
					'sitelinks' => [
262
						[
263
							'site' => 'svwiki',
264
							'title' => 'FOO2',
265
							'badges' => [ "%Q149%", "%Q42%" ]
266
						]
267
					]
268
				]
269
			],
270
			'delete sitelink by providing neither title nor badges' => [
271
				'p' => [ 'data' => '{"sitelinks":{"svwiki":{"site":"svwiki"}}}' ],
272
				'e' => [
273
					'type' => 'item',
274
				]
275
			],
276
			'add a claim' => [
277
				'p' => [ 'data' => '{"claims":[{"mainsnak":{"snaktype":"value",'
278
					. '"property":"%P56%","datavalue":{"value":"imastring","type":"string"}},'
279
					. '"type":"statement","rank":"normal"}]}' ],
280
				'e' => [ 'claims' => [
281
					'%P56%' => [
282
						'mainsnak' => [
283
							'snaktype' => 'value',
284
							'property' => '%P56%',
285
							'datavalue' => [ 'value' => 'imastring', 'type' => 'string' ]
286
						],
287
						'type' => 'statement',
288
						'rank' => 'normal'
289
					]
290
				] ]
291
			],
292
			'change the claim' => [
293
				'p' => [ 'data' => [
294
					'claims' => [
295
						[
296
							'id' => '%lastClaimId%',
297
							'mainsnak' => [
298
								'snaktype' => 'value',
299
								'property' => '%P56%',
300
								'datavalue' => [
301
									'value' => 'diffstring',
302
									'type' => 'string'
303
								],
304
							],
305
							'type' => 'statement',
306
							'rank' => 'normal',
307
						],
308
					],
309
				] ],
310
				'e' => [ 'claims' => [
311
					'%P56%' => [
312
						'mainsnak' => [ 'snaktype' => 'value', 'property' => '%P56%',
313
							'datavalue' => [
314
								'value' => 'diffstring',
315
								'type' => 'string' ] ],
316
						'type' => 'statement',
317
						'rank' => 'normal'
318
					]
319
				] ]
320
			],
321
			'remove the claim' => [
322
				'p' => [ 'data' => '{"claims":[{"id":"%lastClaimId%","remove":""}]}' ],
323
				'e' => [ 'claims' => [] ]
324
			],
325
			'add multiple claims' => [
326
				'p' => [ 'data' => '{"claims":['
327
					. '{"mainsnak":{"snaktype":"value","property":"%P56%","datavalue":'
328
					. '{"value":"imastring1","type":"string"}},"type":"statement","rank":"normal"},'
329
					. '{"mainsnak":{"snaktype":"value","property":"%P56%","datavalue":'
330
					. '{"value":"imastring2","type":"string"}},"type":"statement","rank":"normal"}'
331
					. ']}' ],
332
				'e' => [ 'claims' => [
333
					[
334
						'mainsnak' => [
335
							'snaktype' => 'value', 'property' => '%P56%',
336
							'datavalue' => [
337
								'value' => 'imastring1',
338
								'type' => 'string' ] ],
339
						'type' => 'statement',
340
						'rank' => 'normal' ],
341
					[
342
						'mainsnak' => [
343
							'snaktype' => 'value', 'property' => '%P56%',
344
							'datavalue' => [
345
								'value' => 'imastring2',
346
								'type' => 'string' ] ],
347
						'type' => 'statement',
348
						'rank' => 'normal' ]
349
				] ],
350
			],
351
			'remove all stuff' => [
352
				'p' => [ 'clear' => '', 'data' => '{}' ],
353
				'e' => [
354
					'labels' => [],
355
					'descriptions' => [],
356
					'aliases' => [],
357
					'sitelinks' => [],
358
					'claims' => []
359
				]
360
			],
361
			'add lots of data again' => [
362
				'p' => [ 'data' => '{"claims":['
363
					. '{"mainsnak":{"snaktype":"value","property":"%P56%","datavalue":'
364
					. '{"value":"imastring1","type":"string"}},"type":"statement","rank":"normal"},'
365
					. '{"mainsnak":{"snaktype":"value","property":"%P56%","datavalue":'
366
					. '{"value":"imastring2","type":"string"}},"type":"statement","rank":"normal"}'
367
					. '],'
368
					. '"sitelinks":{"dewiki":{"site":"dewiki","title":"page"}},'
369
					. '"labels":{"en":{"language":"en","value":"A Label"}},'
370
					. '"descriptions":{"en":{"language":"en","value":"A description"}}}' ],
371
				'e' => [ 'type' => 'item' ]
372
			],
373
			'make a null edit' => [
374
				'p' => [ 'data' => '{}' ],
375
				'e' => [ 'nochange' => '' ]
376
			],
377
			'remove all stuff in another way' => [
378
				'p' => [ 'clear' => true, 'data' => '{}' ],
379
				'e' => [
380
					'labels' => [],
381
					'descriptions' => [],
382
					'aliases' => [],
383
					'sitelinks' => [],
384
					'claims' => []
385
				]
386
			],
387
		];
388
	}
389
390
	/**
391
	 * Applies self::$idMap to all data in the given data structure, recursively.
392
	 *
393
	 * @param mixed &$data
394
	 */
395
	protected function injectIds( &$data ) {
396
		EntityTestHelper::injectIds( $data, self::$idMap );
397
	}
398
399
	/**
400
	 * Skips a test of the given entity type is not enabled.
401
	 *
402
	 * @param string|null $requiredEntityType
403
	 */
404
	private function skipIfEntityTypeNotKnown( $requiredEntityType ) {
405
		if ( $requiredEntityType === null ) {
406
			return;
407
		}
408
409
		$enabledTypes = WikibaseRepo::getDefaultInstance()->getLocalEntityTypes();
410
		if ( !in_array( $requiredEntityType, $enabledTypes ) ) {
411
			$this->markTestSkipped( 'Entity type not enabled: ' . $requiredEntityType );
412
		}
413
	}
414
415
	public function testUserCanEditWhenTheyHaveSufficientPermission() {
416
		$userWithAllPermissions = $this->createUserWithGroup( 'all-permission' );
417
418
		$this->setMwGlobals( 'wgGroupPermissions', [
419
			'all-permission' => [ 'read' => true, 'edit' => true, 'item-term' => true, 'createpage' => true ],
420
			'*' => [ 'read' => true, 'edit' => false, 'writeapi' => true ]
421
		] );
422
423
		$newItem = $this->createItemUsing( $userWithAllPermissions );
424
		$this->assertArrayHasKey( 'id', $newItem );
425
	}
426
427
	public function testUserCannotEditWhenTheyLackPermission() {
428
		$userWithInsufficientPermissions = $this->createUserWithGroup( 'no-permission' );
429
		$userWithAllPermissions = $this->createUserWithGroup( 'all-permission' );
430
431
		$this->setMwGlobals( 'wgGroupPermissions', [
432
			'no-permission' => [ 'read' => true, 'edit' => false ],
433
			'all-permission' => [ 'read' => true, 'edit' => true, 'item-term' => true, 'createpage' => true ],
434
			'*' => [ 'read' => true, 'edit' => false, 'writeapi' => true ]
435
		] );
436
437
		MediaWikiServices::getInstance()->resetServiceForTesting( 'PermissionManager' );
438
439
		// And an existing item
440
		$newItem = $this->createItemUsing( $userWithAllPermissions );
441
442
		// Then the request is denied
443
		$expected = [
444
			'type' => ApiUsageException::class,
445
			'code' => 'permissiondenied'
446
		];
447
448
		MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache(
449
			$userWithAllPermissions
450
		);
451
		MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache(
452
			$userWithInsufficientPermissions
453
		);
454
455
		$this->doTestQueryExceptions(
456
			$this->addSiteLink( $newItem['id'] ),
457
			$expected,
458
			$userWithInsufficientPermissions
459
		);
460
	}
461
462
	public function testEditingLabelRequiresEntityTermEditPermissions() {
463
		$userWithInsufficientPermissions = $this->createUserWithGroup( 'no-permission' );
464
		$userWithAllPermissions = $this->createUserWithGroup( 'all-permission' );
465
466
		$this->setMwGlobals( 'wgGroupPermissions', [
467
			'no-permission' => [ 'read' => true, 'edit' => true, 'item-term' => false, ],
468
			'all-permission' => [ 'read' => true, 'edit' => true, 'item-term' => true, 'createpage' => true ],
469
			'*' => [ 'read' => true, 'edit' => false, 'writeapi' => true ]
470
		] );
471
472
		MediaWikiServices::getInstance()->resetServiceForTesting( 'PermissionManager' );
473
474
		// And an existing item
475
		$newItem = $this->createItemUsing( $userWithAllPermissions );
476
477
		// Then the request is denied
478
		$expected = [
479
			'type' => ApiUsageException::class,
480
			'code' => 'permissiondenied'
481
		];
482
483
		MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache(
484
			$userWithAllPermissions
485
		);
486
		MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache(
487
			$userWithInsufficientPermissions
488
		);
489
490
		$this->doTestQueryExceptions(
491
			$this->removeLabel( $newItem['id'] ),
492
			$expected,
493
			$userWithInsufficientPermissions );
494
	}
495
496
	private function createItemUsing( User $user ) {
497
		$createItemParams = [ 'action' => 'wbeditentity',
498
			'new' => 'item',
499
			'data' =>
500
				'{"labels":{"en":{"language":"en","value":"something"}}}' ];
501
		list( $result, ) = $this->doApiRequestWithToken( $createItemParams, null, $user );
502
		return $result['entity'];
503
	}
504
505
	/**
506
	 * @param string $groupName
507
	 *
508
	 * @return User
509
	 */
510
	private function createUserWithGroup( $groupName ) {
511
		return $this->getTestUser( [ 'wbeditor', $groupName ] )->getUser();
512
	}
513
514
	private function addSiteLink( $id ) {
515
		return [
516
			'action' => 'wbeditentity',
517
			'id' => $id,
518
			'data' => '{"sitelinks":{"enwiki":{"site":"enwiki","title":"Hello World"}}}'
519
		];
520
	}
521
522
	private function removeLabel( $id ) {
523
		return [
524
			'action' => 'wbeditentity',
525
			'id' => $id,
526
			'data' => '{"labels":{"en":{"language":"en","value":""}}}'
527
		];
528
	}
529
530
	/**
531
	 * @dataProvider provideData
532
	 */
533
	public function testEditEntity( $params, $expected, $needed = null ) {
534
		$this->skipIfEntityTypeNotKnown( $needed );
535
536
		$this->injectIds( $params );
537
		$this->injectIds( $expected );
538
539
		$p56 = '%P56%';
540
		$this->injectIds( $p56 );
541
542
		if ( isset( $params['data'] ) && is_array( $params['data'] ) ) {
543
			$params['data'] = json_encode( $params['data'] );
544
		}
545
546
		// -- set any defaults ------------------------------------
547
		$params['action'] = 'wbeditentity';
548
		if ( !array_key_exists( 'id', $params )
549
			&& !array_key_exists( 'new', $params )
550
			&& !array_key_exists( 'site', $params )
551
			&& !array_key_exists( 'title', $params )
552
		) {
553
			$params['id'] = self::$idMap['!lastEntityId!'];
554
		}
555
556
		// -- do the request --------------------------------------------------
557
		list( $result, , ) = $this->doApiRequestWithToken( $params );
558
559
		// -- steal ids for later tests -------------------------------------
560
		if ( array_key_exists( 'new', $params ) && stristr( $params['new'], 'item' ) ) {
561
			self::$idMap['!lastEntityId!'] = $result['entity']['id'];
562
		}
563
		if ( array_key_exists( 'claims', $result['entity'] )
564
			&& array_key_exists( $p56, $result['entity']['claims'] )
565
		) {
566
			foreach ( $result['entity']['claims'][$p56] as $claim ) {
567
				if ( array_key_exists( 'id', $claim ) ) {
568
					self::$idMap['%lastClaimId%'] = $claim['id'];
569
				}
570
			}
571
		}
572
573
		// -- check the result ------------------------------------------------
574
		$this->assertArrayHasKey( 'success', $result, "Missing 'success' marker in response." );
575
		$this->assertResultHasEntityType( $result );
576
		$this->assertArrayHasKey( 'entity', $result, "Missing 'entity' section in response." );
577
578
		$this->assertArrayHasKey(
579
			'id',
580
			$result['entity'],
581
			"Missing 'id' section in entity in response."
582
		);
583
584
		$this->assertEntityEquals( $expected, $result['entity'] );
585
586
		// -- check null edits ---------------------------------------------
587
		if ( isset( $expected['nochange'] ) ) {
588
			$this->assertArrayHasKey( 'nochange', $result['entity'] );
589
		}
590
591
		// -- check the item in the database -------------------------------
592
		$dbEntity = $this->loadEntity( $result['entity']['id'] );
593
		$this->assertEntityEquals( $expected, $dbEntity, false );
594
595
		// -- check the edit summary --------------------------------------------
596
		if ( !array_key_exists( 'warning', $expected )
597
			|| $expected['warning'] != 'edit-no-change'
598
		) {
599
			$this->assertRevisionSummary(
600
				[ 'wbeditentity' ],
601
				$result['entity']['lastrevid']
602
			);
603
604
			if ( array_key_exists( 'summary', $params ) ) {
605
				$this->assertRevisionSummary(
606
					'/' . $params['summary'] . '/',
607
					$result['entity']['lastrevid']
608
				);
609
			}
610
		}
611
	}
612
613
	public function provideItemIdParamsAndExpectedSummaryPatternForEditEntity() {
614
		return [
615
			'no languages changed' => [
616
				[
617
					'action' => 'wbeditentity',
618
					'data' => json_encode( [
619
						'labels' => [],
620
						'descriptions' => [],
621
						'aliases' => [],
622
						'sitelinks' => [
623
							[
624
								'site' => 'dewiki',
625
								'title' => 'Page',
626
								'badges' => []
627
							]
628
						]
629
					] )
630
				],
631
				preg_quote( '/* wbeditentity-update:0| */' )
632
			],
633
			'only one language changed, no other parts changed' => [
634
				[
635
					'action' => 'wbeditentity',
636
					'data' => json_encode( [
637
						'labels' => [ 'en' => [ 'language' => 'en', 'value' => 'Foo' ] ],
638
						'descriptions' => [],
639
						'aliases' => []
640
					] )
641
				],
642
				preg_quote( '/* wbeditentity-update-languages-short:0||en */' )
643
			],
644
			'multiple languages changed, no other parts changed' => [
645
				[
646
					'action' => 'wbeditentity',
647
					'data' => json_encode( [
648
						'labels' => [ 'en' => [ 'language' => 'en', 'value' => 'Foo' ] ],
649
						'descriptions' => [ 'de' => [ 'language' => 'de', 'value' => 'Bar' ] ],
650
						'aliases' => [ 'es' => [ [ 'language' => 'es', 'value' => 'ooF' ], [ 'language' => 'es', 'value' => 'raB' ] ] ]
651
					] )
652
				],
653
				preg_quote( '/* wbeditentity-update-languages-short:0||en, de, es */' )
654
			],
655
			'some languages changed and other parts changed' => [
656
				[
657
					'action' => 'wbeditentity',
658
					'data' => json_encode( [
659
						'labels' => [ 'en' => [ 'language' => 'en', 'value' => 'Foo' ] ],
660
						'descriptions' => [ 'de' => [ 'language' => 'de', 'value' => 'Bar' ] ],
661
						'aliases' => [ 'es' => [ [ 'language' => 'es', 'value' => 'ooF' ], [ 'language' => 'es', 'value' => 'raB' ] ] ],
662
						'sitelinks' => [
663
							[
664
								'site' => 'dewiki',
665
								'title' => 'Some Page',
666
								'badges' => []
667
							]
668
						]
669
					] ),
670
				],
671
				preg_quote( '/* wbeditentity-update-languages-and-other-short:0||en, de, es */' )
672
			],
673
			'more than 50 languages changed' => [
674
				[
675
					'action' => 'wbeditentity',
676
					'data' => json_encode( [
677
						'labels' => [ 'en' => [ 'language' => 'en', 'value' => 'Foo' ] ],
678
						'descriptions' => [ 'de' => [ 'language' => 'de', 'value' => 'Bar' ] ],
679
						'aliases' => $this->generateLanguageValuePairs( 50 )
680
					] ),
681
				],
682
				preg_quote( '/* wbeditentity-update-languages:0||52 */' )
683
			],
684
			'more than 50 languages changed and other parts changed' => [
685
				[
686
					'action' => 'wbeditentity',
687
					'data' => json_encode( [
688
						'labels' => [ 'en' => [ 'language' => 'en', 'value' => 'Foo' ] ],
689
						'descriptions' => [ 'de' => [ 'language' => 'de', 'value' => 'Bar' ] ],
690
						'aliases' => $this->generateLanguageValuePairs( 50 ),
691
						'sitelinks' => [
692
							[
693
								'site' => 'dewiki',
694
								'title' => 'Some other Page',
695
								'badges' => []
696
							]
697
						]
698
					] ),
699
				],
700
				preg_quote( '/* wbeditentity-update-languages-and-other:0||52 */' )
701
			]
702
		];
703
	}
704
705
	/**
706
	 * @dataProvider provideItemIdParamsAndExpectedSummaryPatternForEditEntity
707
	 */
708
	public function testEditEntity_producesCorrectSummary( $params, $expectedSummaryPattern ) {
709
		// Saving entity couldn't be done in the provider because there the
710
		// test database setup has not been done yet
711
		$item = new Item();
712
		$this->saveEntity( $item );
713
		$params['id'] = $item->getId()->getSerialization();
714
715
		list( $result ) = $this->doApiRequestWithToken( $params );
716
717
		$this->assertRevisionSummary(
718
			$expectedSummaryPattern,
719
			$result['entity']['lastrevid']
720
		);
721
	}
722
723
	private function generateLanguageValuePairs( $langCount ) {
724
		$result = [];
725
		$langCodes = WikibaseRepo::getDefaultInstance()->getTermsLanguages()->getLanguages();
726
727
		for ( $langCount = min( $langCount, ( count( $langCodes ) ) ); $langCount > 0; $langCount-- ) {
728
			$result[ $langCodes[ $langCount ] ] = [ 'language' => $langCodes[ $langCount ], 'value' => "Foo${langCount}" ];
729
		}
730
		return $result;
731
	}
732
733
	protected function saveEntity( EntityDocument $entity ) {
734
		$this->getEntityStore()->saveEntity(
735
			$entity,
736
			static::class,
737
			$this->getTestUser()->getUser(),
738
			EDIT_NEW
739
		);
740
	}
741
742
	protected function getEntityStore() {
743
		return WikibaseRepo::getDefaultInstance()->getEntityStore();
744
	}
745
746
	/**
747
	 * Provide data for requests that will fail with a set exception, code and message
748
	 */
749
	public function provideExceptionData() {
750
		return [
751
			'no entity id given' => [
752
				'p' => [ 'data' => '{}' ],
753
				'e' => [ 'exception' => [
754
					'type' => ApiUsageException::class,
755
					'code' => 'param-illegal'
756
				] ] ],
757
			'empty entity id given' => [
758
				'p' => [ 'id' => '', 'data' => '{}' ],
759
				'e' => [ 'exception' => [
760
					'type' => ApiUsageException::class,
761
					'code' => 'invalid-entity-id'
762
				] ] ],
763
			'invalid id' => [
764
				'p' => [ 'id' => 'abcde', 'data' => '{}' ],
765
				'e' => [ 'exception' => [
766
					'type' => ApiUsageException::class,
767
					'code' => 'invalid-entity-id'
768
				] ] ],
769
			'unknown id' => [
770
				'p' => [ 'id' => 'Q1234567', 'data' => '{}' ],
771
				'e' => [ 'exception' => [
772
					'type' => ApiUsageException::class,
773
					'code' => 'no-such-entity'
774
				] ] ],
775
			'invalid explicit id' => [
776
				'p' => [ 'id' => '1234', 'data' => '{}' ],
777
				'e' => [ 'exception' => [
778
					'type' => ApiUsageException::class,
779
					'code' => 'invalid-entity-id'
780
				] ] ],
781
			'non existent sitelink' => [
782
				'p' => [ 'site' => 'dewiki','title' => 'NonExistent', 'data' => '{}' ],
783
				'e' => [ 'exception' => [
784
					'type' => ApiUsageException::class,
785
					'code' => 'no-such-entity-link'
786
				] ] ],
787
			'missing site (also bad title)' => [
788
				'p' => [ 'title' => 'abcde', 'data' => '{}' ],
789
				'e' => [ 'exception' => [
790
					'type' => ApiUsageException::class,
791
					'code' => 'param-missing'
792
				] ] ],
793
			'missing site but id given' => [
794
				'p' => [ 'title' => 'abcde', 'id' => 'Q12', 'data' => '{}' ],
795
				'e' => [ 'exception' => [
796
					'type' => ApiUsageException::class,
797
					'code' => 'param-missing'
798
				] ] ],
799
			'cant have id and new' => [
800
				'p' => [ 'id' => 'q666', 'new' => 'item', 'data' => '{}' ],
801
				'e' => [ 'exception' => [
802
					'type' => ApiUsageException::class,
803
					'code' => 'param-illegal',
804
					'message' => 'Either provide the item "id" or pairs of "site" and "title" or a "new" type for an entity',
805
				] ] ],
806
			'when clearing must also have data!' => [
807
				'p' => [ 'site' => 'enwiki', 'title' => 'Berlin', 'clear' => '' ],
808
				'e' => [ 'exception' => [
809
					'type' => ApiUsageException::class,
810
					'code' => $this->logicalOr(
811
						$this->equalTo( 'nodata' ),
812
						$this->equalTo( 'missingparam' )
813
					)
814
				] ] ],
815
			'bad site' => [
816
				'p' => [ 'site' => 'abcde', 'data' => '{}' ],
817
				'e' => [ 'exception' => [
818
					'type' => ApiUsageException::class,
819
					'code' => $this->logicalOr(
820
						$this->equalTo( 'unknown_site' ),
821
						$this->equalTo( 'badvalue' )
822
					)
823
				] ] ],
824
			'no data provided' => [
825
				'p' => [ 'site' => 'enwiki', 'title' => 'Berlin' ],
826
				'e' => [ 'exception' => [
827
					'type' => ApiUsageException::class,
828
					'code' => $this->logicalOr(
829
						$this->equalTo( 'nodata' ), // see 'no$1' in ApiBase::$messageMap
830
						$this->equalTo( 'missingparam' )
831
					)
832
				] ]
833
			],
834
			'malformed json' => [
835
				'p' => [ 'site' => 'enwiki', 'title' => 'Berlin', 'data' => '{{{}' ],
836
				'e' => [ 'exception' => [
837
					'type' => ApiUsageException::class,
838
					'code' => 'invalid-json'
839
				] ] ],
840
			'must be a json object (json_decode s this an an int)' => [
841
				'p' => [ 'site' => 'enwiki', 'title' => 'Berlin', 'data' => '1234' ],
842
				'e' => [ 'exception' => [
843
					'type' => ApiUsageException::class,
844
					'code' => 'not-recognized-array'
845
				] ] ],
846
			'must be a json object (json_decode s this an an indexed array)' => [
847
				'p' => [ 'site' => 'enwiki', 'title' => 'Berlin', 'data' => '[ "xyz" ]' ],
848
				'e' => [ 'exception' => [
849
					'type' => ApiUsageException::class,
850
					'code' => 'not-recognized-string'
851
				] ] ],
852
			'must be a json object (json_decode s this an a string)' => [
853
				'p' => [ 'site' => 'enwiki', 'title' => 'Berlin', 'data' => '"string"' ],
854
				'e' => [ 'exception' => [
855
					'type' => ApiUsageException::class,
856
					'code' => 'not-recognized-array'
857
				] ] ],
858
			'inconsistent site in json' => [
859
				'p' => [
860
					'site' => 'enwiki',
861
					'title' => 'Berlin',
862
					'data' => '{"sitelinks":{"ptwiki":{"site":"svwiki","title":"TestPage!"}}}'
863
				],
864
				'e' => [ 'exception' => [
865
					'type' => ApiUsageException::class,
866
					'code' => 'inconsistent-site'
867
				] ] ],
868
			'inconsistent lang in json' => [
869
				'p' => [
870
					'site' => 'enwiki',
871
					'title' => 'Berlin',
872
					'data' => '{"labels":{"de":{"language":"pt","value":"TestPage!"}}}'
873
				],
874
				'e' => [ 'exception' => [
875
					'type' => ApiUsageException::class,
876
					'code' => 'inconsistent-language'
877
				] ] ],
878
			'inconsistent unknown site in json' => [
879
				'p' => [
880
					'site' => 'enwiki',
881
					'title' => 'Berlin',
882
					'data' => '{"sitelinks":{"BLUB":{"site":"BLUB","title":"TestPage!"}}}'
883
				],
884
				'e' => [ 'exception' => [
885
					'type' => ApiUsageException::class,
886
					'code' => 'not-recognized-site'
887
				] ] ],
888
			'inconsistent unknown languages' => [
889
				'p' => [
890
					'site' => 'enwiki',
891
					'title' => 'Berlin',
892
					'data' => '{"labels":{"BLUB":{"language":"BLUB","value":"ImaLabel"}}}'
893
				],
894
				'e' => [ 'exception' => [
895
					'type' => ApiUsageException::class,
896
					'code' => 'not-recognized-language'
897
				] ] ],
898
			// @todo the error codes in the overly long string tests make no sense
899
			// and should be corrected...
900
			'overly long label' => [
901
				'p' => [
902
					'site' => 'enwiki',
903
					'title' => 'Berlin',
904
					'data' => '{"labels":{"en":{"language":"en","value":"'
905
						. TermTestHelper::makeOverlyLongString() . '"}}}'
906
				],
907
				'e' => [ 'exception' => [ 'type' => ApiUsageException::class ] ] ],
908
			'overly long description' => [
909
				'p' => [
910
					'site' => 'enwiki',
911
					'title' => 'Berlin',
912
					'data' => '{"descriptions":{"en":{"language":"en","value":"'
913
						. TermTestHelper::makeOverlyLongString() . '"}}}'
914
				],
915
				'e' => [ 'exception' => [ 'type' => ApiUsageException::class ] ] ],
916
			'missing language in labels (T54731)' => [
917
				'p' => [
918
					'site' => 'enwiki',
919
					'title' => 'Berlin',
920
					'data' => '{"labels":{"de":{"site":"pt","title":"TestString"}}}'
921
				],
922
				'e' => [ 'exception' => [
923
					'type' => ApiUsageException::class,
924
					'code' => 'missing-language',
925
					'message' => '\'language\' was not found in term serialization for de'
926
				] ]
927
			],
928
			'removing invalid claim fails' => [
929
				'p' => [
930
					'site' => 'enwiki',
931
					'title' => 'Berlin',
932
					'data' => '{"claims":[{"remove":""}]}'
933
				],
934
				'e' => [ 'exception' => [
935
					'type' => ApiUsageException::class,
936
					'code' => 'invalid-claim',
937
					'message' => 'Cannot remove a claim with no GUID'
938
				] ]
939
			],
940
			'invalid entity ID in data value' => [
941
				'p' => [
942
					'id' => '%Berlin%',
943
					'data' => '{ "claims": [ {
944
						"mainsnak": { "snaktype": "novalue", "property": "P0" },
945
						"type": "statement"
946
					} ] }'
947
				],
948
				'e' => [ 'exception' => [
949
					'type' => ApiUsageException::class,
950
					'code' => 'invalid-claim',
951
					'message' => '\'P0\' is not a valid'
952
				] ]
953
			],
954
			'invalid statement GUID' => [
955
				'p' => [
956
					'id' => '%Berlin%',
957
					'data' => '{ "claims": [ {
958
						"id": "Q0$GUID",
959
						"mainsnak": { "snaktype": "novalue", "property": "%P56%" },
960
						"type": "statement"
961
					} ] }'
962
				],
963
				'e' => [ 'exception' => [
964
					'type' => ApiUsageException::class,
965
					'code' => 'modification-failed',
966
					'message' => 'Statement GUID can not be parsed',
967
				] ]
968
			],
969
			'removing valid claim with no guid fails' => [
970
				'p' => [
971
					'site' => 'enwiki',
972
					'title' => 'Berlin',
973
					'data' => '{
974
						"claims": [ {
975
							"remove": "",
976
							"mainsnak": {
977
								"snaktype": "value",
978
								"property": "%P56%",
979
								"datavalue": { "value": "imastring", "type": "string" }
980
							},
981
							"type": "statement",
982
							"rank": "normal"
983
						} ]
984
					}'
985
				],
986
				'e' => [ 'exception' => [
987
					'type' => ApiUsageException::class,
988
					'code' => 'invalid-claim',
989
				] ]
990
			],
991
			'bad badge id' => [
992
				'p' => [
993
					'site' => 'enwiki',
994
					'title' => 'Berlin',
995
					'data' => '{"sitelinks":{"dewiki":{"site":"dewiki","title":"TestPage!",'
996
						. '"badges":["abc","%Q149%"]}}}'
997
				],
998
				'e' => [ 'exception' => [
999
					'type' => ApiUsageException::class,
1000
					'code' => 'invalid-entity-id'
1001
				] ]
1002
			],
1003
			'badge id is not an item id' => [
1004
				'p' => [
1005
					'site' => 'enwiki',
1006
					'title' => 'Berlin',
1007
					'data' => '{"sitelinks":{"dewiki":{"site":"dewiki","title":"TestPage!",'
1008
						. '"badges":["P2","%Q149%"]}}}'
1009
				],
1010
				'e' => [ 'exception' => [
1011
					'type' => ApiUsageException::class,
1012
					'code' => 'invalid-entity-id'
1013
				] ]
1014
			],
1015
			'badge id is not specified' => [
1016
				'p' => [
1017
					'site' => 'enwiki',
1018
					'title' => 'Berlin',
1019
					'data' => '{"sitelinks":{"dewiki":{"site":"dewiki","title":"TestPage!",'
1020
						. '"badges":["%Q149%","%Q32%"]}}}'
1021
				],
1022
				'e' => [ 'exception' => [
1023
					'type' => ApiUsageException::class,
1024
					'code' => 'not-badge'
1025
				] ]
1026
			],
1027
			'badge item does not exist' => [
1028
				'p' => [
1029
					'site' => 'enwiki',
1030
					'title' => 'Berlin',
1031
					'data' => '{"sitelinks":{"dewiki":{"site":"dewiki","title":"TestPage!",'
1032
						. '"badges":["Q99999","%Q149%"]}}}'
1033
				],
1034
				'e' => [ 'exception' => [
1035
					'type' => ApiUsageException::class,
1036
					'code' => 'no-such-entity'
1037
				] ]
1038
			],
1039
			'no sitelink - cannot change badges' => [
1040
				'p' => [
1041
					'site' => 'enwiki',
1042
					'title' => 'Berlin',
1043
					'data' => '{"sitelinks":{"svwiki":{"site":"svwiki",'
1044
						. '"badges":["%Q42%","%Q149%"]}}}'
1045
				],
1046
				'e' => [ 'exception' => [
1047
					'type' => ApiUsageException::class,
1048
					'code' => 'modification-failed',
1049
					'message' => wfMessage( 'wikibase-validator-no-such-sitelink', 'svwiki' )->inLanguage( 'en' )->text(),
1050
				] ]
1051
			],
1052
			'bad id in serialization' => [
1053
				'p' => [ 'id' => '%Berlin%', 'data' => '{"id":"Q13244"}' ],
1054
				'e' => [ 'exception' => [
1055
					'type' => ApiUsageException::class,
1056
					'code' => 'param-invalid',
1057
					'message' => 'Invalid field used in call: "id", must match id parameter'
1058
				] ]
1059
			],
1060
			'bad type in serialization' => [
1061
				'p' => [ 'id' => '%Berlin%', 'data' => '{"id":"%Berlin%","type":"foobar"}' ],
1062
				'e' => [ 'exception' => [
1063
					'type' => ApiUsageException::class,
1064
					'code' => 'param-invalid',
1065
					'message' => 'Invalid field used in call: "type", '
1066
						. 'must match type associated with id'
1067
				] ]
1068
			],
1069
			'bad main snak replacement' => [
1070
				'p' => [ 'id' => '%Berlin%', 'data' => json_encode( [
1071
					'claims' => [
1072
						[
1073
							'id' => '%BerlinP56%',
1074
							'mainsnak' => [
1075
								'snaktype' => 'value',
1076
								'property' => '%P72%',
1077
								'datavalue' => [
1078
									'value' => 'anotherstring',
1079
									'type' => 'string'
1080
								],
1081
							],
1082
							'type' => 'statement',
1083
							'rank' => 'normal' ],
1084
					],
1085
				] ) ],
1086
				'e' => [ 'exception' => [
1087
					'type' => ApiUsageException::class,
1088
					'code' => 'modification-failed',
1089
					'message' => 'uses property %P56%, can\'t change to %P72%' ] ] ],
1090
			'invalid main snak' => [
1091
				'p' => [ 'id' => '%Berlin%', 'data' => json_encode( [
1092
					'claims' => [
1093
						[
1094
							'id' => '%BerlinP56%',
1095
							'mainsnak' => [
1096
								'snaktype' => 'value',
1097
								'property' => '%P56%',
1098
								'datavalue' => [ 'value' => '   ', 'type' => 'string' ],
1099
							],
1100
							'type' => 'statement',
1101
							'rank' => 'normal' ],
1102
					],
1103
				] ) ],
1104
				'e' => [ 'exception' => [
1105
					'type' => ApiUsageException::class,
1106
					'code' => 'modification-failed' ] ] ],
1107
			'properties cannot have sitelinks' => [
1108
				'p' => [
1109
					'id' => '%P56%',
1110
					'data' => '{"sitelinks":{"dewiki":{"site":"dewiki","title":"TestPage!"}}}',
1111
				],
1112
				'e' => [ 'exception' => [
1113
					'type' => ApiUsageException::class,
1114
					'code' => 'not-supported',
1115
					'message' => 'The requested feature is not supported by the given entity'
1116
				] ] ],
1117
			'property with invalid datatype' => [
1118
				'p' => [
1119
					'new' => 'property',
1120
					'data' => '{"datatype":"invalid"}',
1121
				],
1122
				'e' => [ 'exception' => [
1123
					'type' => ApiUsageException::class,
1124
					'code' => 'param-illegal'
1125
				] ] ],
1126
			'remove key misplaced in data' => [
1127
				'p' => [
1128
					'id' => '%Berlin%',
1129
					'data' => json_encode( [
1130
						'remove' => '',
1131
						'claims' => [ [
1132
							'type' => 'statement',
1133
							'mainsnak' => [
1134
								'snaktype' => 'novalue',
1135
								'property' => '%P56%',
1136
							],
1137
							'id' => '%BerlinP56%',
1138
						] ],
1139
					] )
1140
				],
1141
				'e' => [ 'exception' => [
1142
					'type' => ApiUsageException::class,
1143
					'code' => 'not-recognized',
1144
					'message-key' => 'wikibase-api-illegal-entity-remove',
1145
				] ],
1146
			],
1147
			'invalid tag (one)' => [
1148
				'p' => [
1149
					'new' => 'item',
1150
					'data' => '{}',
1151
					'tags' => 'test tag that definitely does not exist',
1152
				],
1153
				'e' => [ 'exception' => [
1154
					'type' => ApiUsageException::class,
1155
					'code' => $this->logicalOr(
1156
						$this->equalTo( 'tags-apply-not-allowed-one' ),
1157
						$this->equalTo( 'badtags' )
1158
					),
1159
				] ],
1160
			],
1161
			'invalid tag (multi)' => [
1162
				'p' => [
1163
					'new' => 'item',
1164
					'data' => '{}',
1165
					'tags' => implode( '|', [
1166
						'test tag that definitely does not exist',
1167
						'second test that that does not exist either',
1168
					] ),
1169
				],
1170
				'e' => [ 'exception' => [
1171
					'type' => ApiUsageException::class,
1172
					'code' => $this->logicalOr(
1173
						$this->equalTo( 'tags-apply-not-allowed-multi' ),
1174
						$this->equalTo( 'badtags' )
1175
					),
1176
				] ],
1177
			],
1178
		];
1179
	}
1180
1181
	/**
1182
	 * @dataProvider provideExceptionData
1183
	 */
1184
	public function testEditEntityExceptions( $params, $expected, $needed = null ) {
1185
		$this->skipIfEntityTypeNotKnown( $needed );
1186
1187
		$this->injectIds( $params );
1188
		$this->injectIds( $expected );
1189
1190
		// -- set any defaults ------------------------------------
1191
		$params['action'] = 'wbeditentity';
1192
		$this->doTestQueryExceptions( $params, $expected['exception'] );
1193
	}
1194
1195
	public function testItemCreationWithTag() {
1196
		$this->assertCanTagSuccessfulRequest( [
1197
			'action' => 'wbeditentity',
1198
			'new' => 'item',
1199
			'data' => '{}',
1200
		] );
1201
	}
1202
1203
	public function testItemLabelEqualsDescriptionConflict() {
1204
		$params = [
1205
			'action' => 'wbeditentity',
1206
			'new' => 'item',
1207
			'data' => '{
1208
				"labels": { "de": { "language": "de", "value": "label should not = description" } },
1209
				"descriptions": { "de": { "language": "de", "value": "label should not = description" } }
1210
			}',
1211
		];
1212
1213
		$expectedException = [
1214
			'type' => ApiUsageException::class,
1215
			'code' => 'modification-failed',
1216
		];
1217
		$this->doTestQueryExceptions( $params, $expectedException );
1218
	}
1219
1220
	public function testItemLabelConflictAvoidSelfConflictOnClear() {
1221
		$params = [
1222
			'action' => 'wbeditentity',
1223
			'new' => 'item',
1224
			'data' => '{
1225
				"labels": { "de": { "language": "de", "value": "Very German label" } },
1226
				"descriptions": { "de": { "language": "de", "value": "Very German description" } }
1227
			}',
1228
		];
1229
		list( $result, ) = $this->doApiRequestWithToken( $params );
1230
1231
		$params = [
1232
			'action' => 'wbeditentity',
1233
			'id' => $result['entity']['id'],
1234
			'clear' => 1,
1235
			'data' => '{
1236
				"labels": {
1237
					"de": { "language": "de", "value": "Very German label" },
1238
					"fa": { "language": "fa", "value": "Very non-German label" }
1239
				},
1240
				"descriptions": { "de": { "language": "de", "value": "Very German description" } }
1241
			}',
1242
		];
1243
		list( $result, ) = $this->doApiRequestWithToken( $params );
1244
		$this->assertSame( 1, $result['success'] );
1245
	}
1246
1247
	public function testClearFromBadRevId() {
1248
		$params = [
1249
			'action' => 'wbeditentity',
1250
			'id' => '%Berlin%',
1251
			'data' => '{}',
1252
			// 'baserevid' => '', // baserevid is set below
1253
			'clear' => '' ];
1254
		$this->injectIds( $params );
1255
1256
		$setupParams = [
1257
			'action' => 'wbeditentity',
1258
			'id' => $params['id'],
1259
			'clear' => '',
1260
			'data' => '{"descriptions":{"en":{"language":"en","value":"ClearFromBadRevidDesc1"}}}',
1261
		];
1262
1263
		list( $result, , ) = $this->doApiRequestWithToken( $setupParams );
1264
		$params['baserevid'] = $result['entity']['lastrevid'];
1265
		$setupParams['data'] = '{"descriptions":{"en":{"language":"en","value":"ClearFromBadRevidDesc2"}}}';
1266
		$this->doApiRequestWithToken( $setupParams );
1267
1268
		$expectedException = [ 'type' => ApiUsageException::class, 'code' => 'editconflict' ];
1269
		$this->doTestQueryExceptions( $params, $expectedException );
1270
	}
1271
1272
	public function testGivenReadOnlyType_errorIsShownAndNoEditHappened() {
1273
		$oldSetting = WikibaseRepo::getDefaultInstance()->getSettings()->getSetting(
1274
			'readOnlyEntityTypes'
1275
		);
1276
1277
		WikibaseRepo::getDefaultInstance()->getSettings()->setSetting(
1278
			'readOnlyEntityTypes',
1279
			[ 'item' ]
1280
		);
1281
1282
		$params = [
1283
			'action' => 'wbeditentity',
1284
			'data' => json_encode( [
1285
				'labels' => [ 'en' => [ 'value' => 'fooooo', 'language' => 'en' ] ]
1286
			] ),
1287
			'new' => 'item'
1288
		];
1289
1290
		try {
1291
			$this->doApiRequestWithToken( $params );
1292
			$this->fail( 'Read only error did not happen but should' );
1293
		} catch ( ReadOnlyError $e ) {
0 ignored issues
show
Bug introduced by
The class ReadOnlyError does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
1294
			$message = $e->getMessageObject();
1295
			$this->assertEquals( 'readonlytext', $message->getKey() );
1296
			$this->assertEquals(
1297
				[ 'Editing of entity type: item is currently disabled. It will be enabled soon.' ],
1298
				$message->getParams()
1299
			);
1300
		}
1301
1302
		WikibaseRepo::getDefaultInstance()->getSettings()->setSetting(
1303
			'readOnlyEntityTypes',
1304
			$oldSetting
1305
		);
1306
	}
1307
1308
}
1309