Merge "Add type hint against LinkTarget"
[lhc/web/wiklou.git] / tests / phpunit / includes / session / SessionTest.php
1 <?php
2
3 namespace MediaWiki\Session;
4
5 use Psr\Log\LogLevel;
6 use MediaWikiTestCase;
7 use User;
8
9 /**
10 * @group Session
11 * @covers MediaWiki\Session\Session
12 */
13 class SessionTest extends MediaWikiTestCase {
14
15 public function testConstructor() {
16 $backend = TestUtils::getDummySessionBackend();
17 \TestingAccessWrapper::newFromObject( $backend )->requests = [ -1 => 'dummy' ];
18 \TestingAccessWrapper::newFromObject( $backend )->id = new SessionId( 'abc' );
19
20 $session = new Session( $backend, 42, new \TestLogger );
21 $priv = \TestingAccessWrapper::newFromObject( $session );
22 $this->assertSame( $backend, $priv->backend );
23 $this->assertSame( 42, $priv->index );
24
25 $request = new \FauxRequest();
26 $priv2 = \TestingAccessWrapper::newFromObject( $session->sessionWithRequest( $request ) );
27 $this->assertSame( $backend, $priv2->backend );
28 $this->assertNotSame( $priv->index, $priv2->index );
29 $this->assertSame( $request, $priv2->getRequest() );
30 }
31
32 /**
33 * @dataProvider provideMethods
34 * @param string $m Method to test
35 * @param array $args Arguments to pass to the method
36 * @param bool $index Whether the backend method gets passed the index
37 * @param bool $ret Whether the method returns a value
38 */
39 public function testMethods( $m, $args, $index, $ret ) {
40 $mock = $this->getMock( 'MediaWiki\\Session\\DummySessionBackend',
41 [ $m, 'deregisterSession' ] );
42 $mock->expects( $this->once() )->method( 'deregisterSession' )
43 ->with( $this->identicalTo( 42 ) );
44
45 $tmp = $mock->expects( $this->once() )->method( $m );
46 $expectArgs = [];
47 if ( $index ) {
48 $expectArgs[] = $this->identicalTo( 42 );
49 }
50 foreach ( $args as $arg ) {
51 $expectArgs[] = $this->identicalTo( $arg );
52 }
53 $tmp = call_user_func_array( [ $tmp, 'with' ], $expectArgs );
54
55 $retval = new \stdClass;
56 $tmp->will( $this->returnValue( $retval ) );
57
58 $session = TestUtils::getDummySession( $mock, 42 );
59
60 if ( $ret ) {
61 $this->assertSame( $retval, call_user_func_array( [ $session, $m ], $args ) );
62 } else {
63 $this->assertNull( call_user_func_array( [ $session, $m ], $args ) );
64 }
65
66 // Trigger Session destructor
67 $session = null;
68 }
69
70 public static function provideMethods() {
71 return [
72 [ 'getId', [], false, true ],
73 [ 'getSessionId', [], false, true ],
74 [ 'resetId', [], false, true ],
75 [ 'getProvider', [], false, true ],
76 [ 'isPersistent', [], false, true ],
77 [ 'persist', [], false, false ],
78 [ 'shouldRememberUser', [], false, true ],
79 [ 'setRememberUser', [ true ], false, false ],
80 [ 'getRequest', [], true, true ],
81 [ 'getUser', [], false, true ],
82 [ 'getAllowedUserRights', [], false, true ],
83 [ 'canSetUser', [], false, true ],
84 [ 'setUser', [ new \stdClass ], false, false ],
85 [ 'suggestLoginUsername', [], true, true ],
86 [ 'shouldForceHTTPS', [], false, true ],
87 [ 'setForceHTTPS', [ true ], false, false ],
88 [ 'getLoggedOutTimestamp', [], false, true ],
89 [ 'setLoggedOutTimestamp', [ 123 ], false, false ],
90 [ 'getProviderMetadata', [], false, true ],
91 [ 'save', [], false, false ],
92 [ 'delaySave', [], false, true ],
93 [ 'renew', [], false, false ],
94 ];
95 }
96
97 public function testDataAccess() {
98 $session = TestUtils::getDummySession();
99 $backend = \TestingAccessWrapper::newFromObject( $session )->backend;
100
101 $this->assertEquals( 1, $session->get( 'foo' ) );
102 $this->assertEquals( 'zero', $session->get( 0 ) );
103 $this->assertFalse( $backend->dirty );
104
105 $this->assertEquals( null, $session->get( 'null' ) );
106 $this->assertEquals( 'default', $session->get( 'null', 'default' ) );
107 $this->assertFalse( $backend->dirty );
108
109 $session->set( 'foo', 55 );
110 $this->assertEquals( 55, $backend->data['foo'] );
111 $this->assertTrue( $backend->dirty );
112 $backend->dirty = false;
113
114 $session->set( 1, 'one' );
115 $this->assertEquals( 'one', $backend->data[1] );
116 $this->assertTrue( $backend->dirty );
117 $backend->dirty = false;
118
119 $session->set( 1, 'one' );
120 $this->assertFalse( $backend->dirty );
121
122 $this->assertTrue( $session->exists( 'foo' ) );
123 $this->assertTrue( $session->exists( 1 ) );
124 $this->assertFalse( $session->exists( 'null' ) );
125 $this->assertFalse( $session->exists( 100 ) );
126 $this->assertFalse( $backend->dirty );
127
128 $session->remove( 'foo' );
129 $this->assertArrayNotHasKey( 'foo', $backend->data );
130 $this->assertTrue( $backend->dirty );
131 $backend->dirty = false;
132 $session->remove( 1 );
133 $this->assertArrayNotHasKey( 1, $backend->data );
134 $this->assertTrue( $backend->dirty );
135 $backend->dirty = false;
136
137 $session->remove( 101 );
138 $this->assertFalse( $backend->dirty );
139
140 $backend->data = [ 'a', 'b', '?' => 'c' ];
141 $this->assertSame( 3, $session->count() );
142 $this->assertSame( 3, count( $session ) );
143 $this->assertFalse( $backend->dirty );
144
145 $data = [];
146 foreach ( $session as $key => $value ) {
147 $data[$key] = $value;
148 }
149 $this->assertEquals( $backend->data, $data );
150 $this->assertFalse( $backend->dirty );
151
152 $this->assertEquals( $backend->data, iterator_to_array( $session ) );
153 $this->assertFalse( $backend->dirty );
154 }
155
156 public function testArrayAccess() {
157 $logger = new \TestLogger;
158 $session = TestUtils::getDummySession( null, -1, $logger );
159 $backend = \TestingAccessWrapper::newFromObject( $session )->backend;
160
161 $this->assertEquals( 1, $session['foo'] );
162 $this->assertEquals( 'zero', $session[0] );
163 $this->assertFalse( $backend->dirty );
164
165 $logger->setCollect( true );
166 $this->assertEquals( null, $session['null'] );
167 $logger->setCollect( false );
168 $this->assertFalse( $backend->dirty );
169 $this->assertSame( [
170 [ LogLevel::DEBUG, 'Undefined index (auto-adds to session with a null value): null' ]
171 ], $logger->getBuffer() );
172 $logger->clearBuffer();
173
174 $session['foo'] = 55;
175 $this->assertEquals( 55, $backend->data['foo'] );
176 $this->assertTrue( $backend->dirty );
177 $backend->dirty = false;
178
179 $session[1] = 'one';
180 $this->assertEquals( 'one', $backend->data[1] );
181 $this->assertTrue( $backend->dirty );
182 $backend->dirty = false;
183
184 $session[1] = 'one';
185 $this->assertFalse( $backend->dirty );
186
187 $session['bar'] = [ 'baz' => [] ];
188 $session['bar']['baz']['quux'] = 2;
189 $this->assertEquals( [ 'baz' => [ 'quux' => 2 ] ], $backend->data['bar'] );
190
191 $logger->setCollect( true );
192 $session['bar2']['baz']['quux'] = 3;
193 $logger->setCollect( false );
194 $this->assertEquals( [ 'baz' => [ 'quux' => 3 ] ], $backend->data['bar2'] );
195 $this->assertSame( [
196 [ LogLevel::DEBUG, 'Undefined index (auto-adds to session with a null value): bar2' ]
197 ], $logger->getBuffer() );
198 $logger->clearBuffer();
199
200 $backend->dirty = false;
201 $this->assertTrue( isset( $session['foo'] ) );
202 $this->assertTrue( isset( $session[1] ) );
203 $this->assertFalse( isset( $session['null'] ) );
204 $this->assertFalse( isset( $session['missing'] ) );
205 $this->assertFalse( isset( $session[100] ) );
206 $this->assertFalse( $backend->dirty );
207
208 unset( $session['foo'] );
209 $this->assertArrayNotHasKey( 'foo', $backend->data );
210 $this->assertTrue( $backend->dirty );
211 $backend->dirty = false;
212 unset( $session[1] );
213 $this->assertArrayNotHasKey( 1, $backend->data );
214 $this->assertTrue( $backend->dirty );
215 $backend->dirty = false;
216
217 unset( $session[101] );
218 $this->assertFalse( $backend->dirty );
219 }
220
221 public function testClear() {
222 $session = TestUtils::getDummySession();
223 $priv = \TestingAccessWrapper::newFromObject( $session );
224
225 $backend = $this->getMock(
226 'MediaWiki\\Session\\DummySessionBackend', [ 'canSetUser', 'setUser', 'save' ]
227 );
228 $backend->expects( $this->once() )->method( 'canSetUser' )
229 ->will( $this->returnValue( true ) );
230 $backend->expects( $this->once() )->method( 'setUser' )
231 ->with( $this->callback( function ( $user ) {
232 return $user instanceof User && $user->isAnon();
233 } ) );
234 $backend->expects( $this->once() )->method( 'save' );
235 $priv->backend = $backend;
236 $session->clear();
237 $this->assertSame( [], $backend->data );
238 $this->assertTrue( $backend->dirty );
239
240 $backend = $this->getMock(
241 'MediaWiki\\Session\\DummySessionBackend', [ 'canSetUser', 'setUser', 'save' ]
242 );
243 $backend->data = [];
244 $backend->expects( $this->once() )->method( 'canSetUser' )
245 ->will( $this->returnValue( true ) );
246 $backend->expects( $this->once() )->method( 'setUser' )
247 ->with( $this->callback( function ( $user ) {
248 return $user instanceof User && $user->isAnon();
249 } ) );
250 $backend->expects( $this->once() )->method( 'save' );
251 $priv->backend = $backend;
252 $session->clear();
253 $this->assertFalse( $backend->dirty );
254
255 $backend = $this->getMock(
256 'MediaWiki\\Session\\DummySessionBackend', [ 'canSetUser', 'setUser', 'save' ]
257 );
258 $backend->expects( $this->once() )->method( 'canSetUser' )
259 ->will( $this->returnValue( false ) );
260 $backend->expects( $this->never() )->method( 'setUser' );
261 $backend->expects( $this->once() )->method( 'save' );
262 $priv->backend = $backend;
263 $session->clear();
264 $this->assertSame( [], $backend->data );
265 $this->assertTrue( $backend->dirty );
266 }
267
268 public function testTokens() {
269 $rc = new \ReflectionClass( 'MediaWiki\\Session\\Session' );
270 if ( !method_exists( $rc, 'newInstanceWithoutConstructor' ) ) {
271 $this->markTestSkipped(
272 'ReflectionClass::newInstanceWithoutConstructor isn\'t available'
273 );
274 }
275
276 // Instead of actually constructing the Session, we use reflection to
277 // bypass the constructor and plug a mock SessionBackend into the
278 // private fields to avoid having to actually create a SessionBackend.
279 $backend = new DummySessionBackend;
280 $session = $rc->newInstanceWithoutConstructor();
281 $priv = \TestingAccessWrapper::newFromObject( $session );
282 $priv->backend = $backend;
283 $priv->index = 42;
284
285 $token = \TestingAccessWrapper::newFromObject( $session->getToken() );
286 $this->assertArrayHasKey( 'wsTokenSecrets', $backend->data );
287 $this->assertArrayHasKey( 'default', $backend->data['wsTokenSecrets'] );
288 $secret = $backend->data['wsTokenSecrets']['default'];
289 $this->assertSame( $secret, $token->secret );
290 $this->assertSame( '', $token->salt );
291 $this->assertTrue( $token->wasNew() );
292
293 $token = \TestingAccessWrapper::newFromObject( $session->getToken( 'foo' ) );
294 $this->assertSame( $secret, $token->secret );
295 $this->assertSame( 'foo', $token->salt );
296 $this->assertFalse( $token->wasNew() );
297
298 $backend->data['wsTokenSecrets']['secret'] = 'sekret';
299 $token = \TestingAccessWrapper::newFromObject(
300 $session->getToken( [ 'bar', 'baz' ], 'secret' )
301 );
302 $this->assertSame( 'sekret', $token->secret );
303 $this->assertSame( 'bar|baz', $token->salt );
304 $this->assertFalse( $token->wasNew() );
305
306 $session->resetToken( 'secret' );
307 $this->assertArrayHasKey( 'wsTokenSecrets', $backend->data );
308 $this->assertArrayHasKey( 'default', $backend->data['wsTokenSecrets'] );
309 $this->assertArrayNotHasKey( 'secret', $backend->data['wsTokenSecrets'] );
310
311 $session->resetAllTokens();
312 $this->assertArrayNotHasKey( 'wsTokenSecrets', $backend->data );
313
314 }
315 }