Session: Implement ArrayAccess
[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 = array( -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 array( $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 = array();
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( 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( array( $session, $m ), $args ) );
62 } else {
63 $this->assertNull( call_user_func_array( array( $session, $m ), $args ) );
64 }
65
66 // Trigger Session destructor
67 $session = null;
68 }
69
70 public static function provideMethods() {
71 return array(
72 array( 'getId', array(), false, true ),
73 array( 'getSessionId', array(), false, true ),
74 array( 'resetId', array(), false, true ),
75 array( 'getProvider', array(), false, true ),
76 array( 'isPersistent', array(), false, true ),
77 array( 'persist', array(), false, false ),
78 array( 'shouldRememberUser', array(), false, true ),
79 array( 'setRememberUser', array( true ), false, false ),
80 array( 'getRequest', array(), true, true ),
81 array( 'getUser', array(), false, true ),
82 array( 'getAllowedUserRights', array(), false, true ),
83 array( 'canSetUser', array(), false, true ),
84 array( 'setUser', array( new \stdClass ), false, false ),
85 array( 'suggestLoginUsername', array(), true, true ),
86 array( 'shouldForceHTTPS', array(), false, true ),
87 array( 'setForceHTTPS', array( true ), false, false ),
88 array( 'getLoggedOutTimestamp', array(), false, true ),
89 array( 'setLoggedOutTimestamp', array( 123 ), false, false ),
90 array( 'getProviderMetadata', array(), false, true ),
91 array( 'save', array(), false, false ),
92 array( 'delaySave', array(), false, true ),
93 array( 'renew', array(), 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 = array( 'a', 'b', '?' => 'c' );
141 $this->assertSame( 3, $session->count() );
142 $this->assertSame( 3, count( $session ) );
143 $this->assertFalse( $backend->dirty );
144
145 $data = array();
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( array(
170 array( 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'] = array( 'baz' => array() );
188 $session['bar']['baz']['quux'] = 2;
189 $this->assertEquals( array( 'baz' => array( 'quux' => 2 ) ), $backend->data['bar'] );
190
191 $logger->setCollect( true );
192 $session['bar2']['baz']['quux'] = 3;
193 $logger->setCollect( false );
194 $this->assertEquals( array( 'baz' => array( 'quux' => 3 ) ), $backend->data['bar2'] );
195 $this->assertSame( array(
196 array( 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', array( '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( array(), $backend->data );
238 $this->assertTrue( $backend->dirty );
239
240 $backend = $this->getMock(
241 'MediaWiki\\Session\\DummySessionBackend', array( 'canSetUser', 'setUser', 'save' )
242 );
243 $backend->data = array();
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', array( '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( array(), $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( array( '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 }