v2 / vlib / veb / tests / veb_test.v
399 lines · 356 sloc · 10.67 KB · 344b9afcfe67902dd9660bd3b077f18464d1d114
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)! == 'veb'
132 assert x.header.get(.content_length)!.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 assert x.header.get(.connection)! == 'close'
141}
142
143fn test_http_client_404() {
144 server := 'http://${localserver}'
145 url_404_list := [
146 '/zxcnbnm',
147 '/JHKAJA',
148 '/unknown',
149 ]
150 for url in url_404_list {
151 res := http.get('${server}${url}') or { panic(err) }
152 assert res.status() == .not_found
153 assert res.body == '404 on "${url}"'
154 }
155}
156
157fn test_http_client_simple() {
158 x := http.get('http://${localserver}/simple') or { panic(err) }
159 assert_common_http_headers(x)!
160 assert x.header.get(.content_type)! == 'text/plain'
161 assert x.body == 'A simple result'
162}
163
164fn test_http_client_html_page() {
165 x := http.get('http://${localserver}/html_page') or { panic(err) }
166 assert_common_http_headers(x)!
167 assert x.header.get(.content_type)! == 'text/html'
168 assert x.body == '<h1>ok</h1>'
169}
170
171fn test_http_client_settings_page() {
172 x := http.get('http://${localserver}/bilbo/settings') or { panic(err) }
173 assert_common_http_headers(x)!
174 assert x.body == 'username: bilbo'
175
176 y := http.get('http://${localserver}/kent/settings') or { panic(err) }
177 assert_common_http_headers(y)!
178 assert y.body == 'username: kent'
179}
180
181fn test_http_client_user_repo_settings_page() {
182 x := http.get('http://${localserver}/bilbo/gostamp/settings') or { panic(err) }
183 assert_common_http_headers(x)!
184 assert x.body == 'username: bilbo | repository: gostamp'
185
186 y := http.get('http://${localserver}/kent/golang/settings') or { panic(err) }
187 assert_common_http_headers(y)!
188 assert y.body == 'username: kent | repository: golang'
189
190 z := http.get('http://${localserver}/missing/golang/settings') or { panic(err) }
191 assert z.status() == .not_found
192}
193
194struct User {
195 name string
196 age int
197}
198
199fn test_http_client_json_post() {
200 ouser := User{
201 name: 'Bilbo'
202 age: 123
203 }
204 json_for_ouser := json.encode(ouser)
205 mut x := http.post_json('http://${localserver}/json_echo', json_for_ouser) or { panic(err) }
206 $if debug_net_socket_client ? {
207 eprintln('/json_echo endpoint response: ${x}')
208 }
209 assert x.header.get(.content_type)! == 'application/json'
210 assert x.body == json_for_ouser
211 nuser := json.decode[User](x.body) or { User{} }
212 assert '${ouser}' == '${nuser}'
213
214 x = http.post_json('http://${localserver}/json', json_for_ouser) or { panic(err) }
215 $if debug_net_socket_client ? {
216 eprintln('/json endpoint response: ${x}')
217 }
218 assert x.header.get(.content_type)! == 'application/json'
219 assert x.body == json_for_ouser
220 nuser2 := json.decode[User](x.body) or { User{} }
221 assert '${ouser}' == '${nuser2}'
222}
223
224fn test_http_client_multipart_form_data() {
225 mut form_config := http.PostMultipartFormConfig{
226 form: {
227 'foo': 'baz buzz'
228 }
229 }
230
231 mut x := http.post_multipart_form('http://${localserver}/form_echo', form_config)!
232
233 $if debug_net_socket_client ? {
234 eprintln('/form_echo endpoint response: ${x}')
235 }
236 assert x.body == form_config.form['foo']
237
238 mut files := []http.FileData{}
239 files << http.FileData{
240 filename: 'veb'
241 content_type: 'text'
242 data: '"veb test"'
243 }
244
245 mut form_config_files := http.PostMultipartFormConfig{
246 files: {
247 'file': files
248 }
249 }
250
251 x = http.post_multipart_form('http://${localserver}/file_echo', form_config_files)!
252 $if debug_net_socket_client ? {
253 eprintln('/form_echo endpoint response: ${x}')
254 }
255 assert x.body == files[0].data
256}
257
258fn test_login_with_multipart_form_data_send_by_fetch() {
259 mut form_config := http.PostMultipartFormConfig{
260 form: {
261 'username': 'myusername'
262 'password': 'mypassword123'
263 }
264 }
265 x := http.post_multipart_form('http://${localserver}/login', form_config)!
266 assert x.status_code == 200
267 assert x.status_msg == 'OK'
268 assert x.body == 'username: xmyusernamex | password: xmypassword123x'
269}
270
271fn test_query_params_are_passed_as_arguments() {
272 x := http.get('http://${localserver}/query_echo?c=3&a="test"&b=20')!
273 assert x.status() == .ok
274 assert x.body == 'a: x"test"x | b: x20x'
275}
276
277fn test_host() {
278 mut req := http.Request{
279 url: 'http://${localserver}/with_host'
280 method: .get
281 }
282
283 mut x := req.do()!
284 assert x.status() == .not_found
285
286 req.add_header(.host, 'example.com')
287 x = req.do()!
288 assert x.status() == .ok
289}
290
291fn test_empty_response_body_has_content_length() {
292 req := http.Request{
293 url: 'http://${localserver}/empty_response_body'
294 method: .get
295 }
296
297 mut x := req.do()!
298 assert x.status() == .ok
299 assert x.header.get(.content_length)! == '0'
300}
301
302fn test_http_client_shutdown_does_not_work_without_a_cookie() {
303 x := http.get('http://${localserver}/shutdown') or {
304 assert err.msg() == ''
305 return
306 }
307 assert x.status() == .not_found
308}
309
310fn testsuite_end() {
311 // This test is guaranteed to be called last.
312 // It sends a request to the server to shutdown.
313 x := http.fetch(
314 url: 'http://${localserver}/shutdown'
315 method: .get
316 cookies: {
317 'skey': 'superman'
318 }
319 ) or {
320 assert err.msg() == ''
321 return
322 }
323 assert x.status() == .ok
324 assert x.body == 'good bye'
325}
326
327// utility code:
328struct SimpleTcpClientConfig {
329 retries int = 4
330 host string = 'static.dev'
331 path string = '/'
332 agent string = 'v/net.tcp.v'
333 headers string = '\r\n'
334 content string
335 method_str string = 'GET'
336}
337
338fn simple_tcp_client(config SimpleTcpClientConfig) !string {
339 mut client := &net.TcpConn(unsafe { nil })
340 mut tries := 0
341 for tries < config.retries {
342 tries++
343 eprintln('> client retries: ${tries}')
344 client = net.dial_tcp(localserver) or {
345 eprintln('dial error: ${err.msg()}')
346 if tries > config.retries {
347 return err
348 }
349 time.sleep(100 * time.millisecond)
350 continue
351 }
352 break
353 }
354 if client == unsafe { nil } {
355 eprintln('could not create a tcp client connection to http://${localserver} after ${config.retries} retries')
356 exit(1)
357 }
358 client.set_read_timeout(tcp_r_timeout)
359 client.set_write_timeout(tcp_w_timeout)
360 defer {
361 client.close() or {}
362 }
363 message := '${config.method_str} ${config.path} HTTP/1.1
364Host: ${config.host}
365User-Agent: ${config.agent}
366Accept: */*
367Connection: close
368${config.headers}
369${config.content}'
370 $if debug_net_socket_client ? {
371 eprintln('sending:\n${message}')
372 }
373 client.write(message.bytes())!
374 read := io.read_all(reader: client)!
375 $if debug_net_socket_client ? {
376 eprintln('received:\n${read}')
377 }
378 return read.bytestr()
379}
380
381// for issue 20476
382// phenomenon: parsing url error when querypath is `//`
383fn test_empty_querypath() {
384 mut x := http.get('http://${localserver}') or { panic(err) }
385 assert x.body == 'Welcome to veb'
386 x = http.get('http://${localserver}/') or { panic(err) }
387 assert x.body == 'Welcome to veb'
388 x = http.get('http://${localserver}//') or { panic(err) }
389 assert x.body == 'Welcome to veb'
390 x = http.get('http://${localserver}///') or { panic(err) }
391 assert x.body == 'Welcome to veb'
392}
393
394fn test_large_response() {
395 received := simple_tcp_client(path: '/large_response') or { panic(err) }
396 assert_common_headers(received)
397 assert received.ends_with('}]')
398 assert received.len == 830778
399}
400