1<?php
2
3namespace MediaWiki\Api\Validator;
4
5use ApiMain;
6use ApiModuleManager;
7use MockApi;
8use Wikimedia\Message\DataMessageValue;
9use Wikimedia\ParamValidator\ParamValidator;
10use Wikimedia\ParamValidator\SimpleCallbacks;
11use Wikimedia\ParamValidator\TypeDef\EnumDef;
12use Wikimedia\ParamValidator\TypeDef\TypeDefTestCase;
13use Wikimedia\ParamValidator\ValidationException;
14use Wikimedia\TestingAccessWrapper;
15
16/**
17 * @covers MediaWiki\Api\Validator\SubmoduleDef
18 */
19class SubmoduleDefTest extends TypeDefTestCase {
20
21	protected function getInstance( SimpleCallbacks $callbacks, array $options ) {
22		return new SubmoduleDef( $callbacks, $options );
23	}
24
25	private function mockApi() {
26		$api = $this->getMockBuilder( MockApi::class )
27			->setMethods( [ 'getModuleManager' ] )
28			->getMock();
29		$w = TestingAccessWrapper::newFromObject( $api );
30		$w->mModuleName = 'testmod';
31		$w->mMainModule = new ApiMain;
32		$w->mModulePrefix = 'tt';
33
34		$w->mMainModule->getModuleManager()->addModule( 'testmod', 'action', [
35			'class' => MockApi::class,
36			'factory' => static function () use ( $api ) {
37				return $api;
38			},
39		] );
40
41		$dep = $this->getMockBuilder( MockApi::class )
42			->setMethods( [ 'isDeprecated' ] )
43			->getMock();
44		$dep->method( 'isDeprecated' )->willReturn( true );
45		$int = $this->getMockBuilder( MockApi::class )
46			->setMethods( [ 'isInternal' ] )
47			->getMock();
48		$int->method( 'isInternal' )->willReturn( true );
49		$depint = $this->getMockBuilder( MockApi::class )
50			->setMethods( [ 'isDeprecated', 'isInternal' ] )
51			->getMock();
52		$depint->method( 'isDeprecated' )->willReturn( true );
53		$depint->method( 'isInternal' )->willReturn( true );
54
55		$manager = new ApiModuleManager( $api );
56		$api->method( 'getModuleManager' )->willReturn( $manager );
57		$manager->addModule( 'mod1', 'test', MockApi::class );
58		$manager->addModule( 'mod2', 'test', MockApi::class );
59		$manager->addModule( 'dep', 'test', [
60			'class' => MockApi::class,
61			'factory' => static function () use ( $dep ) {
62				return $dep;
63			},
64		] );
65		$manager->addModule( 'depint', 'test', [
66			'class' => MockApi::class,
67			'factory' => static function () use ( $depint ) {
68				return $depint;
69			},
70		] );
71		$manager->addModule( 'int', 'test', [
72			'class' => MockApi::class,
73			'factory' => static function () use ( $int ) {
74				return $int;
75			},
76		] );
77		$manager->addModule( 'recurse', 'test', [
78			'class' => MockApi::class,
79			'factory' => static function () use ( $api ) {
80				return $api;
81			},
82		] );
83		$manager->addModule( 'mod3', 'xyz', MockApi::class );
84
85		$this->assertSame( $api, $api->getModuleFromPath( 'testmod' ), 'sanity check' );
86		$this->assertSame( $dep, $api->getModuleFromPath( 'testmod+dep' ), 'sanity check' );
87		$this->assertSame( $int, $api->getModuleFromPath( 'testmod+int' ), 'sanity check' );
88		$this->assertSame( $depint, $api->getModuleFromPath( 'testmod+depint' ), 'sanity check' );
89
90		return $api;
91	}
92
93	public function provideValidate() {
94		$opts = [
95			'module' => $this->mockApi(),
96		];
97		$map = [
98			SubmoduleDef::PARAM_SUBMODULE_MAP => [
99				'mod2' => 'testmod+mod1',
100				'mod3' => 'testmod+mod3',
101			],
102		];
103
104		return [
105			'Basic' => [ 'mod1', 'mod1', [], $opts ],
106			'Nonexistent submodule' => [
107				'mod3',
108				new ValidationException(
109					DataMessageValue::new( 'paramvalidator-badvalue', [], 'badvalue', [] ), 'test', 'mod3', []
110				),
111				[],
112				$opts,
113			],
114			'Mapped' => [ 'mod3', 'mod3', $map, $opts ],
115			'Mapped, not in map' => [
116				'mod1',
117				new ValidationException(
118					DataMessageValue::new( 'paramvalidator-badvalue', [], 'badvalue', [] ), 'test', 'mod1', $map
119				),
120				$map,
121				$opts,
122			],
123		];
124	}
125
126	public function provideCheckSettings() {
127		$opts = [
128			'module' => $this->mockApi(),
129		];
130		$keys = [
131			'Y', EnumDef::PARAM_DEPRECATED_VALUES,
132			SubmoduleDef::PARAM_SUBMODULE_MAP, SubmoduleDef::PARAM_SUBMODULE_PARAM_PREFIX
133		];
134
135		return [
136			'Basic test' => [
137				[],
138				self::STDRET,
139				[
140					'issues' => [ 'X' ],
141					'allowedKeys' => $keys,
142					'messages' => [],
143				],
144				$opts
145			],
146			'Test with everything' => [
147				[
148					SubmoduleDef::PARAM_SUBMODULE_MAP => [
149						'foo' => 'testmod+mod1', 'bar' => 'testmod+mod2'
150					],
151					SubmoduleDef::PARAM_SUBMODULE_PARAM_PREFIX => 'g',
152				],
153				self::STDRET,
154				[
155					'issues' => [ 'X' ],
156					'allowedKeys' => $keys,
157					'messages' => [],
158				],
159				$opts
160			],
161			'Bad types' => [
162				[
163					SubmoduleDef::PARAM_SUBMODULE_MAP => false,
164					SubmoduleDef::PARAM_SUBMODULE_PARAM_PREFIX => true,
165				],
166				self::STDRET,
167				[
168					'issues' => [
169						'X',
170						SubmoduleDef::PARAM_SUBMODULE_MAP => 'PARAM_SUBMODULE_MAP must be an array, got boolean',
171						SubmoduleDef::PARAM_SUBMODULE_PARAM_PREFIX
172							=> 'PARAM_SUBMODULE_PARAM_PREFIX must be a string, got boolean',
173					],
174					'allowedKeys' => $keys,
175					'messages' => [],
176				],
177				$opts
178			],
179			'Bad values in map' => [
180				[
181					SubmoduleDef::PARAM_SUBMODULE_MAP => [
182						'a' => 'testmod+mod1',
183						'b' => false,
184						'c' => null,
185						'd' => 'testmod+mod7',
186						'r' => 'testmod+recurse+recurse',
187					],
188				],
189				self::STDRET,
190				[
191					'issues' => [
192						'X',
193						'Values for PARAM_SUBMODULE_MAP must be strings, but value for "b" is boolean',
194						'Values for PARAM_SUBMODULE_MAP must be strings, but value for "c" is NULL',
195						'PARAM_SUBMODULE_MAP contains "testmod+mod7", which is not a valid module path',
196					],
197					'allowedKeys' => $keys,
198					'messages' => [],
199				],
200				$opts
201			],
202		];
203	}
204
205	public function provideGetEnumValues() {
206		$opts = [
207			'module' => $this->mockApi(),
208		];
209
210		return [
211			'Basic test' => [
212				[ ParamValidator::PARAM_TYPE => 'submodule' ],
213				[ 'mod1', 'mod2', 'dep', 'depint', 'int', 'recurse' ],
214				$opts,
215			],
216			'Mapped' => [
217				[
218					ParamValidator::PARAM_TYPE => 'submodule',
219					SubmoduleDef::PARAM_SUBMODULE_MAP => [
220						'mod2' => 'test+mod1',
221						'mod3' => 'test+mod3',
222					]
223				],
224				[ 'mod2', 'mod3' ],
225				$opts,
226			],
227		];
228	}
229
230	public function provideGetInfo() {
231		$opts = [
232			'module' => $this->mockApi(),
233		];
234
235		return [
236			'Basic' => [
237				[],
238				[
239					'type' => [ 'mod1', 'mod2', 'recurse', 'dep', 'int', 'depint' ],
240					'submodules' => [
241						'mod1' => 'testmod+mod1',
242						'mod2' => 'testmod+mod2',
243						'recurse' => 'testmod+recurse',
244						'dep' => 'testmod+dep',
245						'int' => 'testmod+int',
246						'depint' => 'testmod+depint',
247					],
248					'deprecatedvalues' => [ 'dep', 'depint' ],
249					'internalvalues' => [ 'depint', 'int' ],
250				],
251				[
252					// phpcs:ignore Generic.Files.LineLength.TooLong
253					ParamValidator::PARAM_TYPE => '<message key="paramvalidator-help-type-enum"><text>1</text><list listType="comma"><text>[[Special:ApiHelp/testmod+mod1|&lt;span dir=&quot;ltr&quot; lang=&quot;en&quot;&gt;mod1&lt;/span&gt;]]</text><text>[[Special:ApiHelp/testmod+mod2|&lt;span dir=&quot;ltr&quot; lang=&quot;en&quot;&gt;mod2&lt;/span&gt;]]</text><text>[[Special:ApiHelp/testmod+recurse|&lt;span dir=&quot;ltr&quot; lang=&quot;en&quot;&gt;recurse&lt;/span&gt;]]</text><text>[[Special:ApiHelp/testmod+dep|&lt;span dir=&quot;ltr&quot; lang=&quot;en&quot; class=&quot;apihelp-deprecated-value&quot;&gt;dep&lt;/span&gt;]]</text><text>[[Special:ApiHelp/testmod+int|&lt;span dir=&quot;ltr&quot; lang=&quot;en&quot; class=&quot;apihelp-internal-value&quot;&gt;int&lt;/span&gt;]]</text><text>[[Special:ApiHelp/testmod+depint|&lt;span dir=&quot;ltr&quot; lang=&quot;en&quot; class=&quot;apihelp-deprecated-value apihelp-internal-value&quot;&gt;depint&lt;/span&gt;]]</text></list><num>6</num></message>',
254					ParamValidator::PARAM_ISMULTI => null,
255				],
256				$opts,
257			],
258			'Mapped' => [
259				[
260					ParamValidator::PARAM_DEFAULT => 'mod3|mod4',
261					ParamValidator::PARAM_ISMULTI => true,
262					SubmoduleDef::PARAM_SUBMODULE_PARAM_PREFIX => 'g',
263					SubmoduleDef::PARAM_SUBMODULE_MAP => [
264						'xyz' => 'testmod+dep',
265						'mod3' => 'testmod+mod3',
266						'mod4' => 'testmod+mod4', // doesn't exist
267					],
268				],
269				[
270					'type' => [ 'mod3', 'mod4', 'xyz' ],
271					'submodules' => [
272						'mod3' => 'testmod+mod3',
273						'mod4' => 'testmod+mod4',
274						'xyz' => 'testmod+dep',
275					],
276					'submoduleparamprefix' => 'g',
277					'deprecatedvalues' => [ 'xyz' ],
278				],
279				[
280					// phpcs:ignore Generic.Files.LineLength.TooLong
281					ParamValidator::PARAM_TYPE => '<message key="paramvalidator-help-type-enum"><text>2</text><list listType="comma"><text>[[Special:ApiHelp/testmod+mod3|&lt;span dir=&quot;ltr&quot; lang=&quot;en&quot;&gt;mod3&lt;/span&gt;]]</text><text>[[Special:ApiHelp/testmod+mod4|&lt;span dir=&quot;ltr&quot; lang=&quot;en&quot;&gt;mod4&lt;/span&gt;]]</text><text>[[Special:ApiHelp/testmod+dep|&lt;span dir=&quot;ltr&quot; lang=&quot;en&quot; class=&quot;apihelp-deprecated-value&quot;&gt;xyz&lt;/span&gt;]]</text></list><num>3</num></message>',
282					ParamValidator::PARAM_ISMULTI => null,
283				],
284				$opts,
285			],
286		];
287	}
288
289}
290