Commit | Line | Data |
0ca510f0 |
1 | use utf8; |
2 | use warnings; |
3 | use strict; |
4 | use Test::More; |
b9d96e27 |
5 | use HTTP::Request::Common; |
59e11cd7 |
6 | use Encode 2.21 'decode_utf8', 'encode_utf8'; |
7 | use File::Spec; |
12982f86 |
8 | use JSON::MaybeXS; |
0ca510f0 |
9 | |
10 | # Test cases for incoming utf8 |
11 | |
12 | { |
13 | package MyApp::Controller::Root; |
14 | $INC{'MyApp/Controller/Root.pm'} = __FILE__; |
15 | |
16 | use base 'Catalyst::Controller'; |
17 | |
18 | sub heart :Path('♥') { |
19 | my ($self, $c) = @_; |
20 | $c->response->content_type('text/html'); |
21 | $c->response->body("<p>This is path-heart action ♥</p>"); |
22 | # We let the content length middleware find the length... |
23 | } |
24 | |
25 | sub hat :Path('^') { |
26 | my ($self, $c) = @_; |
27 | $c->response->content_type('text/html'); |
28 | $c->response->body("<p>This is path-hat action ^</p>"); |
29 | } |
30 | |
e5a5e80b |
31 | sub uri_for :Path('uri_for') { |
32 | my ($self, $c) = @_; |
33 | $c->response->content_type('text/html'); |
34 | $c->response->body("${\$c->uri_for($c->controller('Root')->action_for('argend'), ['♥'], '♥', {'♥'=>'♥♥'})}"); |
35 | } |
36 | |
37 | sub heart_with_arg :Path('a♥') Args(1) { |
38 | my ($self, $c, $arg) = @_; |
39 | $c->response->content_type('text/html'); |
40 | $c->response->body("<p>This is path-heart-arg action $arg</p>"); |
41 | Test::More::is $c->req->args->[0], '♥'; |
42 | } |
43 | |
0ca510f0 |
44 | sub base :Chained('/') CaptureArgs(0) { } |
45 | sub link :Chained('base') PathPart('♥') Args(0) { |
46 | my ($self, $c) = @_; |
47 | $c->response->content_type('text/html'); |
48 | $c->response->body("<p>This is base-link action ♥</p>"); |
49 | } |
e5a5e80b |
50 | sub arg :Chained('base') PathPart('♥') Args(1) { |
51 | my ($self, $c, $arg) = @_; |
52 | $c->response->content_type('text/html'); |
53 | $c->response->body("<p>This is base-link action ♥ $arg</p>"); |
54 | } |
55 | sub capture :Chained('base') PathPart('♥') CaptureArgs(1) { |
56 | my ($self, $c, $arg) = @_; |
57 | $c->stash(capture=>$arg); |
58 | } |
59 | sub argend :Chained('capture') PathPart('♥') Args(1) { |
60 | my ($self, $c, $arg) = @_; |
61 | $c->response->content_type('text/html'); |
62 | |
63 | Test::More::is $c->req->args->[0], '♥'; |
64 | Test::More::is $c->req->captures->[0], '♥'; |
65 | |
66 | $c->response->body("<p>This is base-link action ♥ ${\$c->req->args->[0]}</p>"); |
dd096a3a |
67 | |
68 | # Test to make sure redirect can now take an object (sorry don't have a better place for it |
69 | # but wanted test coverage. |
70 | my $location = $c->res->redirect( $c->uri_for($c->controller('Root')->action_for('uri_for')) ); |
71 | Test::More::ok !ref $location; |
e5a5e80b |
72 | } |
0ca510f0 |
73 | |
dd096a3a |
74 | sub stream_write :Local { |
75 | my ($self, $c) = @_; |
76 | $c->response->content_type('text/html'); |
77 | $c->response->write("<p>This is stream_write action ♥</p>"); |
78 | } |
79 | |
fe1dfeaf |
80 | sub stream_write_fh :Local { |
81 | my ($self, $c) = @_; |
82 | $c->response->content_type('text/html'); |
83 | |
84 | my $writer = $c->res->write_fh; |
e8361cf8 |
85 | $writer->write_encoded('<p>This is stream_write_fh action ♥</p>'); |
59e11cd7 |
86 | $writer->close; |
87 | } |
88 | |
e8361cf8 |
89 | # Stream a file with utf8 chars directly, you don't need to decode |
59e11cd7 |
90 | sub stream_body_fh :Local { |
91 | my ($self, $c) = @_; |
59e11cd7 |
92 | my $path = File::Spec->catfile('t', 'utf8.txt'); |
93 | open(my $fh, '<', $path) || die "trouble: $!"; |
94 | $c->response->content_type('text/html'); |
95 | $c->response->body($fh); |
fe1dfeaf |
96 | } |
97 | |
e8361cf8 |
98 | # If you pull the file contents into a var, NOW you need to specify the |
99 | # IO encoding on the FH. Ultimately Plack at the end wants bytes... |
100 | sub stream_body_fh2 :Local { |
101 | my ($self, $c) = @_; |
102 | my $path = File::Spec->catfile('t', 'utf8.txt'); |
103 | open(my $fh, '<:encoding(UTF-8)', $path) || die "trouble: $!"; |
104 | my $contents = do { local $/; <$fh> }; |
105 | |
106 | $c->response->content_type('text/html'); |
107 | $c->response->body($contents); |
108 | } |
109 | |
12982f86 |
110 | sub file_upload :POST Consumes(Multipart) Local { |
111 | my ($self, $c) = @_; |
112 | Test::More::is $c->req->body_parameters->{'♥'}, '♥♥'; |
113 | Test::More::ok my $upload = $c->req->uploads->{file}; |
114 | |
115 | my $text = $upload->slurp; |
116 | Test::More::is Encode::decode_utf8($text), "<p>This is stream_body_fh action ♥</p>\n"; |
117 | |
118 | $c->response->content_type('text/html'); |
119 | $c->response->body($upload->fh); |
120 | } |
121 | |
122 | sub json :POST Consumes(JSON) Local { |
123 | my ($self, $c) = @_; |
124 | my $post = $c->req->body_data; |
125 | |
126 | Test::More::is $post->{'♥'}, '♥♥'; |
127 | $c->response->content_type('application/json'); |
128 | |
129 | # Encode JSON also encodes to a UTF-8 encoded, binary string. This is why we don't |
130 | # have application/json as one of the things we match, otherwise we get double |
131 | # encoding. |
132 | $c->response->body(JSON::MaybeXS::encode_json($post)); |
133 | } |
e8361cf8 |
134 | |
0ca510f0 |
135 | package MyApp; |
136 | use Catalyst; |
137 | |
7b39dea1 |
138 | # Default encoding is now UTF-8 |
139 | # MyApp->config(encoding=>'UTF-8'); |
0ca510f0 |
140 | |
141 | Test::More::ok(MyApp->setup, 'setup app'); |
142 | } |
143 | |
144 | ok my $psgi = MyApp->psgi_app, 'build psgi app'; |
145 | |
146 | use Catalyst::Test 'MyApp'; |
0ca510f0 |
147 | |
148 | { |
149 | my $res = request "/root/♥"; |
150 | |
151 | is $res->code, 200, 'OK'; |
152 | is decode_utf8($res->content), '<p>This is path-heart action ♥</p>', 'correct body'; |
153 | is $res->content_length, 36, 'correct length'; |
4a64c27b |
154 | is $res->content_charset, 'UTF-8'; |
0ca510f0 |
155 | } |
156 | |
157 | { |
e5a5e80b |
158 | my $res = request "/root/a♥/♥"; |
159 | |
160 | is $res->code, 200, 'OK'; |
161 | is decode_utf8($res->content), '<p>This is path-heart-arg action ♥</p>', 'correct body'; |
162 | is $res->content_length, 40, 'correct length'; |
4a64c27b |
163 | is $res->content_charset, 'UTF-8'; |
e5a5e80b |
164 | } |
165 | |
166 | { |
0ca510f0 |
167 | my $res = request "/root/^"; |
168 | |
169 | is $res->code, 200, 'OK'; |
170 | is decode_utf8($res->content), '<p>This is path-hat action ^</p>', 'correct body'; |
171 | is $res->content_length, 32, 'correct length'; |
4a64c27b |
172 | is $res->content_charset, 'UTF-8'; |
0ca510f0 |
173 | } |
174 | |
175 | { |
176 | my $res = request "/base/♥"; |
177 | |
178 | is $res->code, 200, 'OK'; |
179 | is decode_utf8($res->content), '<p>This is base-link action ♥</p>', 'correct body'; |
180 | is $res->content_length, 35, 'correct length'; |
4a64c27b |
181 | is $res->content_charset, 'UTF-8'; |
0ca510f0 |
182 | } |
183 | |
184 | { |
b9d96e27 |
185 | my ($res, $c) = ctx_request POST "/base/♥?♥=♥&♥=♥♥", [a=>1, b=>'', '♥'=>'♥', '♥'=>'♥♥']; |
0ca510f0 |
186 | |
187 | is $res->code, 200, 'OK'; |
188 | is decode_utf8($res->content), '<p>This is base-link action ♥</p>', 'correct body'; |
189 | is $res->content_length, 35, 'correct length'; |
b9d96e27 |
190 | is $c->req->parameters->{'♥'}[0], '♥'; |
191 | is $c->req->query_parameters->{'♥'}[0], '♥'; |
192 | is $c->req->body_parameters->{'♥'}[0], '♥'; |
193 | is $c->req->parameters->{'♥'}[0], '♥'; |
4a62800d |
194 | is $c->req->parameters->{a}, 1; |
195 | is $c->req->body_parameters->{a}, 1; |
4a64c27b |
196 | is $res->content_charset, 'UTF-8'; |
e5a5e80b |
197 | } |
4a62800d |
198 | |
e5a5e80b |
199 | { |
200 | my ($res, $c) = ctx_request GET "/base/♥?♥♥♥"; |
4a62800d |
201 | |
e5a5e80b |
202 | is $res->code, 200, 'OK'; |
203 | is decode_utf8($res->content), '<p>This is base-link action ♥</p>', 'correct body'; |
204 | is $res->content_length, 35, 'correct length'; |
205 | is $c->req->query_keywords, '♥♥♥'; |
4a64c27b |
206 | is $res->content_charset, 'UTF-8'; |
0ca510f0 |
207 | } |
208 | |
e5a5e80b |
209 | { |
210 | my $res = request "/base/♥/♥"; |
211 | |
212 | is $res->code, 200, 'OK'; |
213 | is decode_utf8($res->content), '<p>This is base-link action ♥ ♥</p>', 'correct body'; |
214 | is $res->content_length, 39, 'correct length'; |
4a64c27b |
215 | is $res->content_charset, 'UTF-8'; |
e5a5e80b |
216 | } |
b9d96e27 |
217 | |
e5a5e80b |
218 | { |
219 | my $res = request "/base/♥/♥/♥/♥"; |
220 | |
e5a5e80b |
221 | is decode_utf8($res->content), '<p>This is base-link action ♥ ♥</p>', 'correct body'; |
222 | is $res->content_length, 39, 'correct length'; |
4a64c27b |
223 | is $res->content_charset, 'UTF-8'; |
e5a5e80b |
224 | } |
225 | |
226 | { |
227 | my ($res, $c) = ctx_request POST "/base/♥/♥/♥/♥?♥=♥♥", [a=>1, b=>'2', '♥'=>'♥♥']; |
228 | |
229 | ## Make sure that the urls we generate work the same |
b063a165 |
230 | my $uri_for1 = $c->uri_for($c->controller('Root')->action_for('argend'), ['♥'], '♥', {'♥'=>'♥♥'}); |
231 | my $uri_for2 = $c->uri_for($c->controller('Root')->action_for('argend'), ['♥', '♥'], {'♥'=>'♥♥'}); |
e5a5e80b |
232 | my $uri = $c->req->uri; |
233 | |
b063a165 |
234 | is "$uri_for1", "$uri_for2"; |
235 | is "$uri", "$uri_for1"; |
e5a5e80b |
236 | |
237 | { |
b063a165 |
238 | my ($res, $c) = ctx_request POST "$uri_for1", [a=>1, b=>'2', '♥'=>'♥♥']; |
e5a5e80b |
239 | is $c->req->query_parameters->{'♥'}, '♥♥'; |
240 | is $c->req->body_parameters->{'♥'}, '♥♥'; |
241 | is $c->req->parameters->{'♥'}[0], '♥♥'; #combined with query and body |
4a64c27b |
242 | is $res->content_charset, 'UTF-8'; |
e5a5e80b |
243 | } |
244 | } |
245 | |
246 | { |
247 | my ($res, $c) = ctx_request "/root/uri_for"; |
248 | my $url = $c->uri_for($c->controller('Root')->action_for('argend'), ['♥'], '♥', {'♥'=>'♥♥'}); |
249 | |
250 | is $res->code, 200, 'OK'; |
251 | is decode_utf8($res->content), "$url", 'correct body'; #should do nothing |
252 | is $res->content, "$url", 'correct body'; |
253 | is $res->content_length, 90, 'correct length'; |
4a64c27b |
254 | is $res->content_charset, 'UTF-8'; |
b063a165 |
255 | |
256 | { |
257 | my $url = $c->uri_for($c->controller->action_for('heart_with_arg'), '♥'); |
258 | is "$url", 'http://localhost/root/a%E2%99%A5/%E2%99%A5'; |
259 | } |
260 | |
261 | { |
262 | my $url = $c->uri_for($c->controller->action_for('heart_with_arg'), ['♥']); |
263 | is "$url", 'http://localhost/root/a%E2%99%A5/%E2%99%A5'; |
264 | } |
dd096a3a |
265 | } |
266 | |
267 | { |
268 | my $res = request "/root/stream_write"; |
00038a21 |
269 | |
dd096a3a |
270 | is $res->code, 200, 'OK'; |
271 | is decode_utf8($res->content), '<p>This is stream_write action ♥</p>', 'correct body'; |
4a64c27b |
272 | is $res->content_charset, 'UTF-8'; |
e5a5e80b |
273 | } |
0ca510f0 |
274 | |
fe1dfeaf |
275 | { |
59e11cd7 |
276 | my $res = request "/root/stream_body_fh"; |
277 | |
278 | is $res->code, 200, 'OK'; |
279 | is decode_utf8($res->content), "<p>This is stream_body_fh action ♥</p>\n", 'correct body'; |
4a64c27b |
280 | is $res->content_charset, 'UTF-8'; |
59e11cd7 |
281 | # Not sure why there is a trailing newline above... its not in catalyst code I can see. Not sure |
282 | # if is a problem or just an artifact of the why the test stuff works - JNAP |
283 | } |
284 | |
285 | { |
7b39dea1 |
286 | my $res = request "/root/stream_write_fh"; |
fe1dfeaf |
287 | |
288 | is $res->code, 200, 'OK'; |
289 | is decode_utf8($res->content), '<p>This is stream_write_fh action ♥</p>', 'correct body'; |
8a79126d |
290 | #is $res->content_length, 41, 'correct length'; |
4a64c27b |
291 | is $res->content_charset, 'UTF-8'; |
fe1dfeaf |
292 | } |
dd096a3a |
293 | |
e8361cf8 |
294 | { |
295 | my $res = request "/root/stream_body_fh2"; |
296 | |
297 | is $res->code, 200, 'OK'; |
298 | is decode_utf8($res->content), "<p>This is stream_body_fh action ♥</p>\n", 'correct body'; |
299 | is $res->content_length, 41, 'correct length'; |
300 | is $res->content_charset, 'UTF-8'; |
301 | } |
302 | |
12982f86 |
303 | { |
304 | ok my $path = File::Spec->catfile('t', 'utf8.txt'); |
305 | ok my $req = POST '/root/file_upload', |
306 | Content_Type => 'form-data', |
307 | Content => [encode_utf8('♥')=>encode_utf8('♥♥'), file=>["$path", 'attachment.txt', 'Content-Type' =>'text/html; charset=UTF-8', ]]; |
308 | |
309 | ok my $res = request $req; |
310 | is decode_utf8($res->content), "<p>This is stream_body_fh action ♥</p>\n"; |
311 | } |
312 | |
313 | { |
314 | ok my $req = POST '/root/json', |
315 | Content_Type => 'application/json', |
316 | Content => encode_json +{'♥'=>'♥♥'}; # Note: JSON does the UTF* encoding for us |
317 | |
318 | ok my $res = request $req; |
319 | |
320 | ## decode_json expect the binary utf8 string and does the decoded bit for us. |
321 | is_deeply decode_json(($res->content)), +{'♥'=>'♥♥'}; |
322 | } |
323 | |
324 | ## should we use binmode on filehandles to force the encoding...? |
325 | ## Not sure what else to do with multipart here, if docs are enough... |
326 | |
0ca510f0 |
327 | done_testing; |