ExtendedTokenTest   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 281
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 132
c 1
b 0
f 1
dl 0
loc 281
rs 9.76
wmc 33

32 Methods

Rating   Name   Duplication   Size   Complexity  
A testTokenHeaderContainsType() 0 6 1
A testTokenHeaderIsArray() 0 5 1
A testTokenConstructorWithEmptyString() 0 5 1
A testTokenConstructorWithFourParts() 0 5 1
A testIsExpiredWithNoExpClaim() 0 9 1
A testTokenHasPublicProperties() 0 7 1
A testGetClaimsWithVariousClaims() 0 8 1
A testIsExpiredWithMalformedToken() 0 5 1
A testTokenConstructorWithSinglePart() 0 4 1
A testTokenHeaderContainsAlgorithm() 0 6 1
A testGetSignedReturnsHeaderAndPayload() 0 10 1
A testIsExpiredWithTokenExpiringInFuture() 0 5 1
A testGetClaimsWithBooleanClaim() 0 9 1
A testIsExpiredWithTokenExpiringNow() 0 6 1
A testGetClaimsWithArrayClaim() 0 11 1
A testTokenPayloadIsArray() 0 5 1
A testGetClaimsWithNullClaim() 0 9 1
A testGetSignatureWithMalformedToken() 0 6 2
A testGetClaimsWithNumericClaim() 0 8 1
A testTokenConstructorWithInvalidJSON() 0 8 1
A testTokenConstructorWithInvalidBase64() 0 4 1
A testIsExpiredWithTokenExpiredInPast() 0 5 1
A testGetSignedExcludesSignature() 0 9 1
A testTokenConstructorWithOnlyDots() 0 4 1
A createJWTWithCustomExpiry() 0 11 1
A testIsExpiredWithVeryOldToken() 0 5 1
A testGetClaimsWithNonExistentClaim() 0 6 1
A testGetClaimsWithEmptyClaimName() 0 6 1
A testIsExpiredWithVeryFutureToken() 0 5 1
A testGetSignatureWithValidToken() 0 7 1
A testGetPayloadReturnsOriginalJWT() 0 6 1
A testGetPayloadWithMalformedToken() 0 6 1
1
<?php
2
3
/*
4
 * SPDX-License-Identifier: AGPL-3.0-only
5
 * SPDX-FileCopyrightText: Copyright 2025 grommunio GmbH
6
 *
7
 * Extended unit tests for Token class edge cases and additional scenarios
8
 */
9
10
use PHPUnit\Framework\TestCase;
0 ignored issues
show
Bug introduced by
The type PHPUnit\Framework\TestCase was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
11
12
/**
13
 * @internal
14
 *
15
 * @coversNothing
16
 */
