1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* League.Uri (https://uri.thephpleague.com) |
5
|
|
|
* |
6
|
|
|
* (c) Ignace Nyamagana Butera <[email protected]> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
declare(strict_types=1); |
13
|
|
|
|
14
|
|
|
namespace LeagueTest\Uri\UriTemplate; |
15
|
|
|
|
16
|
|
|
use League\Uri\Exceptions\SyntaxError; |
17
|
|
|
use League\Uri\Exceptions\TemplateCanNotBeExpanded; |
18
|
|
|
use League\Uri\UriTemplate\Expression; |
19
|
|
|
use League\Uri\UriTemplate\VariableBag; |
20
|
|
|
use PHPUnit\Framework\TestCase; |
21
|
|
|
use function var_export; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @coversDefaultClass \League\Uri\UriTemplate\Expression |
25
|
|
|
*/ |
26
|
|
|
final class ExpressionTest extends TestCase |
27
|
|
|
{ |
28
|
|
|
/** |
29
|
|
|
* @covers ::createFromString |
30
|
|
|
* @covers ::__construct |
31
|
|
|
* @covers ::toString |
32
|
|
|
* @covers ::variableNames |
33
|
|
|
* @covers ::setExpressionString |
34
|
|
|
* @covers ::setVariableNames |
35
|
|
|
* |
36
|
|
|
* @dataProvider providesValidNotation |
37
|
|
|
*/ |
38
|
|
|
public function testItCanBeInstantiatedWithAValidNotation(string $notation, array $variableNames): void |
39
|
|
|
{ |
40
|
|
|
$expression = Expression::createFromString($notation); |
41
|
|
|
self::assertSame($notation, $expression->toString()); |
42
|
|
|
self::assertSame($variableNames, $expression->variableNames()); |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
public function providesValidNotation(): iterable |
46
|
|
|
{ |
47
|
|
|
return [ |
48
|
|
|
'level 1' => ['notation' => '{var}', 'variableNames' => ['var']], |
49
|
|
|
'level 2' => ['notation' => '{+var}', 'variableNames' => ['var']], |
50
|
|
|
'level 3.1' => ['notation' => '{#var}', 'variableNames' => ['var']], |
51
|
|
|
'level 3.2' => ['notation' => '{x,y}', 'variableNames' => ['x', 'y']], |
52
|
|
|
'level 3.3' => ['notation' => '{+x,hello,y}', 'variableNames' => ['x', 'hello', 'y']], |
53
|
|
|
'level 3.4' => ['notation' => '{#path,x}', 'variableNames' => ['path', 'x']], |
54
|
|
|
'level 3.5' => ['notation' => '{.x,y}', 'variableNames' => ['x', 'y']], |
55
|
|
|
'level 3.6' => ['notation' => '{/var,x}', 'variableNames' => ['var', 'x']], |
56
|
|
|
'level 3.7' => ['notation' => '{;x,y,empty}', 'variableNames' => ['x', 'y', 'empty']], |
57
|
|
|
'level 3.8' => ['notation' => '{?x,y,undef}', 'variableNames' => ['x', 'y', 'undef']], |
58
|
|
|
'level 3.9' => ['notation' => '{&x,y,empty}', 'variableNames' => ['x', 'y', 'empty']], |
59
|
|
|
'level 4.1' => ['notation' => '{+path:6}', 'variableNames' => ['path']], |
60
|
|
|
'level 4.2' => ['notation' => '{var:3}', 'variableNames' => ['var']], |
61
|
|
|
'level 4.3' => ['notation' => '{#keys*}', 'variableNames' => ['keys']], |
62
|
|
|
'level 4.4' => ['notation' => '{/var:1,var}', 'variableNames' => ['var']], |
63
|
|
|
'level 4.5' => ['notation' => '{;keys*}', 'variableNames' => ['keys']], |
64
|
|
|
'level 4.6' => ['notation' => '{?var:3}', 'variableNames' => ['var']], |
65
|
|
|
'level 4.7' => ['notation' => '{.null}', 'variableNames' => ['null']], |
66
|
|
|
]; |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @covers ::__set_state |
71
|
|
|
* @covers \League\Uri\UriTemplate\Template |
72
|
|
|
*/ |
73
|
|
View Code Duplication |
public function testSetState(): void |
|
|
|
|
74
|
|
|
{ |
75
|
|
|
$expressionString = '{;keys*}'; |
76
|
|
|
|
77
|
|
|
$expression = Expression::createFromString($expressionString); |
78
|
|
|
|
79
|
|
|
self::assertEquals($expression, eval('return '.var_export($expression, true).';')); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* @covers ::createFromString |
84
|
|
|
* |
85
|
|
|
* @dataProvider providesInvalidExpression |
86
|
|
|
*/ |
87
|
|
|
public function testExpressionConstructFailsWithInvalidString(string $expression): void |
88
|
|
|
{ |
89
|
|
|
self::expectException(SyntaxError::class); |
90
|
|
|
|
91
|
|
|
Expression::createFromString($expression); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
public function providesInvalidExpression(): iterable |
95
|
|
|
{ |
96
|
|
|
return [ |
97
|
|
|
'missing content' => ['{}'], |
98
|
|
|
'missing delimiter' => ['foobar'], |
99
|
|
|
'reserved operator' => ['{|var*}'], |
100
|
|
|
'missing ending braces' => ['{/id*'], |
101
|
|
|
'missing starting braces' => ['/id*}'], |
102
|
|
|
'multiple starting operators' => ['{/?id}'], |
103
|
|
|
'invalid prefix' => ['{var:prefix}'], |
104
|
|
|
'multiple operator modifiers (1)' => ['{hello:2*}'] , |
105
|
|
|
'duplicate operator' => ['{??hello}'] , |
106
|
|
|
'reserved operator !' => ['{!hello}'] , |
107
|
|
|
'space inside variable name' => ['{with space}'], |
108
|
|
|
'leading space in variable name (1)' => ['{ leading_space}'], |
109
|
|
|
'trailing space in variable name' => ['{trailing_space }'], |
110
|
|
|
'reserved operator =' => ['{=path}'] , |
111
|
|
|
'forbidden operator $' => ['{$var}'], |
112
|
|
|
'reserved operator |' => ['{|var*}'], |
113
|
|
|
'using an operator modifier as an operator' => ['{*keys?}'], |
114
|
|
|
'variable name contains a reserved character (1)' => ['{?empty=default,var}'], |
115
|
|
|
'variable name contains invalid character (1)' => ['{-prefix|/-/|var}'], |
116
|
|
|
'variable name contains invalid prefix' => ['{example:color?}'], |
117
|
|
|
'variable name contains a reserved character (2)' => ['{?empty|foo=none}'], |
118
|
|
|
'variable name contains a reserved character (3)' => ['{#hello+}'], |
119
|
|
|
'variable name contains a reserved character (4)' => ['{hello+}'], |
120
|
|
|
'multiple operator modifiers (2)' => ['{;keys:1*}'], |
121
|
|
|
'variable name contains invalid character (2)' => ['{-join|&|var,list}'], |
122
|
|
|
'variable name contains invalid character (3)' => ['{~thing}'], |
123
|
|
|
'variable name contains invalid character (4)' => ['{default-graph-uri}'], |
124
|
|
|
'variable name contains invalid character (5)' => ['{?query,default-graph-uri}'], |
125
|
|
|
'variable name contains invalid character (6)' => ['{?query){&default-graph-uri*}'], |
126
|
|
|
'leading space in variable name (2)' => ['{?x, y}'], |
127
|
|
|
]; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* @covers ::expand |
132
|
|
|
* @covers ::replace |
133
|
|
|
* @covers ::inject |
134
|
|
|
* @covers ::replaceString |
135
|
|
|
* @covers ::replaceList |
136
|
|
|
* @covers ::decodeReserved |
137
|
|
|
* @covers ::isAssoc |
138
|
|
|
* |
139
|
|
|
* @dataProvider templateExpansionProvider |
140
|
|
|
*/ |
141
|
|
|
public function testExpandsUriTemplates(string $template, string $expectedUriString, array $variables): void |
142
|
|
|
{ |
143
|
|
|
self::assertSame($expectedUriString, Expression::createFromString($template)->expand(new VariableBag($variables))); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
public function templateExpansionProvider(): iterable |
147
|
|
|
{ |
148
|
|
|
$variables = [ |
149
|
|
|
'var' => 'value', |
150
|
|
|
'hello' => 'Hello World!', |
151
|
|
|
'empty' => '', |
152
|
|
|
'path' => '/foo/bar', |
153
|
|
|
'x' => '1024', |
154
|
|
|
'y' => '768', |
155
|
|
|
'null' => null, |
156
|
|
|
'list' => ['red', 'green', 'blue'], |
157
|
|
|
'keys' => [ |
158
|
|
|
'semi' => ';', |
159
|
|
|
'dot' => '.', |
160
|
|
|
'comma' => ',', |
161
|
|
|
], |
162
|
|
|
'empty_keys' => [], |
163
|
|
|
'bool' => true, |
164
|
|
|
]; |
165
|
|
|
|
166
|
|
|
$templateAndExpansionData = [ |
167
|
|
|
'level 1' => [ |
168
|
|
|
['{var}', 'value'], |
169
|
|
|
['{hello}', 'Hello%20World%21'], |
170
|
|
|
['{bool}', '1'], |
171
|
|
|
], |
172
|
|
|
'level 2' => [ |
173
|
|
|
['{+var}', 'value'], |
174
|
|
|
['{+hello}', 'Hello%20World!'], |
175
|
|
|
['{+path}', '/foo/bar'], |
176
|
|
|
], |
177
|
|
|
'level 3' => [ |
178
|
|
|
['{#var}', '#value'], |
179
|
|
|
['{#hello}', '#Hello%20World!'], |
180
|
|
|
['{x,y}', '1024,768'], |
181
|
|
|
['{x,hello,y}', '1024,Hello%20World%21,768'], |
182
|
|
|
['{+x,hello,y}', '1024,Hello%20World!,768'], |
183
|
|
|
['{+path,x}', '/foo/bar,1024'], |
184
|
|
|
['{#x,hello,y}', '#1024,Hello%20World!,768'], |
185
|
|
|
['{#path,x}', '#/foo/bar,1024'], |
186
|
|
|
['{.var}', '.value'], |
187
|
|
|
['{.x,y}', '.1024.768'], |
188
|
|
|
['{/var}', '/value'], |
189
|
|
|
['{/var,x}', '/value/1024'], |
190
|
|
|
['{;x,y}', ';x=1024;y=768'], |
191
|
|
|
['{;x,y,empty}', ';x=1024;y=768;empty'], |
192
|
|
|
['{?x,y}', '?x=1024&y=768'], |
193
|
|
|
['{?x,y,empty}', '?x=1024&y=768&empty='], |
194
|
|
|
['{?x,y,undef}', '?x=1024&y=768'], |
195
|
|
|
['{&x}', '&x=1024'], |
196
|
|
|
['{&x,y,empty}', '&x=1024&y=768&empty='], |
197
|
|
|
], |
198
|
|
|
'level 4' => [ |
199
|
|
|
['{var:3}', 'val'], |
200
|
|
|
['{var:30}', 'value'], |
201
|
|
|
['{list}', 'red,green,blue'], |
202
|
|
|
['{list*}', 'red,green,blue'], |
203
|
|
|
['{keys}', 'semi,%3B,dot,.,comma,%2C'], |
204
|
|
|
['{keys*}', 'semi=%3B,dot=.,comma=%2C'], |
205
|
|
|
['{+path:6}', '/foo/b'], |
206
|
|
|
['{+list}', 'red,green,blue'], |
207
|
|
|
['{+list*}', 'red,green,blue'], |
208
|
|
|
['{+keys}', 'semi,;,dot,.,comma,,'], |
209
|
|
|
['{+keys*}', 'semi=;,dot=.,comma=,'], |
210
|
|
|
['{#path:6}', '#/foo/b'], |
211
|
|
|
['{#list}', '#red,green,blue'], |
212
|
|
|
['{#list*}', '#red,green,blue'], |
213
|
|
|
['{#keys}', '#semi,;,dot,.,comma,,'], |
214
|
|
|
['{#keys*}', '#semi=;,dot=.,comma=,'], |
215
|
|
|
['{.var:3}', '.val'], |
216
|
|
|
['{.list}', '.red,green,blue'], |
217
|
|
|
['{.list*}', '.red.green.blue'], |
218
|
|
|
['{.keys}', '.semi,%3B,dot,.,comma,%2C'], |
219
|
|
|
['{.keys*}', '.semi=%3B.dot=..comma=%2C'], |
220
|
|
|
['{/var:1,var}', '/v/value'], |
221
|
|
|
['{/list}', '/red,green,blue'], |
222
|
|
|
['{/list*}', '/red/green/blue'], |
223
|
|
|
['{/list*,path:4}', '/red/green/blue/%2Ffoo'], |
224
|
|
|
['{/keys}', '/semi,%3B,dot,.,comma,%2C'], |
225
|
|
|
['{/keys*}', '/semi=%3B/dot=./comma=%2C'], |
226
|
|
|
['{;hello:5}', ';hello=Hello'], |
227
|
|
|
['{;list}', ';list=red,green,blue'], |
228
|
|
|
['{;list*}', ';list=red;list=green;list=blue'], |
229
|
|
|
['{;keys}', ';keys=semi,%3B,dot,.,comma,%2C'], |
230
|
|
|
['{;keys*}', ';semi=%3B;dot=.;comma=%2C'], |
231
|
|
|
['{?var:3}', '?var=val'], |
232
|
|
|
['{?list}', '?list=red,green,blue'], |
233
|
|
|
['{?list*}', '?list=red&list=green&list=blue'], |
234
|
|
|
['{?keys}', '?keys=semi,%3B,dot,.,comma,%2C'], |
235
|
|
|
['{?keys*}', '?semi=%3B&dot=.&comma=%2C'], |
236
|
|
|
['{&var:3}', '&var=val'], |
237
|
|
|
['{&list}', '&list=red,green,blue'], |
238
|
|
|
['{&list*}', '&list=red&list=green&list=blue'], |
239
|
|
|
['{&keys}', '&keys=semi,%3B,dot,.,comma,%2C'], |
240
|
|
|
['{&keys*}', '&semi=%3B&dot=.&comma=%2C'], |
241
|
|
|
['{.null}', ''], |
242
|
|
|
['{.null,var}', '.value'], |
243
|
|
|
['{.empty_keys*}', ''], |
244
|
|
|
['{.empty_keys}', ''], |
245
|
|
|
], |
246
|
|
|
'extra' => [ |
247
|
|
|
// Test that missing expansions are skipped |
248
|
|
|
['{&missing*}', ''], |
249
|
|
|
], |
250
|
|
|
]; |
251
|
|
|
|
252
|
|
View Code Duplication |
foreach ($templateAndExpansionData as $specification => $tests) { |
|
|
|
|
253
|
|
|
foreach ($tests as $offset => $test) { |
254
|
|
|
yield $specification.' test '.$offset => [ |
255
|
|
|
'template' => $test[0], |
256
|
|
|
'expectedUriString' => $test[1], |
257
|
|
|
'variables' => $variables, |
258
|
|
|
]; |
259
|
|
|
} |
260
|
|
|
} |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* @covers ::replaceList |
265
|
|
|
* @covers \League\Uri\Exceptions\TemplateCanNotBeExpanded |
266
|
|
|
* |
267
|
|
|
* @dataProvider invalidModifierToApply |
268
|
|
|
*/ |
269
|
|
|
public function testExpandThrowsExceptionIfTheModifierCanNotBeApplied(string $expression, array $variables): void |
270
|
|
|
{ |
271
|
|
|
self::expectException(TemplateCanNotBeExpanded::class); |
272
|
|
|
|
273
|
|
|
Expression::createFromString($expression)->expand(new VariableBag($variables)); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* Following negative tests with wrong variable can only be detected at runtime. |
278
|
|
|
* |
279
|
|
|
* @see https://github.com/uri-templates/uritemplate-test/blob/master/negative-tests.json |
280
|
|
|
*/ |
281
|
|
|
public function invalidModifierToApply(): iterable |
282
|
|
|
{ |
283
|
|
|
return [ |
284
|
|
|
'can not apply a modifier on a hash value (1)' => [ |
285
|
|
|
'expression' => '{keys:1}', |
286
|
|
|
'variables' => [ |
287
|
|
|
'keys' => [ |
288
|
|
|
'semi' => ';', |
289
|
|
|
'dot' => '.', |
290
|
|
|
'comma' => ',', |
291
|
|
|
], |
292
|
|
|
], |
293
|
|
|
], |
294
|
|
|
'can not apply a modifier on a hash value (2)' => [ |
295
|
|
|
'expression' => '{+keys:1}', |
296
|
|
|
'variables' => [ |
297
|
|
|
'keys' => [ |
298
|
|
|
'semi' => ';', |
299
|
|
|
'dot' => '.', |
300
|
|
|
'comma' => ',', |
301
|
|
|
], |
302
|
|
|
], |
303
|
|
|
], |
304
|
|
|
]; |
305
|
|
|
} |
306
|
|
|
} |
307
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.