Skip to content

Commit 52eee94

Browse files
authored
Merge pull request #47 from itk-dev/test/mutation-cli-login
test: pin down CLI login token lifecycle and URL generation
2 parents a2fd951 + cab60df commit 52eee94

3 files changed

Lines changed: 68 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818

1919
### Changed
2020

21+
- Dev: strengthened CLI login flow tests based on mutation testing
22+
findings — redeeming an unknown token is asserted to throw
23+
`TokenNotFoundException` specifically, both cache entries (token and
24+
reverse username entry) are asserted removed after a token is used,
25+
`encodeKey` asserts the exact namespaced encoding instead of only an
26+
encode/decode roundtrip, and the CLI login URL is asserted to receive
27+
the login token and route. No effect on the published package.
2128
- Dev: added a test for `ItkDevOpenIdConnectBundle::getContainerExtension()`
2229
asserting the custom extension is created and memoized (same instance on
2330
repeated calls), prompted by mutation testing findings. No effect on the

tests/Command/UserLoginCommandTest.php

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,36 @@ public function testExecuteSuccess(): void
4444

4545
$this->stubUrlGenerator
4646
->method('generate')
47-
->willReturn('https://app.com/login?loginToken=generated-token');
47+
->willReturn('https://app.example.org/login?loginToken=generated-token');
4848

4949
$tester = new CommandTester($this->command);
5050
$result = $tester->execute(['username' => 'testuser']);
5151

5252
$this->assertSame(Command::SUCCESS, $result);
53-
$this->assertStringContainsString('https://app.com/login?loginToken=generated-token', $tester->getDisplay());
53+
$this->assertStringContainsString('https://app.example.org/login?loginToken=generated-token', $tester->getDisplay());
54+
}
55+
56+
public function testExecutePassesTokenAndRouteToUrlGenerator(): void
57+
{
58+
$this->stubCliLoginHelper
59+
->method('createToken')
60+
->willReturn('generated-token');
61+
62+
$urlGenerator = $this->createMock(UrlGeneratorInterface::class);
63+
$urlGenerator->expects($this->once())
64+
->method('generate')
65+
->with('cli_login_route', ['loginToken' => 'generated-token'], UrlGeneratorInterface::ABSOLUTE_URL)
66+
->willReturn('https://app.example.org/login?loginToken=generated-token');
67+
68+
$command = new UserLoginCommand(
69+
$this->stubCliLoginHelper,
70+
'cli_login_route',
71+
$urlGenerator,
72+
$this->stubUserProvider
73+
);
74+
75+
$tester = new CommandTester($command);
76+
$this->assertSame(Command::SUCCESS, $tester->execute(['username' => 'testuser']));
5477
}
5578

5679
public function testExecuteUserNotFound(): void

tests/Util/CliLoginHelperTest.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use ItkDev\OpenIdConnectBundle\Exception\CacheException;
66
use ItkDev\OpenIdConnectBundle\Exception\OpenIdConnectBundleExceptionInterface;
7+
use ItkDev\OpenIdConnectBundle\Exception\TokenNotFoundException;
78
use ItkDev\OpenIdConnectBundle\Util\CliLoginHelper;
89
use PHPUnit\Framework\TestCase;
910
use Psr\Cache\CacheItemInterface;
@@ -40,7 +41,10 @@ public function testDecodeKeyReturnsInputWhenNotValidBase64(): void
4041

4142
public function testThrowExceptionIfTokenDoesNotExist(): void
4243
{
43-
$this->expectException(OpenIdConnectBundleExceptionInterface::class);
44+
// TokenNotFoundException (not just the marker interface) is part of
45+
// the public contract: CliLoginTokenAuthenticator catches it
46+
// specifically to distinguish "no such token" from cache failures.
47+
$this->expectException(TokenNotFoundException::class);
4448

4549
$cache = new ArrayAdapter();
4650

@@ -79,6 +83,37 @@ public function testTokenIsRemovedAfterUse(): void
7983
$cliHelper->getUsername($token);
8084
}
8185

86+
public function testBothCacheEntriesAreRemovedAfterUse(): void
87+
{
88+
$cache = new ArrayAdapter();
89+
90+
$cliHelper = new CliLoginHelper($cache);
91+
92+
$testUser = 'test_user';
93+
$token = $cliHelper->createToken($testUser);
94+
95+
$this->assertEquals($testUser, $cliHelper->getUsername($token));
96+
97+
// The reverse entry (username => token) must be gone too; otherwise
98+
// createToken() would hand out the already-redeemed token again.
99+
$this->assertFalse($cache->hasItem($cliHelper->encodeKey($testUser)));
100+
101+
$newToken = $cliHelper->createToken($testUser);
102+
$this->assertNotSame($token, $newToken);
103+
$this->assertEquals($testUser, $cliHelper->getUsername($newToken));
104+
}
105+
106+
public function testEncodeKeyPrependsNamespace(): void
107+
{
108+
$cache = new ArrayAdapter();
109+
$cliHelper = new CliLoginHelper($cache);
110+
111+
// Assert the exact encoding, not just an encode/decode roundtrip:
112+
// the namespace prefix guards against cache key collisions with the
113+
// consuming application, and a roundtrip is blind to losing it.
114+
$this->assertSame(base64_encode('itk-dev-cli-logintest_user'), $cliHelper->encodeKey('test_user'));
115+
}
116+
82117
public function testCreateTokenAndGetUsername(): void
83118
{
84119
$cache = new ArrayAdapter();

0 commit comments

Comments
 (0)