17
class ExtendedTokenTest extends TestCase {
18
	private function createJWTWithCustomExpiry(int $expiryOffset): string {
19
		$header = base64_encode(json_encode(['alg' => 'HS256', 'typ' => 'JWT']));
20
		$payload = base64_encode(json_encode([
21
			'sub' => '1234567890',
22
			'name' => 'Test User',
23
			'exp' => time() + $expiryOffset,
24
			'iat' => time(),
25
		]));
26
		$signature = base64_encode('test-signature');
27
28
		return "{$header}.{$payload}.{$signature}";
29
	}
30
31
	// Constructor edge cases
32
	public function testTokenConstructorWithEmptyString(): void {
33
		$token = new Token('');
34
35
		$this->assertIsArray($token->token_payload);
36
		$this->assertArrayHasKey('expires_at', $token->token_payload);
37
	}
38
39
	public function testTokenConstructorWithOnlyDots(): void {
40
		$token = new Token('...');
41
42
		$this->assertIsArray($token->token_payload);
43
	}
44
45
	public function testTokenConstructorWithSinglePart(): void {
46
		$token = new Token('singlepart');
47
48
		$this->assertIsArray($token->token_payload);
49
	}
50
51
	public function testTokenConstructorWithFourParts(): void {
52
		$token = new Token('part1.part2.part3.part4');
53
54
		// Should still try to parse first 3 parts
55
		$this->assertIsArray($token->token_payload);
56
	}
57
58
	public function testTokenConstructorWithInvalidBase64(): void {
59
		$token = new Token('!!!.!!!.!!!');
60
61
		$this->assertIsArray($token->token_payload);
62
	}
63
64
	public function testTokenConstructorWithInvalidJSON(): void {
65
		$header = base64_encode('{invalid json}');
66
		$payload = base64_encode('{also invalid}');
67
		$signature = base64_encode('sig');
68
69
		$token = new Token("{$header}.{$payload}.{$signature}");
70
71
		$this->assertIsArray($token->token_payload);
72
	}
73
74
	// get_signature tests
75
	public function testGetSignatureWithValidToken(): void {
76
		$jwt = $this->createJWTWithCustomExpiry(3600);
77
		$token = new Token($jwt);
78
79
		$signature = $token->get_signature();
80
		$this->assertNotNull($signature);
81
		$this->assertIsString($signature);
82
	}
83
84
	public function testGetSignatureWithMalformedToken(): void {
85
		$token = new Token('malformed');
86
87
		$signature = $token->get_signature();
88
		// Should handle gracefully
89
		$this->assertTrue($signature === null || is_string($signature));
90
	}
91
92
	// get_signed tests
93
	public function testGetSignedReturnsHeaderAndPayload(): void {
94
		$jwt = $this->createJWTWithCustomExpiry(3600);
95
		$token = new Token($jwt);
96
97
		$signed = $token->get_signed();
98
		$this->assertIsString($signed);
99
		$this->assertStringContainsString('.', $signed);
100
101
		// Should have exactly one dot (header.payload)
102
		$this->assertEquals(1, substr_count($signed, '.'));
0 ignored issues
show
Bug introduced by
It seems like $signed can also be of type null; however, parameter $haystack of substr_count() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

102
		$this->assertEquals(1, substr_count(/** @scrutinizer ignore-type */ $signed, '.'));
Loading history...
103
	}
104
105
	public function testGetSignedExcludesSignature(): void {
106
		$jwt = $this->createJWTWithCustomExpiry(3600);
107
		$token = new Token($jwt);
108
109
		$signed = $token->get_signed();
110
		$parts = explode('.', $jwt);
111
112
		// Signed should not contain the signature part
113
		$this->assertStringNotContainsString($parts[2], $signed);
114
	}
115
116
	// get_payload tests
117
	public function testGetPayloadReturnsOriginalJWT(): void {
118
		$jwt = $this->createJWTWithCustomExpiry(3600);
119
		$token = new Token($jwt);
120
121
		$payload = $token->get_payload();
122
		$this->assertEquals($jwt, $payload);
123
	}
124
125
	public function testGetPayloadWithMalformedToken(): void {
126
		$malformed = 'malformed.token';
127
		$token = new Token($malformed);
128
129
		$payload = $token->get_payload();
130
		$this->assertEquals($malformed, $payload);
131
	}
132
133
	// get_claims tests
134
	public function testGetClaimsWithVariousClaims(): void {
135
		$jwt = $this->createJWTWithCustomExpiry(3600);
136
		$token = new Token($jwt);
137
138
		$this->assertEquals('Test User', $token->get_claims('name'));
139
		$this->assertEquals('1234567890', $token->get_claims('sub'));
140
		$this->assertIsInt($token->get_claims('exp'));
141
		$this->assertIsInt($token->get_claims('iat'));
142
	}
143
144
	public function testGetClaimsWithNonExistentClaim(): void {
145
		$jwt = $this->createJWTWithCustomExpiry(3600);
146
		$token = new Token($jwt);
147
148
		$this->assertEquals('', $token->get_claims('non_existent'));
149
		$this->assertEquals('', $token->get_claims('missing'));
150
	}
151
152
	public function testGetClaimsWithEmptyClaimName(): void {
153
		$jwt = $this->createJWTWithCustomExpiry(3600);
154
		$token = new Token($jwt);
155
156
		$result = $token->get_claims('');
157
		$this->assertEquals('', $result);
158
	}
159
160
	public function testGetClaimsWithNumericClaim(): void {
161
		$header = base64_encode(json_encode(['alg' => 'HS256']));
162
		$payload = base64_encode(json_encode(['numeric' => 12345]));
163
		$signature = base64_encode('sig');
164
165
		$token = new Token("{$header}.{$payload}.{$signature}");
166
167
		$this->assertEquals(12345, $token->get_claims('numeric'));
168
	}
169
170
	public function testGetClaimsWithBooleanClaim(): void {
171
		$header = base64_encode(json_encode(['alg' => 'HS256']));
172
		$payload = base64_encode(json_encode(['active' => true, 'deleted' => false]));
173
		$signature = base64_encode('sig');
174
175
		$token = new Token("{$header}.{$payload}.{$signature}");
176
177
		$this->assertTrue($token->get_claims('active'));
178
		$this->assertFalse($token->get_claims('deleted'));
179
	}
180
181
	public function testGetClaimsWithNullClaim(): void {
182
		$header = base64_encode(json_encode(['alg' => 'HS256']));
183
		$payload = base64_encode(json_encode(['null_value' => null]));
184
		$signature = base64_encode('sig');
185
186
		$token = new Token("{$header}.{$payload}.{$signature}");
187
188
		$result = $token->get_claims('null_value');
189
		$this->assertNull($result);
190
	}
191
192
	public function testGetClaimsWithArrayClaim(): void {
193
		$header = base64_encode(json_encode(['alg' => 'HS256']));
194
		$payload = base64_encode(json_encode(['roles' => ['admin', 'user']]));
195
		$signature = base64_encode('sig');
196
197
		$token = new Token("{$header}.{$payload}.{$signature}");
198
199
		$roles = $token->get_claims('roles');
200
		$this->assertIsArray($roles);
201
		$this->assertContains('admin', $roles);
202
		$this->assertContains('user', $roles);
203
	}
204
205
	// is_expired tests
206
	public function testIsExpiredWithTokenExpiringInFuture(): void {
207
		$jwt = $this->createJWTWithCustomExpiry(7200); // 2 hours
208
		$token = new Token($jwt);
209
210
		$this->assertFalse($token->is_expired());
211
	}
212
213
	public function testIsExpiredWithTokenExpiredInPast(): void {
214
		$jwt = $this->createJWTWithCustomExpiry(-7200); // 2 hours ago
215
		$token = new Token($jwt);
216
217
		$this->assertTrue($token->is_expired());
218
	}
219
220
	public function testIsExpiredWithTokenExpiringNow(): void {
221
		$jwt = $this->createJWTWithCustomExpiry(0); // Expires right now
222
		$token = new Token($jwt);
223
224
		// Should be considered expired (not > current time)
225
		$this->assertTrue($token->is_expired());
226
	}
227
228
	public function testIsExpiredWithNoExpClaim(): void {
229
		$header = base64_encode(json_encode(['alg' => 'HS256']));
230
		$payload = base64_encode(json_encode(['sub' => 'test'])); // No exp claim
231
		$signature = base64_encode('sig');
232
233
		$token = new Token("{$header}.{$payload}.{$signature}");
234
235
		// Without exp claim, should check expires_at
236
		$this->assertIsBool($token->is_expired());
237
	}
238
239
	public function testIsExpiredWithMalformedToken(): void {
240
		$token = new Token('malformed');
241
242
		// Should have default expires_at of 0, so expired
243
		$this->assertTrue($token->is_expired());
244
	}
245
246
	public function testIsExpiredWithVeryOldToken(): void {
247
		$jwt = $this->createJWTWithCustomExpiry(-86400 * 365); // 1 year ago
248
		$token = new Token($jwt);
249
250
		$this->assertTrue($token->is_expired());
251
	}
252
253
	public function testIsExpiredWithVeryFutureToken(): void {
254
		$jwt = $this->createJWTWithCustomExpiry(86400 * 365); // 1 year from now
255
		$token = new Token($jwt);
256
257
		$this->assertFalse($token->is_expired());
258
	}
259
260
	// Token properties tests
261
	public function testTokenHasPublicProperties(): void {
262
		$jwt = $this->createJWTWithCustomExpiry(3600);
263
		$token = new Token($jwt);
264
265
		$this->assertObjectHasProperty('token_header', $token);
266
		$this->assertObjectHasProperty('token_payload', $token);
267
		$this->assertObjectHasProperty('signed', $token);
268
	}
269
270
	public function testTokenHeaderIsArray(): void {
271
		$jwt = $this->createJWTWithCustomExpiry(3600);
272
		$token = new Token($jwt);
273
274
		$this->assertIsArray($token->token_header);
275
	}
276
277
	public function testTokenPayloadIsArray(): void {
278
		$jwt = $this->createJWTWithCustomExpiry(3600);
279
		$token = new Token($jwt);
280
281
		$this->assertIsArray($token->token_payload);
282
	}
283
284
	public function testTokenHeaderContainsAlgorithm(): void {
285
		$jwt = $this->createJWTWithCustomExpiry(3600);
286
		$token = new Token($jwt);
287
288
		$this->assertArrayHasKey('alg', $token->token_header);
289
		$this->assertEquals('HS256', $token->token_header['alg']);
290
	}
291
292
	public function testTokenHeaderContainsType(): void {
293
		$jwt = $this->createJWTWithCustomExpiry(3600);
294
		$token = new Token($jwt);
295
296
		$this->assertArrayHasKey('typ', $token->token_header);
297
		$this->assertEquals('JWT', $token->token_header['typ']);
298
	}
299
}
300