v / vlib / veb / tests / veb_test.v
407 lines · 364 sloc · 11.14 KB · 065a450b86f6459b1e4398fe7b0594bbfcc2d691
Raw
1// vtest retry: 3
2// vtest build: !sanitized_job? && !windows // !windows: fasthttp.Server.run not implemented yet
3import os
4import time
5import x.json2 as json
6import net
7import net.http
8import io
9
10const sport = 13005
11const localserver = '127.0.0.1:${sport}'
12const exit_after_time = 12000
13// milliseconds
14const vexe = os.getenv('VEXE')
15const veb_logfile = os.getenv('VEB_LOGFILE')
16const vroot = os.dir(vexe)
17const serverexe = os.join_path(os.cache_dir(), 'veb_test_server.exe')
18const tcp_r_timeout = 10 * time.second
19const tcp_w_timeout = 10 * time.second
20
21// setup of veb webserver
22fn testsuite_begin() {
23 os.chdir(vroot) or {}
24 if os.exists(serverexe) {
25 os.rm(serverexe) or {}
26 }
27}
28
29fn test_simple_veb_app_can_be_compiled() {
30 // did_server_compile := os.system('${os.quoted_path(vexe)} -g -o ${os.quoted_path(serverexe)} vlib/veb/tests/veb_test_server.v')
31 did_server_compile :=
32 os.system('${os.quoted_path(vexe)} -o ${os.quoted_path(serverexe)} vlib/veb/tests/veb_test_server.v')
33 assert did_server_compile == 0
34 assert os.exists(serverexe)
35}
36
37fn test_a_simple_veb_app_runs_in_the_background() {
38 mut suffix := ''
39 $if !windows {
40 suffix = ' > /dev/null &'
41 }
42 if veb_logfile != '' {
43 suffix = ' 2>> ${os.quoted_path(veb_logfile)} >> ${os.quoted_path(veb_logfile)} &'
44 }
45 server_exec_cmd := '${os.quoted_path(serverexe)} ${sport} ${exit_after_time} ${suffix}'
46 $if debug_net_socket_client ? {
47 eprintln('running:\n${server_exec_cmd}')
48 }
49 $if windows {
50 spawn os.system(server_exec_cmd)
51 } $else {
52 res := os.system(server_exec_cmd)
53 assert res == 0
54 }
55 $if macos {
56 time.sleep(1000 * time.millisecond)
57 } $else {
58 time.sleep(100 * time.millisecond)
59 }
60}
61
62// web client tests follow
63fn assert_common_headers(received string) {
64 assert received.starts_with('HTTP/1.1 200 OK\r\n'), received
65 assert received.contains('Server: veb\r\n'), received
66 assert received.contains('Content-Length:'), received
67 assert received.contains('Connection: close\r\n'), received
68}
69
70fn test_a_simple_tcp_client_can_connect_to_the_veb_server() {
71 received := simple_tcp_client(path: '/') or {
72 assert err.msg() == ''
73 return
74 }
75 assert_common_headers(received)
76 assert received.contains('Content-Type: text/plain'), received
77 assert received.contains('Content-Length: 14'), received
78 assert received.ends_with('Welcome to veb'), received
79}
80
81fn test_a_simple_tcp_client_simple_route() {
82 received := simple_tcp_client(path: '/simple') or {
83 assert err.msg() == ''
84 return
85 }
86 assert_common_headers(received)
87 assert received.contains('Content-Type: text/plain')
88 assert received.contains('Content-Length: 15')
89 assert received.ends_with('A simple result')
90}
91
92fn test_a_simple_tcp_client_zero_content_length() {
93 // tests that sending a content-length header of 0 doesn't hang on a read timeout
94 watch := time.new_stopwatch(auto_start: true)
95 simple_tcp_client(path: '/', headers: 'Content-Length: 0\r\n\r\n') or {
96 assert err.msg() == ''
97 return
98 }
99 assert watch.elapsed() < 1 * time.second
100}
101
102fn test_timeout_after_delayed_body() {
103 // content length is 10, but we don't send anything. The request should timeout,
104 // but not error.
105 watch := time.new_stopwatch(auto_start: true)
106 res := simple_tcp_client(
107 path: '/json_echo'
108 headers: 'Content-Length: 10\r\n\r\n'
109 method_str: 'POST'
110 ) or {
111 assert err.msg() == ''
112 return
113 }
114
115 assert res.ends_with('408 Request Timeout')
116}
117
118fn test_a_simple_tcp_client_html_page() {
119 received := simple_tcp_client(path: '/html_page') or {
120 assert err.msg() == ''
121 return
122 }
123 assert_common_headers(received)
124 assert received.contains('Content-Type: text/html')
125 assert received.ends_with('<h1>ok</h1>')
126}
127
128// net.http client based tests follow:
129fn assert_common_http_headers(x http.Response) ! {
130 assert x.status() == .ok
131 assert x.header.get(.server) or { '' } == 'veb'
132 assert (x.header.get(.content_length) or { '0' }).int() > 0
133}
134
135fn test_http_client_index() {
136 x := http.get('http://${localserver}/') or { panic(err) }
137 assert_common_http_headers(x)!
138 assert x.header.get(.content_type)? == 'text/plain'
139 assert x.body == 'Welcome to veb'
140 // The default http client keeps connections alive (it no longer sends
141 // `Connection: close`), so veb must not answer with a close either.
142 if conn_header := x.header.get(.connection) {
143 assert conn_header != 'close'
144 }
145 // Opting out of connection reuse restores the historical behavior
146 // end-to-end: the client sends `Connection: close` and veb honors it.
147 y := http.fetch(url: 'http://${localserver}/', disable_connection_reuse: true) or { panic(err) }
148 assert y.header.get(.connection)? == 'close'
149}
150
151fn test_http_client_404() {
152 server := 'http://${localserver}'
153 url_404_list := [
154 '/zxcnbnm',
155 '/JHKAJA',
156 '/unknown',
157 ]
158 for url in url_404_list {
159 res := http.get('${server}${url}') or { panic(err) }
160 assert res.status() == .not_found
161 assert res.body == '404 on "${url}"'
162 }
163}
164
165fn test_http_client_simple() {
166 x := http.get('http://${localserver}/simple') or { panic(err) }
167 assert_common_http_headers(x)!
168 assert x.header.get(.content_type)? == 'text/plain'
169 assert x.body == 'A simple result'
170}
171
172fn test_http_client_html_page() {
173 x := http.get('http://${localserver}/html_page') or { panic(err) }
174 assert_common_http_headers(x)!
175 assert x.header.get(.content_type)? == 'text/html'
176 assert x.body == '<h1>ok</h1>'
177}
178
179fn test_http_client_settings_page() {
180 x := http.get('http://${localserver}/bilbo/settings') or { panic(err) }
181 assert_common_http_headers(x)!
182 assert x.body == 'username: bilbo'
183
184 y := http.get('http://${localserver}/kent/settings') or { panic(err) }
185 assert_common_http_headers(y)!
186 assert y.body == 'username: kent'
187}
188
189fn test_http_client_user_repo_settings_page() {
190 x := http.get('http://${localserver}/bilbo/gostamp/settings') or { panic(err) }
191 assert_common_http_headers(x)!
192 assert x.body == 'username: bilbo | repository: gostamp'
193
194 y := http.get('http://${localserver}/kent/golang/settings') or { panic(err) }
195 assert_common_http_headers(y)!
196 assert y.body == 'username: kent | repository: golang'
197
198 z := http.get('http://${localserver}/missing/golang/settings') or { panic(err) }
199 assert z.status() == .not_found
200}
201
202struct User {
203 name string
204 age int
205}
206
207fn test_http_client_json_post() {
208 ouser := User{
209 name: 'Bilbo'
210 age: 123
211 }
212 json_for_ouser := json.encode(ouser)
213 mut x := http.post_json('http://${localserver}/json_echo', json_for_ouser) or { panic(err) }
214 $if debug_net_socket_client ? {
215 eprintln('/json_echo endpoint response: ${x}')
216 }
217 assert x.header.get(.content_type)? == 'application/json'
218 assert x.body == json_for_ouser
219 nuser := json.decode[User](x.body) or { User{} }
220 assert '${ouser}' == '${nuser}'
221
222 x = http.post_json('http://${localserver}/json', json_for_ouser) or { panic(err) }
223 $if debug_net_socket_client ? {
224 eprintln('/json endpoint response: ${x}')
225 }
226 assert x.header.get(.content_type)? == 'application/json'
227 assert x.body == json_for_ouser
228 nuser2 := json.decode[User](x.body) or { User{} }
229 assert '${ouser}' == '${nuser2}'
230}
231
232fn test_http_client_multipart_form_data() {
233 mut form_config := http.PostMultipartFormConfig{
234 form: {
235 'foo': 'baz buzz'
236 }
237 }
238
239 mut x := http.post_multipart_form('http://${localserver}/form_echo', form_config)!
240
241 $if debug_net_socket_client ? {
242 eprintln('/form_echo endpoint response: ${x}')
243 }
244 assert x.body == form_config.form['foo']
245
246 mut files := []http.FileData{}
247 files << http.FileData{
248 filename: 'veb'
249 content_type: 'text'
250 data: '"veb test"'
251 }
252
253 mut form_config_files := http.PostMultipartFormConfig{
254 files: {
255 'file': files
256 }
257 }
258
259 x = http.post_multipart_form('http://${localserver}/file_echo', form_config_files)!
260 $if debug_net_socket_client ? {
261 eprintln('/form_echo endpoint response: ${x}')
262 }
263 assert x.body == files[0].data
264}
265
266fn test_login_with_multipart_form_data_send_by_fetch() {
267 mut form_config := http.PostMultipartFormConfig{
268 form: {
269 'username': 'myusername'
270 'password': 'mypassword123'
271 }
272 }
273 x := http.post_multipart_form('http://${localserver}/login', form_config)!
274 assert x.status_code == 200
275 assert x.status_msg == 'OK'
276 assert x.body == 'username: xmyusernamex | password: xmypassword123x'
277}
278
279fn test_query_params_are_passed_as_arguments() {
280 x := http.get('http://${localserver}/query_echo?c=3&a="test"&b=20')!
281 assert x.status() == .ok
282 assert x.body == 'a: x"test"x | b: x20x'
283}
284
285fn test_host() {
286 mut req := http.Request{
287 url: 'http://${localserver}/with_host'
288 method: .get
289 }
290
291 mut x := req.do()!
292 assert x.status() == .not_found
293
294 req.add_header(.host, 'example.com')
295 x = req.do()!
296 assert x.status() == .ok
297}
298
299fn test_empty_response_body_has_content_length() {
300 req := http.Request{
301 url: 'http://${localserver}/empty_response_body'
302 method: .get
303 }
304
305 mut x := req.do()!
306 assert x.status() == .ok
307 assert x.header.get(.content_length)? == '0'
308}
309
310fn test_http_client_shutdown_does_not_work_without_a_cookie() {
311 x := http.get('http://${localserver}/shutdown') or {
312 assert err.msg() == ''
313 return
314 }
315 assert x.status() == .not_found
316}
317
318fn testsuite_end() {
319 // This test is guaranteed to be called last.
320 // It sends a request to the server to shutdown.
321 x := http.fetch(
322 url: 'http://${localserver}/shutdown'
323 method: .get
324 cookies: {
325 'skey': 'superman'
326 }
327 ) or {
328 assert err.msg() == ''
329 return
330 }
331 assert x.status() == .ok
332 assert x.body == 'good bye'
333}
334
335// utility code:
336struct SimpleTcpClientConfig {
337 retries int = 4
338 host string = 'static.dev'
339 path string = '/'
340 agent string = 'v/net.tcp.v'
341 headers string = '\r\n'
342 content string
343 method_str string = 'GET'
344}
345
346fn simple_tcp_client(config SimpleTcpClientConfig) !string {
347 mut client := &net.TcpConn(unsafe { nil })
348 mut tries := 0
349 for tries < config.retries {
350 tries++
351 eprintln('> client retries: ${tries}')
352 client = net.dial_tcp(localserver) or {
353 eprintln('dial error: ${err.msg()}')
354 if tries > config.retries {
355 return err
356 }
357 time.sleep(100 * time.millisecond)
358 continue
359 }
360 break
361 }
362 if client == unsafe { nil } {
363 eprintln('could not create a tcp client connection to http://${localserver} after ${config.retries} retries')
364 exit(1)
365 }
366 client.set_read_timeout(tcp_r_timeout)
367 client.set_write_timeout(tcp_w_timeout)
368 defer {
369 client.close() or {}
370 }
371 message := '${config.method_str} ${config.path} HTTP/1.1
372Host: ${config.host}
373User-Agent: ${config.agent}
374Accept: */*
375Connection: close
376${config.headers}
377${config.content}'
378 $if debug_net_socket_client ? {
379 eprintln('sending:\n${message}')
380 }
381 client.write(message.bytes())!
382 read := io.read_all(reader: client)!
383 $if debug_net_socket_client ? {
384 eprintln('received:\n${read}')
385 }
386 return read.bytestr()
387}
388
389// for issue 20476
390// phenomenon: parsing url error when querypath is `//`
391fn test_empty_querypath() {
392 mut x := http.get('http://${localserver}') or { panic(err) }
393 assert x.body == 'Welcome to veb'
394 x = http.get('http://${localserver}/') or { panic(err) }
395 assert x.body == 'Welcome to veb'
396 x = http.get('http://${localserver}//') or { panic(err) }
397 assert x.body == 'Welcome to veb'
398 x = http.get('http://${localserver}///') or { panic(err) }
399 assert x.body == 'Welcome to veb'
400}
401
402fn test_large_response() {
403 received := simple_tcp_client(path: '/large_response') or { panic(err) }
404 assert_common_headers(received)
405 assert received.ends_with('}]')
406 assert received.len == 830778
407}
408