1<?php
2
3class MWRestrictionsTest extends MediaWikiUnitTestCase {
4
5	protected static $restrictionsForChecks;
6
7	public static function setUpBeforeClass(): void {
8		parent::setUpBeforeClass();
9		self::$restrictionsForChecks = MWRestrictions::newFromArray( [
10			'IPAddresses' => [
11				'10.0.0.0/8',
12				'172.16.0.0/12',
13				'2001:db8::/33',
14			]
15		] );
16	}
17
18	/**
19	 * @covers MWRestrictions::newDefault
20	 * @covers MWRestrictions::__construct
21	 */
22	public function testNewDefault() {
23		$ret = MWRestrictions::newDefault();
24		$this->assertInstanceOf( MWRestrictions::class, $ret );
25		$this->assertSame(
26			'{"IPAddresses":["0.0.0.0/0","::/0"]}',
27			$ret->toJson()
28		);
29	}
30
31	/**
32	 * @covers MWRestrictions::newFromArray
33	 * @covers MWRestrictions::__construct
34	 * @covers MWRestrictions::loadFromArray
35	 * @covers MWRestrictions::toArray
36	 * @dataProvider provideArray
37	 * @param array $data
38	 * @param bool|InvalidArgumentException $expect True if the call succeeds,
39	 *  otherwise the exception that should be thrown.
40	 */
41	public function testArray( $data, $expect ) {
42		if ( $expect === true ) {
43			$ret = MWRestrictions::newFromArray( $data );
44			$this->assertInstanceOf( MWRestrictions::class, $ret );
45			$this->assertSame( $data, $ret->toArray() );
46		} else {
47			try {
48				MWRestrictions::newFromArray( $data );
49				$this->fail( 'Expected exception not thrown' );
50			} catch ( InvalidArgumentException $ex ) {
51				$this->assertEquals( $expect, $ex );
52			}
53		}
54	}
55
56	public static function provideArray() {
57		return [
58			[ [ 'IPAddresses' => [] ], true ],
59			[ [ 'IPAddresses' => [ '127.0.0.1/32' ] ], true ],
60			[
61				[ 'IPAddresses' => [ '256.0.0.1/32' ] ],
62				new InvalidArgumentException( 'Invalid IP address: 256.0.0.1/32' )
63			],
64			[
65				[ 'IPAddresses' => '127.0.0.1/32' ],
66				new InvalidArgumentException( 'IPAddresses is not an array' )
67			],
68			[
69				[],
70				new InvalidArgumentException( 'Array is missing required keys: IPAddresses' )
71			],
72			[
73				[ 'foo' => 'bar', 'bar' => 42 ],
74				new InvalidArgumentException( 'Array contains invalid keys: foo, bar' )
75			],
76		];
77	}
78
79	/**
80	 * @covers MWRestrictions::newFromJson
81	 * @covers MWRestrictions::__construct
82	 * @covers MWRestrictions::loadFromArray
83	 * @covers MWRestrictions::toJson
84	 * @covers MWRestrictions::__toString
85	 * @dataProvider provideJson
86	 * @param string $json
87	 * @param array|InvalidArgumentException $expect
88	 */
89	public function testJson( $json, $expect ) {
90		if ( is_array( $expect ) ) {
91			$ret = MWRestrictions::newFromJson( $json );
92			$this->assertInstanceOf( MWRestrictions::class, $ret );
93			$this->assertSame( $expect, $ret->toArray() );
94
95			$this->assertSame( $json, $ret->toJson( false ) );
96			$this->assertSame( $json, (string)$ret );
97
98			$this->assertSame(
99				FormatJson::encode( $expect, true, FormatJson::ALL_OK ),
100				$ret->toJson( true )
101			);
102		} else {
103			try {
104				MWRestrictions::newFromJson( $json );
105				$this->fail( 'Expected exception not thrown' );
106			} catch ( InvalidArgumentException $ex ) {
107				$this->assertTrue( true );
108			}
109		}
110	}
111
112	public static function provideJson() {
113		return [
114			[
115				'{"IPAddresses":[]}',
116				[ 'IPAddresses' => [] ]
117			],
118			[
119				'{"IPAddresses":["127.0.0.1/32"]}',
120				[ 'IPAddresses' => [ '127.0.0.1/32' ] ]
121			],
122			[
123				'{"IPAddresses":["256.0.0.1/32"]}',
124				new InvalidArgumentException( 'Invalid IP address: 256.0.0.1/32' )
125			],
126			[
127				'{"IPAddresses":"127.0.0.1/32"}',
128				new InvalidArgumentException( 'IPAddresses is not an array' )
129			],
130			[
131				'{}',
132				new InvalidArgumentException( 'Array is missing required keys: IPAddresses' )
133			],
134			[
135				'{"foo":"bar","bar":42}',
136				new InvalidArgumentException( 'Array contains invalid keys: foo, bar' )
137			],
138			[
139				'{"IPAddresses":[]',
140				new InvalidArgumentException( 'Invalid restrictions JSON' )
141			],
142			[
143				'"IPAddresses"',
144				new InvalidArgumentException( 'Invalid restrictions JSON' )
145			],
146		];
147	}
148
149	/**
150	 * @covers MWRestrictions::checkIP
151	 * @dataProvider provideCheckIP
152	 * @param string $ip
153	 * @param bool $pass
154	 */
155	public function testCheckIP( $ip, $pass ) {
156		$this->assertSame( $pass, self::$restrictionsForChecks->checkIP( $ip ) );
157	}
158
159	public static function provideCheckIP() {
160		return [
161			[ '10.0.0.1', true ],
162			[ '172.16.0.0', true ],
163			[ '192.0.2.1', false ],
164			[ '2001:db8:1::', true ],
165			[ '2001:0db8:0000:0000:0000:0000:0000:0000', true ],
166			[ '2001:0DB8:8000::', false ],
167		];
168	}
169
170	/**
171	 * @covers MWRestrictions::check
172	 * @dataProvider provideCheck
173	 * @param WebRequest $request
174	 * @param Status $expect
175	 */
176	public function testCheck( $request, $expect ) {
177		$this->assertEquals( $expect, self::$restrictionsForChecks->check( $request ) );
178	}
179
180	public function provideCheck() {
181		$ret = [];
182
183		$mockBuilder = $this->getMockBuilder( FauxRequest::class )
184			->onlyMethods( [ 'getIP' ] );
185
186		foreach ( self::provideCheckIP() as $checkIP ) {
187			$ok = [];
188			$request = $mockBuilder->getMock();
189
190			$request->method( 'getIP' )
191				->willReturn( $checkIP[0] );
192			$ok['ip'] = $checkIP[1];
193
194			/* If we ever add more restrictions, add nested for loops here:
195			 *  foreach ( self::provideCheckFoo() as $checkFoo ) {
196			 *      $request->method( 'getFoo' )->willReturn( $checkFoo[0] );
197			 *      $ok['foo'] = $checkFoo[1];
198			 *
199			 *      foreach ( self::provideCheckBar() as $checkBar ) {
200			 *          $request->method( 'getBar' )->willReturn( $checkBar[0] );
201			 *          $ok['bar'] = $checkBar[1];
202			 *
203			 *          // etc.
204			 *      }
205			 *  }
206			 */
207
208			$status = Status::newGood();
209			$status->setResult( $ok === array_filter( $ok ), $ok );
210			$ret[] = [ $request, $status ];
211		}
212
213		return $ret;
214	}
215}
216