Complex classes like SessionMiddlewareTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use SessionMiddlewareTest, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
54 | final class SessionMiddlewareTest extends TestCase |
||
55 | { |
||
56 | public function testFromSymmetricKeyDefaultsUsesASecureCookie() : void |
||
57 | { |
||
58 | $response = SessionMiddleware::fromSymmetricKeyDefaults('not relevant', 100) |
||
59 | ->process(new ServerRequest(), $this->writingMiddleware()); |
||
60 | |||
61 | $cookie = $this->getCookie($response); |
||
62 | |||
63 | self::assertTrue($cookie->getSecure()); |
||
64 | self::assertTrue($cookie->getHttpOnly()); |
||
65 | } |
||
66 | |||
67 | public function testFromAsymmetricKeyDefaultsUsesASecureCookie() : void |
||
68 | { |
||
69 | $response = SessionMiddleware::fromAsymmetricKeyDefaults( |
||
70 | self::privateKey(), |
||
71 | self::publicKey(), |
||
72 | 200 |
||
73 | ) |
||
74 | ->process(new ServerRequest(), $this->writingMiddleware()); |
||
75 | |||
76 | $cookie = $this->getCookie($response); |
||
77 | |||
78 | self::assertTrue($cookie->getSecure()); |
||
79 | self::assertTrue($cookie->getHttpOnly()); |
||
80 | } |
||
81 | |||
82 | /** |
||
83 | * @dataProvider validMiddlewaresProvider |
||
84 | */ |
||
85 | public function testSkipsInjectingSessionCookieOnEmptyContainer(SessionMiddleware $middleware) : void |
||
86 | { |
||
87 | $response = $this->ensureSameResponse($middleware, new ServerRequest(), $this->emptyValidationMiddleware()); |
||
88 | |||
89 | self::assertNull($this->getCookie($response)->getValue()); |
||
90 | } |
||
91 | |||
92 | /** |
||
93 | * @dataProvider validMiddlewaresProvider |
||
94 | */ |
||
95 | public function testExtractsSessionContainerFromEmptyRequest(SessionMiddleware $middleware) : void |
||
96 | { |
||
97 | $this->ensureSameResponse($middleware, new ServerRequest(), $this->emptyValidationMiddleware()); |
||
98 | } |
||
99 | |||
100 | /** |
||
101 | * @dataProvider validMiddlewaresProvider |
||
102 | */ |
||
103 | public function testInjectsSessionInResponseCookies(SessionMiddleware $middleware) : void |
||
104 | { |
||
105 | $initialResponse = new Response(); |
||
106 | $response = $middleware->process(new ServerRequest(), $this->writingMiddleware()); |
||
107 | |||
108 | self::assertNotSame($initialResponse, $response); |
||
109 | self::assertEmpty($this->getCookie($response, 'non-existing')->getValue()); |
||
110 | |||
111 | $token = $this->getCookie($response)->getValue(); |
||
112 | |||
113 | self::assertIsString($token); |
||
114 | self::assertEquals((object) ['foo' => 'bar'], (new Parser())->parse($token)->getClaim('session-data')); |
||
115 | } |
||
116 | |||
117 | /** |
||
118 | * @dataProvider validMiddlewaresProvider |
||
119 | */ |
||
120 | public function testSessionContainerCanBeReusedOverMultipleRequests(SessionMiddleware $middleware) : void |
||
121 | { |
||
122 | $sessionValue = uniqid('', true); |
||
123 | |||
124 | $checkingMiddleware = $this->fakeDelegate( |
||
125 | static function (ServerRequestInterface $request) use ($sessionValue) { |
||
126 | $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE); |
||
127 | assert($session instanceof SessionInterface); |
||
128 | |||
129 | self::assertSame($sessionValue, $session->get('foo')); |
||
130 | self::assertFalse($session->hasChanged()); |
||
131 | |||
132 | $session->set('foo', $sessionValue . 'changed'); |
||
133 | |||
134 | self::assertTrue( |
||
135 | $session->hasChanged(), |
||
136 | 'ensuring that the cookie is sent again: ' |
||
137 | . 'non-modified session containers are not to be re-serialized into a token' |
||
138 | ); |
||
139 | |||
140 | return new Response(); |
||
141 | } |
||
142 | ); |
||
143 | |||
144 | $firstResponse = $middleware->process(new ServerRequest(), $this->writingMiddleware($sessionValue)); |
||
145 | |||
146 | $response = $middleware->process( |
||
147 | $this->requestWithResponseCookies($firstResponse), |
||
148 | $checkingMiddleware |
||
149 | ); |
||
150 | |||
151 | self::assertNotSame($response, $firstResponse); |
||
152 | } |
||
153 | |||
154 | /** |
||
155 | * @dataProvider validMiddlewaresProvider |
||
156 | */ |
||
157 | public function testSessionContainerCanBeCreatedEvenIfTokenDataIsMalformed(SessionMiddleware $middleware) : void |
||
158 | { |
||
159 | $sessionValue = uniqid('not valid session data', true); |
||
160 | |||
161 | $checkingMiddleware = $this->fakeDelegate( |
||
162 | static function (ServerRequestInterface $request) use ($sessionValue) { |
||
163 | $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE); |
||
164 | assert($session instanceof SessionInterface); |
||
165 | |||
166 | self::assertSame($sessionValue, $session->get('scalar')); |
||
167 | self::assertFalse($session->hasChanged()); |
||
168 | |||
169 | return new Response(); |
||
170 | } |
||
171 | ); |
||
172 | |||
173 | $this->createTokenWithCustomClaim( |
||
174 | $middleware, |
||
175 | new DateTime('-1 day'), |
||
176 | new DateTime('+1 day'), |
||
177 | 'not valid session data' |
||
178 | ); |
||
179 | |||
180 | $middleware->process( |
||
181 | (new ServerRequest()) |
||
182 | ->withCookieParams([ |
||
183 | SessionMiddleware::DEFAULT_COOKIE => $this->createTokenWithCustomClaim( |
||
184 | $middleware, |
||
185 | new DateTime('-1 day'), |
||
186 | new DateTime('+1 day'), |
||
187 | $sessionValue |
||
188 | ), |
||
189 | ]), |
||
190 | $checkingMiddleware |
||
191 | ); |
||
192 | } |
||
193 | |||
194 | /** |
||
195 | * @dataProvider validMiddlewaresProvider |
||
196 | */ |
||
197 | public function testWillIgnoreRequestsWithExpiredTokens(SessionMiddleware $middleware) : void |
||
198 | { |
||
199 | $expiredToken = (new ServerRequest()) |
||
200 | ->withCookieParams([ |
||
201 | SessionMiddleware::DEFAULT_COOKIE => $this->createToken( |
||
202 | $middleware, |
||
203 | new DateTime('-1 day'), |
||
204 | new DateTime('-2 day') |
||
205 | ), |
||
206 | ]); |
||
207 | |||
208 | $this->ensureSameResponse($middleware, $expiredToken, $this->emptyValidationMiddleware()); |
||
209 | } |
||
210 | |||
211 | /** |
||
212 | * @dataProvider validMiddlewaresProvider |
||
213 | */ |
||
214 | public function testWillIgnoreRequestsWithTokensFromFuture(SessionMiddleware $middleware) : void |
||
215 | { |
||
216 | $tokenInFuture = (new ServerRequest()) |
||
217 | ->withCookieParams([ |
||
218 | SessionMiddleware::DEFAULT_COOKIE => $this->createToken( |
||
219 | $middleware, |
||
220 | new DateTime('+1 day'), |
||
221 | new DateTime('-2 day') |
||
222 | ), |
||
223 | ]); |
||
224 | |||
225 | $this->ensureSameResponse($middleware, $tokenInFuture, $this->emptyValidationMiddleware()); |
||
226 | } |
||
227 | |||
228 | /** |
||
229 | * @dataProvider validMiddlewaresProvider |
||
230 | */ |
||
231 | public function testWillIgnoreUnSignedTokens(SessionMiddleware $middleware) : void |
||
232 | { |
||
233 | $unsignedToken = (new ServerRequest()) |
||
234 | ->withCookieParams([ |
||
235 | SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder()) |
||
236 | ->setIssuedAt((new DateTime('-1 day'))->getTimestamp()) |
||
237 | ->setExpiration((new DateTime('+1 day'))->getTimestamp()) |
||
238 | ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar'])) |
||
239 | ->getToken(), |
||
240 | ]); |
||
241 | |||
242 | $this->ensureSameResponse($middleware, $unsignedToken, $this->emptyValidationMiddleware()); |
||
243 | } |
||
244 | |||
245 | /** |
||
246 | * @dataProvider validMiddlewaresProvider |
||
247 | */ |
||
248 | public function testWillNotRefreshSignedTokensWithoutIssuedAt(SessionMiddleware $middleware) : void |
||
249 | { |
||
250 | $unsignedToken = (new ServerRequest()) |
||
251 | ->withCookieParams([ |
||
252 | SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder()) |
||
253 | ->setExpiration((new DateTime('+1 day'))->getTimestamp()) |
||
254 | ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar'])) |
||
255 | ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware)) |
||
256 | ->getToken(), |
||
257 | ]); |
||
258 | |||
259 | $this->ensureSameResponse($middleware, $unsignedToken); |
||
260 | } |
||
261 | |||
262 | public function testWillRefreshTokenWithIssuedAtExactlyAtTokenRefreshTimeThreshold() : void |
||
263 | { |
||
264 | // forcing ourselves to think of time as a mutable value: |
||
265 | $time = time() + random_int(-100, +100); |
||
266 | |||
267 | $clock = new FrozenClock(new DateTimeImmutable('@' . $time)); |
||
268 | |||
269 | $middleware = new SessionMiddleware( |
||
270 | new Sha256(), |
||
271 | 'foo', |
||
272 | 'foo', |
||
273 | SetCookie::create(SessionMiddleware::DEFAULT_COOKIE), |
||
274 | new Parser(), |
||
275 | 1000, |
||
276 | $clock, |
||
277 | 100 |
||
278 | ); |
||
279 | |||
280 | $requestWithTokenIssuedInThePast = (new ServerRequest()) |
||
281 | ->withCookieParams([ |
||
282 | SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder()) |
||
283 | ->setExpiration($time + 10000) |
||
284 | ->setIssuedAt($time - 100) |
||
285 | ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar'])) |
||
286 | ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware)) |
||
287 | ->getToken(), |
||
288 | ]); |
||
289 | |||
290 | $tokenString = $this |
||
291 | ->getCookie($middleware->process($requestWithTokenIssuedInThePast, $this->fakeDelegate(static function () { |
||
292 | return new Response(); |
||
293 | }))) |
||
294 | ->getValue(); |
||
295 | |||
296 | self::assertIsString($tokenString); |
||
297 | |||
298 | $token = (new Parser())->parse($tokenString); |
||
299 | |||
300 | self::assertEquals($time, $token->getClaim(SessionMiddleware::ISSUED_AT_CLAIM), 'Token was refreshed'); |
||
301 | } |
||
302 | |||
303 | /** |
||
304 | * @dataProvider validMiddlewaresProvider |
||
305 | */ |
||
306 | public function testWillSkipInjectingSessionCookiesWhenSessionIsNotChanged(SessionMiddleware $middleware) : void |
||
307 | { |
||
308 | $this->ensureSameResponse( |
||
309 | $middleware, |
||
310 | $this->requestWithResponseCookies( |
||
311 | $middleware->process(new ServerRequest(), $this->writingMiddleware()) |
||
312 | ), |
||
313 | $this->fakeDelegate( |
||
314 | static function (ServerRequestInterface $request) { |
||
315 | $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE); |
||
316 | assert($session instanceof SessionInterface); |
||
317 | |||
318 | // note: we set the same data just to make sure that we are indeed interacting with the session |
||
319 | $session->set('foo', 'bar'); |
||
320 | |||
321 | self::assertFalse($session->hasChanged()); |
||
322 | |||
323 | return new Response(); |
||
324 | } |
||
325 | ) |
||
326 | ); |
||
327 | } |
||
328 | |||
329 | /** |
||
330 | * @dataProvider validMiddlewaresProvider |
||
331 | */ |
||
332 | public function testWillSendExpirationCookieWhenSessionContentsAreCleared(SessionMiddleware $middleware) : void |
||
333 | { |
||
334 | $this->ensureClearsSessionCookie( |
||
335 | $middleware, |
||
336 | $this->requestWithResponseCookies( |
||
337 | $middleware->process(new ServerRequest(), $this->writingMiddleware()) |
||
338 | ), |
||
339 | $this->fakeDelegate( |
||
340 | static function (ServerRequestInterface $request) { |
||
341 | $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE); |
||
342 | assert($session instanceof SessionInterface); |
||
343 | |||
344 | $session->clear(); |
||
345 | |||
346 | return new Response(); |
||
347 | } |
||
348 | ) |
||
349 | ); |
||
350 | } |
||
351 | |||
352 | /** |
||
353 | * @dataProvider validMiddlewaresProvider |
||
354 | */ |
||
355 | public function testWillIgnoreMalformedTokens(SessionMiddleware $middleware) : void |
||
356 | { |
||
357 | $this->ensureSameResponse( |
||
358 | $middleware, |
||
359 | (new ServerRequest())->withCookieParams([SessionMiddleware::DEFAULT_COOKIE => 'malformed content']), |
||
360 | $this->emptyValidationMiddleware() |
||
361 | ); |
||
362 | } |
||
363 | |||
364 | public function testRejectsTokensWithInvalidSignature() : void |
||
365 | { |
||
366 | $middleware = new SessionMiddleware( |
||
367 | new Sha256(), |
||
368 | 'foo', |
||
369 | 'bar', // wrong symmetric key (on purpose) |
||
370 | SetCookie::create(SessionMiddleware::DEFAULT_COOKIE), |
||
371 | new Parser(), |
||
372 | 100, |
||
373 | new SystemClock() |
||
374 | ); |
||
375 | |||
376 | $this->ensureSameResponse( |
||
377 | $middleware, |
||
378 | $this->requestWithResponseCookies( |
||
379 | $middleware->process(new ServerRequest(), $this->writingMiddleware()) |
||
380 | ), |
||
381 | $this->emptyValidationMiddleware() |
||
382 | ); |
||
383 | } |
||
384 | |||
385 | public function testAllowsModifyingCookieDetails() : void |
||
386 | { |
||
387 | $defaultCookie = SetCookie::create('a-different-cookie-name') |
||
388 | ->withDomain('foo.bar') |
||
389 | ->withPath('/yadda') |
||
390 | ->withHttpOnly(false) |
||
391 | ->withMaxAge(123123) |
||
392 | ->withValue('a-random-value'); |
||
393 | |||
394 | $dateTime = new DateTimeImmutable(); |
||
395 | $middleware = new SessionMiddleware( |
||
396 | new Sha256(), |
||
397 | 'foo', |
||
398 | 'foo', |
||
399 | $defaultCookie, |
||
400 | new Parser(), |
||
401 | 123456, |
||
402 | new FrozenClock($dateTime), |
||
403 | 123 |
||
404 | ); |
||
405 | |||
406 | $response = $middleware->process(new ServerRequest(), $this->writingMiddleware()); |
||
407 | |||
408 | self::assertNull($this->getCookie($response)->getValue()); |
||
409 | |||
410 | $tokenCookie = $this->getCookie($response, 'a-different-cookie-name'); |
||
411 | |||
412 | self::assertNotEmpty($tokenCookie->getValue()); |
||
413 | self::assertNotSame($defaultCookie->getValue(), $tokenCookie->getValue()); |
||
414 | self::assertSame($defaultCookie->getDomain(), $tokenCookie->getDomain()); |
||
415 | self::assertSame($defaultCookie->getPath(), $tokenCookie->getPath()); |
||
416 | self::assertSame($defaultCookie->getHttpOnly(), $tokenCookie->getHttpOnly()); |
||
417 | self::assertSame($defaultCookie->getMaxAge(), $tokenCookie->getMaxAge()); |
||
418 | self::assertEquals($dateTime->getTimestamp() + 123456, $tokenCookie->getExpires()); |
||
419 | } |
||
420 | |||
421 | public function testSessionTokenParsingIsDelayedWhenSessionIsNotBeingUsed() : void |
||
422 | { |
||
423 | $signer = $this->createMock(Signer::class); |
||
424 | |||
425 | $signer->expects(self::never())->method('verify'); |
||
426 | $signer->method('getAlgorithmId')->willReturn('HS256'); |
||
427 | |||
428 | $currentTimeProvider = new SystemClock(); |
||
429 | $setCookie = SetCookie::create(SessionMiddleware::DEFAULT_COOKIE); |
||
430 | $middleware = new SessionMiddleware($signer, 'foo', 'foo', $setCookie, new Parser(), 100, $currentTimeProvider); |
||
431 | $request = (new ServerRequest()) |
||
432 | ->withCookieParams([ |
||
433 | SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder()) |
||
434 | ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar'])) |
||
435 | ->setIssuedAt(time()) |
||
436 | ->sign(new Sha256(), 'foo') |
||
437 | ->getToken(), |
||
438 | ]); |
||
439 | |||
440 | $middleware->process( |
||
441 | $request, |
||
442 | $this->fakeDelegate(static function (ServerRequestInterface $request) { |
||
443 | self::assertInstanceOf( |
||
444 | SessionInterface::class, |
||
445 | $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE) |
||
446 | ); |
||
447 | |||
448 | return new Response(); |
||
449 | }) |
||
450 | ); |
||
451 | } |
||
452 | |||
453 | public function testShouldRegenerateTokenWhenRequestHasATokenThatIsAboutToExpire() : void |
||
490 | |||
491 | public function testShouldNotRegenerateTokenWhenRequestHasATokenThatIsFarFromExpiration() : void |
||
516 | |||
517 | /** |
||
518 | * @return SessionMiddleware[][] |
||
519 | */ |
||
520 | public function validMiddlewaresProvider() : array |
||
542 | |||
543 | /** |
||
544 | * @group #46 |
||
545 | */ |
||
546 | public function testFromSymmetricKeyDefaultsWillHaveADefaultSessionPath() : void |
||
558 | |||
559 | public function testFromSymmetricKeyDefaultsWillHaveALaxSameSitePolicy() : void |
||
571 | |||
572 | /** |
||
573 | * @throws InvalidArgumentException |
||
574 | * @throws OutOfBoundsException |
||
575 | * |
||
576 | * @group #46 |
||
577 | */ |
||
578 | public function testFromAsymmetricKeyDefaultsWillHaveADefaultSessionPath() : void |
||
594 | |||
595 | public function testFromAsymmetricKeyDefaultsWillHaveALaxSameSitePolicy() : void |
||
611 | |||
612 | public function testMutableCookieWillNotBeUsed() : void |
||
613 | { |
||
614 | $cookie = MutableBadCookie::create(SessionMiddleware::DEFAULT_COOKIE); |
||
615 | |||
616 | assert($cookie instanceof MutableBadCookie); |
||
617 | |||
618 | $middleware = new SessionMiddleware( |
||
619 | new Sha256(), |
||
620 | 'foo', |
||
621 | 'foo', |
||
622 | $cookie, |
||
623 | new Parser(), |
||
624 | 1000, |
||
625 | new SystemClock() |
||
626 | ); |
||
627 | |||
628 | $cookie->mutated = true; |
||
629 | |||
637 | |||
638 | private function ensureSameResponse( |
||
672 | |||
673 | private function ensureClearsSessionCookie( |
||
687 | |||
688 | private function createToken(SessionMiddleware $middleware, DateTime $issuedAt, DateTime $expiration) : string |
||
697 | |||
698 | /** @param mixed $claim */ |
||
699 | private function createTokenWithCustomClaim( |
||
712 | |||
713 | private function emptyValidationMiddleware() : RequestHandlerInterface |
||
726 | |||
727 | private function writingMiddleware(string $value = 'bar') : RequestHandlerInterface |
||
739 | |||
740 | private function fakeDelegate(callable $callback) : RequestHandlerInterface |
||
752 | |||
753 | /** |
||
754 | * @return ServerRequest |
||
755 | */ |
||
756 | private function requestWithResponseCookies(ResponseInterface $response) : ServerRequestInterface |
||
762 | |||
763 | private function getCookie(ResponseInterface $response, string $name = SessionMiddleware::DEFAULT_COOKIE) : SetCookie |
||
767 | |||
768 | private function getSigner(SessionMiddleware $middleware) : Signer |
||
776 | |||
777 | private function getSignatureKey(SessionMiddleware $middleware) : string |
||
785 | |||
786 | private static function privateKey() : string |
||
794 | |||
795 | private static function publicKey() : string |
||
803 | } |
||
804 |
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.