Commit | Line | Data |
d91504e3 |
1 | use warnings; |
2 | use strict; |
b4037086 |
3 | use HTTP::Request::Common; |
d2b583c3 |
4 | use utf8; |
842180f7 |
5 | |
6 | BEGIN { |
7 | use Test::More; |
8748abc5 |
8 | eval "use Type::Tiny 1.000005; 1" || do { |
ea3943b8 |
9 | plan skip_all => "Trouble loading Type::Tiny and friends => $@"; |
842180f7 |
10 | }; |
ea3943b8 |
11 | } |
6f0b85d2 |
12 | |
ea3943b8 |
13 | BEGIN { |
6f0b85d2 |
14 | package MyApp::Types; |
15 | $INC{'MyApp/Types.pm'} = __FILE__; |
16 | |
17 | use strict; |
18 | use warnings; |
19 | |
20 | use Type::Utils -all; |
21 | use Types::Standard -types; |
22 | use Type::Library |
23 | -base, |
d2b583c3 |
24 | -declare => qw( UserId Heart User ContextLike ); |
6f0b85d2 |
25 | |
26 | extends "Types::Standard"; |
27 | |
28 | class_type User, { class => "MyApp::Model::User::user" }; |
29 | duck_type ContextLike, [qw/model/]; |
30 | |
31 | declare UserId, |
32 | as Int, |
33 | where { $_ < 5 }; |
34 | |
d2b583c3 |
35 | declare Heart, |
36 | as Str, |
37 | where { $_ eq '♥' }; |
38 | |
a7ab9aa9 |
39 | # Tests using this are skipped pending deeper thought |
6f0b85d2 |
40 | coerce User, |
41 | from ContextLike, |
42 | via { $_->model('User')->find( $_->req->args->[0] ) }; |
842180f7 |
43 | } |
d91504e3 |
44 | |
45 | { |
afa7a6c9 |
46 | package MyApp::Role::Controller; |
47 | $INC{'MyApp/Role/Controller.pm'} = __FILE__; |
48 | |
49 | use Moose::Role; |
50 | use MooseX::MethodAttributes::Role; |
51 | use MyApp::Types qw/Int Str/; |
52 | |
53 | sub role_str :Path('role_test') Args(Str) { |
54 | my ($self, $c, $arg) = @_; |
55 | $c->res->body('role_str'.$arg); |
56 | } |
57 | |
58 | sub role_int :Path('role_test') Args(Int) { |
59 | my ($self, $c, $arg) = @_; |
60 | $c->res->body('role_int'.$arg); |
61 | } |
62 | |
6f0b85d2 |
63 | package MyApp::Model::User; |
64 | $INC{'MyApp/Model/User.pm'} = __FILE__; |
65 | |
66 | use base 'Catalyst::Model'; |
67 | |
68 | our %users = ( |
69 | 1 => { name => 'john', age => 46 }, |
70 | 2 => { name => 'mary', age => 36 }, |
71 | 3 => { name => 'ian', age => 25 }, |
72 | 4 => { name => 'visha', age => 18 }, |
73 | ); |
74 | |
75 | sub find { |
76 | my ($self, $id) = @_; |
77 | my $user = $users{$id} || return; |
78 | return bless $user, "MyApp::Model::User::user"; |
79 | } |
80 | |
d91504e3 |
81 | package MyApp::Controller::Root; |
82 | $INC{'MyApp/Controller/Root.pm'} = __FILE__; |
83 | |
84 | use Moose; |
85 | use MooseX::MethodAttributes; |
d9f0a350 |
86 | use Types::Standard qw/slurpy/; |
d2b583c3 |
87 | use MyApp::Types qw/Tuple Int Str StrMatch ArrayRef UserId User Heart/; |
d91504e3 |
88 | |
89 | extends 'Catalyst::Controller'; |
afa7a6c9 |
90 | with 'MyApp::Role::Controller'; |
91 | |
d91504e3 |
92 | |
6f0b85d2 |
93 | sub user :Local Args(UserId) { |
94 | my ($self, $c, $int) = @_; |
95 | my $user = $c->model("User")->find($int); |
96 | $c->res->body("name: $user->{name}, age: $user->{age}"); |
97 | } |
98 | |
a7ab9aa9 |
99 | # Tests using this are current skipped pending coercion rethink |
6f0b85d2 |
100 | sub user_object :Local Args(User) Coerce(1) { |
101 | my ($self, $c, $user) = @_; |
102 | $c->res->body("name: $user->{name}, age: $user->{age}"); |
103 | } |
104 | |
75ce30d0 |
105 | sub stringy_enum :Local Args('Int',Int) { |
106 | my ($self, $c) = @_; |
107 | $c->res->body('enum'); |
108 | } |
109 | |
6d62355b |
110 | sub an_int :Local Args(Int) { |
111 | my ($self, $c, $int) = @_; |
6d62355b |
112 | $c->res->body('an_int'); |
113 | } |
114 | |
bf4f1643 |
115 | sub two_ints :Local Args(Int,Int) { |
116 | my ($self, $c, $int) = @_; |
117 | $c->res->body('two_ints'); |
118 | } |
119 | |
4a0218ca |
120 | sub many_ints :Local Args(ArrayRef[Int]) { |
d9f0a350 |
121 | my ($self, $c, @ints) = @_; |
4a0218ca |
122 | $c->res->body('many_ints'); |
123 | } |
124 | |
842180f7 |
125 | sub tuple :Local Args(Tuple[Str,Int]) { |
6f0b85d2 |
126 | my ($self, $c, $str, $int) = @_; |
842180f7 |
127 | $c->res->body('tuple'); |
128 | } |
129 | |
d9f0a350 |
130 | sub slurpy_tuple :Local Args(Tuple[Str,Int, slurpy ArrayRef[Int]]) { |
131 | my ($self, $c, $str, $int) = @_; |
132 | $c->res->body('tuple'); |
133 | } |
134 | |
6f0b85d2 |
135 | sub match :Local Args(StrMatch[qr{\d\d-\d\d-\d\d}]) { |
136 | my ($self, $c, $int) = @_; |
137 | $c->res->body('match'); |
138 | } |
a82c96cf |
139 | |
e5604544 |
140 | sub any_priority :Path('priority_test') Args(1) { $_[1]->res->body('any_priority') } |
842180f7 |
141 | |
b7791bd7 |
142 | sub int_priority :Path('priority_test') Args(Int) { $_[1]->res->body('int_priority') } |
e5604544 |
143 | |
a82c96cf |
144 | sub chain_base :Chained(/) CaptureArgs(1) { } |
145 | |
90102012 |
146 | sub any_priority_chain :GET Chained(chain_base) PathPart('') Args(1) { $_[1]->res->body('any_priority_chain') } |
a82c96cf |
147 | |
148 | sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) { $_[1]->res->body('int_priority_chain') } |
149 | |
480d94b5 |
150 | sub link_any :Chained(chain_base) PathPart('') CaptureArgs(1) { } |
151 | |
152 | sub any_priority_link_any :Chained(link_any) PathPart('') Args(1) { $_[1]->res->body('any_priority_link_any') } |
153 | |
154 | sub int_priority_link_any :Chained(link_any) PathPart('') Args(Int) { $_[1]->res->body('int_priority_link_any') } |
155 | |
a82c96cf |
156 | sub link_int :Chained(chain_base) PathPart('') CaptureArgs(Int) { } |
157 | |
158 | sub any_priority_link :Chained(link_int) PathPart('') Args(1) { $_[1]->res->body('any_priority_link') } |
159 | |
160 | sub int_priority_link :Chained(link_int) PathPart('') Args(Int) { $_[1]->res->body('int_priority_link') } |
161 | |
677c155c |
162 | sub link_int_int :Chained(chain_base) PathPart('') CaptureArgs(Int,Int) { } |
bf4f1643 |
163 | |
164 | sub any_priority_link2 :Chained(link_int_int) PathPart('') Args(1) { $_[1]->res->body('any_priority_link2') } |
165 | |
166 | sub int_priority_link2 :Chained(link_int_int) PathPart('') Args(Int) { $_[1]->res->body('int_priority_link2') } |
167 | |
677c155c |
168 | sub link_tuple :Chained(chain_base) PathPart('') CaptureArgs(Tuple[Int,Int,Int]) { } |
169 | |
170 | sub any_priority_link3 :Chained(link_tuple) PathPart('') Args(1) { $_[1]->res->body('any_priority_link3') } |
171 | |
172 | sub int_priority_link3 :Chained(link_tuple) PathPart('') Args(Int) { $_[1]->res->body('int_priority_link3') } |
173 | |
b6847871 |
174 | sub link2_int :Chained(link_tuple) PathPart('') CaptureArgs(UserId) { } |
175 | |
79b7db20 |
176 | sub finally2 :GET Chained(link2_int) PathPart('') Args { $_[1]->res->body('finally2') } |
90102012 |
177 | sub finally :GET Chained(link2_int) PathPart('') Args(Int) { $_[1]->res->body('finally') } |
a82c96cf |
178 | |
aef0cb5d |
179 | sub chain_base2 :Chained(/) CaptureArgs(1) { } |
180 | |
70949f28 |
181 | sub chained_zero_again : Chained(chain_base2) PathPart('') Args(0) { $_[1]->res->body('chained_zero_again') } |
182 | sub chained_zero_post2 : Chained(chain_base2) PathPart('') Args(0) { $_[1]->res->body('chained_zero_post2') } |
aef0cb5d |
183 | sub chained_zero2 : Chained(chain_base2) PathPart('') Args(0) { $_[1]->res->body('chained_zero2') } |
184 | |
70949f28 |
185 | sub chained_zero_post3 : Chained(chain_base2) PathPart('') Args(1) { $_[1]->res->body('chained_zero_post3') } |
aef0cb5d |
186 | sub chained_zero3 : Chained(chain_base2) PathPart('') Args(1) { $_[1]->res->body('chained_zero3') } |
187 | |
188 | |
d2b583c3 |
189 | sub heart :Local Args(Heart) { } |
190 | |
191 | sub utf8_base :Chained(/) CaptureArgs(Heart) { } |
192 | sub utf8_end :Chained(utf8_base) PathPart('') Args(Heart) { } |
193 | |
6d62355b |
194 | sub default :Default { |
195 | my ($self, $c, $int) = @_; |
196 | $c->res->body('default'); |
d91504e3 |
197 | } |
198 | |
199 | MyApp::Controller::Root->config(namespace=>''); |
200 | |
fc036112 |
201 | package MyApp::Controller::Autoclean; |
202 | $INC{'MyApp/Controller/Autoclean.pm'} = __FILE__; |
203 | |
204 | use Moose; |
205 | use MooseX::MethodAttributes; |
59051400 |
206 | use namespace::autoclean -except => 'Int'; |
fc036112 |
207 | |
208 | use MyApp::Types qw/Int/; |
209 | |
210 | extends 'Catalyst::Controller'; |
211 | |
212 | sub an_int :Local Args(Int) { |
213 | my ($self, $c, $int) = @_; |
214 | $c->res->body('an_int (autoclean)'); |
215 | } |
216 | |
217 | MyApp::Controller::Autoclean->config(namespace=>'autoclean'); |
218 | |
219 | package MyApp::Role; |
220 | $INC{'MyApp/Role.pm'} = __FILE__; |
221 | |
222 | use Moose::Role; |
223 | use MooseX::MethodAttributes::Role; |
50b07d60 |
224 | use MyApp::Types qw/Int/; |
fc036112 |
225 | |
226 | sub an_int :Local Args(Int) { |
227 | my ($self, $c, $int) = @_; |
228 | $c->res->body('an_int (withrole)'); |
229 | } |
230 | |
59051400 |
231 | sub an_int_ns :Local Args(MyApp::Types::Int) { |
232 | my ($self, $c, $int) = @_; |
233 | $c->res->body('an_int (withrole)'); |
234 | } |
235 | |
236 | package MyApp::BaseController; |
237 | $INC{'MyApp/BaseController.pm'} = __FILE__; |
238 | |
239 | use Moose; |
240 | use MooseX::MethodAttributes; |
241 | use MyApp::Types qw/Int/; |
242 | |
243 | extends 'Catalyst::Controller'; |
244 | |
245 | sub from_parent :Local Args(Int) { |
246 | my ($self, $c, $id) = @_; |
247 | $c->res->body("from_parent $id"); |
248 | } |
249 | |
fc036112 |
250 | package MyApp::Controller::WithRole; |
251 | $INC{'MyApp/Controller/WithRole.pm'} = __FILE__; |
252 | |
253 | use Moose; |
254 | use MooseX::MethodAttributes; |
255 | |
59051400 |
256 | extends 'MyApp::BaseController'; |
fc036112 |
257 | |
258 | with 'MyApp::Role'; |
259 | |
260 | MyApp::Controller::WithRole->config(namespace=>'withrole'); |
261 | |
d91504e3 |
262 | package MyApp; |
263 | use Catalyst; |
264 | |
265 | MyApp->setup; |
266 | } |
267 | |
268 | use Catalyst::Test 'MyApp'; |
269 | |
270 | { |
6d62355b |
271 | my $res = request '/an_int/1'; |
272 | is $res->content, 'an_int'; |
273 | } |
274 | |
275 | { |
337a627a |
276 | my $res = request '/an_int/aa'; |
277 | is $res->content, 'default'; |
278 | } |
279 | |
280 | { |
4a0218ca |
281 | my $res = request '/many_ints/1'; |
282 | is $res->content, 'many_ints'; |
283 | } |
284 | |
285 | { |
286 | my $res = request '/many_ints/1/2'; |
287 | is $res->content, 'many_ints'; |
288 | } |
289 | |
290 | { |
291 | my $res = request '/many_ints/1/2/3'; |
292 | is $res->content, 'many_ints'; |
293 | } |
294 | |
295 | { |
e5604544 |
296 | my $res = request '/priority_test/1'; |
297 | is $res->content, 'int_priority'; |
298 | } |
842180f7 |
299 | |
e5604544 |
300 | { |
301 | my $res = request '/priority_test/a'; |
302 | is $res->content, 'any_priority'; |
303 | } |
304 | |
842180f7 |
305 | { |
6f0b85d2 |
306 | my $res = request '/match/11-22-33'; |
307 | is $res->content, 'match'; |
308 | } |
81436df9 |
309 | |
6f0b85d2 |
310 | { |
311 | my $res = request '/match/aaa'; |
312 | is $res->content, 'default'; |
313 | } |
314 | |
315 | { |
316 | my $res = request '/user/2'; |
317 | is $res->content, 'name: mary, age: 36'; |
318 | } |
319 | |
320 | { |
321 | my $res = request '/user/20'; |
322 | is $res->content, 'default'; |
323 | } |
324 | |
a7ab9aa9 |
325 | |
326 | SKIP: { |
327 | skip "coercion support needs more thought", 1; |
6f0b85d2 |
328 | my $res = request '/user_object/20'; |
329 | is $res->content, 'default'; |
330 | } |
331 | |
a7ab9aa9 |
332 | SKIP: { |
333 | skip "coercion support needs more thought", 1; |
6f0b85d2 |
334 | my $res = request '/user_object/2'; |
335 | is $res->content, 'name: mary, age: 36'; |
336 | } |
337 | |
a82c96cf |
338 | { |
339 | my $res = request '/chain_base/capture/arg'; |
340 | is $res->content, 'any_priority_chain'; |
341 | } |
342 | |
343 | { |
344 | my $res = request '/chain_base/cap1/100/arg'; |
345 | is $res->content, 'any_priority_link'; |
346 | } |
347 | |
348 | { |
349 | my $res = request '/chain_base/cap1/101/102'; |
350 | is $res->content, 'int_priority_link'; |
351 | } |
352 | |
353 | { |
354 | my $res = request '/chain_base/capture/100'; |
355 | is $res->content, 'int_priority_chain', 'got expected'; |
356 | } |
357 | |
480d94b5 |
358 | { |
359 | my $res = request '/chain_base/cap1/a/arg'; |
360 | is $res->content, 'any_priority_link_any'; |
361 | } |
362 | |
363 | { |
364 | my $res = request '/chain_base/cap1/a/102'; |
365 | is $res->content, 'int_priority_link_any'; |
366 | } |
367 | |
bf4f1643 |
368 | { |
369 | my $res = request '/two_ints/1/2'; |
370 | is $res->content, 'two_ints'; |
371 | } |
372 | |
373 | { |
374 | my $res = request '/two_ints/aa/111'; |
375 | is $res->content, 'default'; |
376 | } |
377 | |
378 | { |
379 | my $res = request '/tuple/aaa/aaa'; |
380 | is $res->content, 'default'; |
381 | } |
382 | |
383 | { |
384 | my $res = request '/tuple/aaa/111'; |
385 | is $res->content, 'tuple'; |
386 | } |
387 | |
388 | { |
d9f0a350 |
389 | my $res = request '/tuple/aaa/111/111/111'; |
390 | is $res->content, 'default'; |
391 | } |
392 | |
393 | { |
394 | my $res = request '/slurpy_tuple/aaa/111/111/111'; |
395 | is $res->content, 'tuple'; |
396 | } |
397 | |
398 | |
399 | { |
bf4f1643 |
400 | my $res = request '/many_ints/1/2/a'; |
401 | is $res->content, 'default'; |
402 | } |
403 | |
404 | { |
405 | my $res = request '/chain_base/100/100/100/100'; |
406 | is $res->content, 'int_priority_link2'; |
407 | } |
408 | |
409 | { |
410 | my $res = request '/chain_base/100/ss/100/100'; |
411 | is $res->content, 'default'; |
412 | } |
413 | |
677c155c |
414 | { |
415 | my $res = request '/chain_base/100/100/100/100/100'; |
416 | is $res->content, 'int_priority_link3'; |
417 | } |
418 | |
419 | { |
420 | my $res = request '/chain_base/100/ss/100/100/100'; |
421 | is $res->content, 'default'; |
422 | } |
423 | |
79b7db20 |
424 | { |
425 | my $res = request '/chain_base/1/2/3/3/3/6'; |
426 | is $res->content, 'finally'; |
427 | } |
428 | |
429 | { |
430 | my $res = request '/chain_base/1/2/3/3/3/a'; |
431 | is $res->content, 'finally2'; |
432 | } |
bf4f1643 |
433 | |
79b7db20 |
434 | { |
435 | my $res = request '/chain_base/1/2/3/3/3/6/7/8/9'; |
436 | is $res->content, 'finally2'; |
437 | } |
438 | |
b4037086 |
439 | |
440 | { |
aef0cb5d |
441 | my $res = request PUT '/chain_base2/capture/1'; |
70949f28 |
442 | is $res->content, 'chained_zero3', "request PUT '/chain_base2/capture/1'"; |
aef0cb5d |
443 | } |
444 | |
445 | { |
446 | my $res = request '/chain_base2/capture/1'; |
70949f28 |
447 | is $res->content, 'chained_zero3', "request '/chain_base2/capture/1'"; |
aef0cb5d |
448 | } |
449 | |
450 | { |
451 | my $res = request POST '/chain_base2/capture/1'; |
70949f28 |
452 | is $res->content, 'chained_zero3', "request POST '/chain_base2/capture/1'"; |
aef0cb5d |
453 | } |
454 | |
455 | { |
456 | my $res = request PUT '/chain_base2/capture'; |
70949f28 |
457 | is $res->content, 'chained_zero2', "request PUT '/chain_base2/capture'"; |
b4037086 |
458 | } |
459 | |
460 | { |
aef0cb5d |
461 | my $res = request '/chain_base2/capture'; |
70949f28 |
462 | is $res->content, 'chained_zero2', "request '/chain_base2/capture'"; |
b4037086 |
463 | } |
464 | |
465 | { |
aef0cb5d |
466 | my $res = request POST '/chain_base2/capture'; |
70949f28 |
467 | is $res->content, 'chained_zero2', "request POST '/chain_base2/capture'"; |
b4037086 |
468 | } |
469 | |
75ce30d0 |
470 | { |
471 | my $res = request '/stringy_enum/1/2'; |
472 | is $res->content, 'enum', "request '/stringy_enum/a'"; |
473 | } |
474 | |
475 | { |
476 | my $res = request '/stringy_enum/b/2'; |
477 | is $res->content, 'default', "request '/stringy_enum/a'"; |
478 | } |
479 | |
480 | { |
481 | my $res = request '/stringy_enum/1/a'; |
482 | is $res->content, 'default', "request '/stringy_enum/a'"; |
483 | } |
484 | |
b4037086 |
485 | =over |
486 | |
487 | | /chain_base/*/*/*/*/*/* | /chain_base (1) |
488 | | | -> /link_tuple (Tuple[Int,Int,Int]) |
489 | | | -> /link2_int (UserId) |
490 | | | => GET /finally (Int) |
491 | |
b6847871 |
492 | =cut |
493 | |
494 | { |
495 | # URI testing |
496 | my ($res, $c) = ctx_request '/'; |
b6847871 |
497 | |
86a399db |
498 | { |
499 | ok my $url = eval { $c->uri_for($c->controller('Root')->action_for('user'), 2) }; |
500 | is $url, 'http://localhost/user/2'; |
501 | } |
c1192f1e |
502 | |
86a399db |
503 | { |
504 | ok my $url = eval { $c->uri_for($c->controller('Root')->action_for('user'), [2]) }; |
505 | is $url, 'http://localhost/user/2'; |
506 | } |
c1192f1e |
507 | |
86a399db |
508 | { |
509 | ok my $url = ! eval { $c->uri_for($c->controller('Root')->action_for('user'), [20]) }; |
510 | } |
511 | |
512 | { |
513 | ok my $url = eval { $c->uri_for($c->controller('Root')->action_for('finally'), [1,2,3,4,4],6) }; |
514 | is $url, 'http://localhost/chain_base/1/2/3/4/4/6'; |
515 | } |
516 | |
517 | { |
518 | ok my $url = eval { $c->uri_for($c->controller('Root')->action_for('finally'), [1,2,3,4,4,6]) }; |
519 | is $url, 'http://localhost/chain_base/1/2/3/4/4/6'; |
520 | } |
521 | |
522 | { |
523 | ok my $url = ! eval { $c->uri_for($c->controller('Root')->action_for('finally'), [1,2,3,4,5,6]) }; |
524 | } |
525 | |
526 | { |
527 | ok my $url = eval { $c->uri_for($c->controller('Root')->action_for('finally'), ['a',2,3,4,4,6]) }; |
528 | is $url, 'http://localhost/chain_base/a/2/3/4/4/6'; |
529 | } |
530 | |
531 | { |
532 | ok my $url = ! eval { $c->uri_for($c->controller('Root')->action_for('finally'), ['a','1',3,4,4,'a']) }; |
533 | } |
534 | |
535 | { |
536 | ok my $url = ! eval { $c->uri_for($c->controller('Root')->action_for('finally'), ['a','a',3,4,4,'6']) }; |
537 | } |
c1192f1e |
538 | |
d2b583c3 |
539 | { |
540 | ok my $url = eval { $c->uri_for($c->controller('Root')->action_for('heart'), ['♥']) }; |
541 | is $url, 'http://localhost/heart/%E2%99%A5'; |
542 | } |
cbe13760 |
543 | |
d2b583c3 |
544 | { |
545 | ok my $url = ! eval { $c->uri_for($c->controller('Root')->action_for('heart'), ['1']) }; |
546 | } |
86a399db |
547 | |
d2b583c3 |
548 | { |
549 | ok my $url = eval { $c->uri_for($c->controller('Root')->action_for('utf8_end'), ['♥','♥']) }; |
550 | is $url, 'http://localhost/utf8_base/%E2%99%A5/%E2%99%A5'; |
551 | } |
cbe13760 |
552 | |
d2b583c3 |
553 | { |
554 | ok my $url = ! eval { $c->uri_for($c->controller('Root')->action_for('utf8_end'), ['2','1']) }; |
555 | } |
cbe13760 |
556 | |
d2b583c3 |
557 | } |
86a399db |
558 | |
afa7a6c9 |
559 | # Test Roles |
560 | |
561 | { |
562 | my $res = request '/role_test/1'; |
563 | is $res->content, 'role_int1'; |
564 | } |
565 | |
566 | { |
567 | my $res = request '/role_test/a'; |
568 | is $res->content, 'role_stra'; |
569 | } |
570 | |
571 | |
59051400 |
572 | { |
573 | my $res = request '/autoclean/an_int/1'; |
574 | is $res->content, 'an_int (autoclean)'; |
575 | } |
576 | |
577 | { |
578 | my $res = request '/withrole/an_int_ns/S'; |
579 | is $res->content, 'default'; |
580 | } |
581 | |
582 | { |
583 | my $res = request '/withrole/an_int_ns/111'; |
584 | is $res->content, 'an_int (withrole)'; |
585 | } |
586 | |
587 | { |
588 | my $res = request '/withrole/an_int/1'; |
589 | is $res->content, 'an_int (withrole)'; |
590 | } |
591 | |
592 | { |
593 | my $res = request '/withrole/from_parent/1'; |
594 | is $res->content, 'from_parent 1'; |
595 | } |
d2b583c3 |
596 | done_testing; |