syntropy 0.28.2 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +2 -2
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +11 -0
  5. data/README.md +0 -4
  6. data/bin/syntropy +2 -3
  7. data/cmd/setup/template/site/Dockerfile +1 -1
  8. data/lib/syntropy/app.rb +22 -14
  9. data/lib/syntropy/errors.rb +23 -10
  10. data/lib/syntropy/http/connection.rb +396 -0
  11. data/lib/syntropy/http/server.rb +174 -0
  12. data/lib/syntropy/http/status.rb +76 -0
  13. data/lib/syntropy/http.rb +5 -0
  14. data/lib/syntropy/json_api.rb +2 -5
  15. data/lib/syntropy/logger.rb +103 -0
  16. data/lib/syntropy/mime_types.rb +37 -0
  17. data/lib/syntropy/request/mock_adapter.rb +58 -0
  18. data/lib/syntropy/request/request_info.rb +236 -0
  19. data/lib/syntropy/request/response.rb +206 -0
  20. data/lib/syntropy/{request_extensions.rb → request/validation.rb} +4 -147
  21. data/lib/syntropy/request.rb +99 -0
  22. data/lib/syntropy/utils.rb +1 -1
  23. data/lib/syntropy/version.rb +1 -1
  24. data/lib/syntropy.rb +53 -5
  25. data/syntropy.gemspec +5 -7
  26. data/test/app/about/_error.rb +1 -1
  27. data/test/app/api+.rb +1 -1
  28. data/test/app_custom/_site.rb +1 -1
  29. data/test/bm_router_proc.rb +3 -3
  30. data/test/helper.rb +12 -7
  31. data/test/test_app.rb +30 -30
  32. data/test/test_caching.rb +2 -2
  33. data/test/test_connection.rb +649 -0
  34. data/test/test_errors.rb +6 -6
  35. data/test/test_json_api.rb +10 -8
  36. data/test/test_mock_adapter.rb +59 -0
  37. data/test/test_request_info.rb +90 -0
  38. data/test/test_response.rb +112 -0
  39. data/test/test_server.rb +336 -0
  40. metadata +34 -34
  41. data/lib/syntropy/file_watch.rb +0 -28
  42. data/test/test_file_watch.rb +0 -36
@@ -3,6 +3,8 @@
3
3
  require_relative 'helper'
4
4
 
5
5
  class JSONAPITest < Minitest::Test
6
+ HTTP = Syntropy::HTTP
7
+
6
8
  class TestAPI < Syntropy::JSONAPI
7
9
  def foo(req)
8
10
  @value
@@ -21,39 +23,39 @@ class JSONAPITest < Minitest::Test
21
23
  def test_json_api
22
24
  req = mock_req(':method' => 'GET', ':path' => '/')
23
25
  @app.call(req)
24
- assert_equal Qeweney::Status::BAD_REQUEST, req.response_status
26
+ assert_equal HTTP::BAD_REQUEST, req.response_status
25
27
 
26
28
  req = mock_req(':method' => 'GET', ':path' => '/?q=foo')
27
29
  @app.call(req)
28
- assert_equal Qeweney::Status::OK, req.response_status
30
+ assert_equal HTTP::OK, req.response_status
29
31
  assert_equal({ status: 'OK', response: nil }, req.response_json)
30
32
 
31
33
  req = mock_req(':method' => 'POST', ':path' => '/?q=foo')
32
34
  @app.call(req)
33
- assert_equal Qeweney::Status::METHOD_NOT_ALLOWED, req.response_status
35
+ assert_equal HTTP::METHOD_NOT_ALLOWED, req.response_status
34
36
 
35
37
 
36
38
  req = mock_req(':method' => 'POST', ':path' => '/?q=bar&v=foo')
37
39
  @app.call(req)
38
- assert_equal Qeweney::Status::OK, req.response_status
40
+ assert_equal HTTP::OK, req.response_status
39
41
  assert_equal({ status: 'OK', response: true }, req.response_json)
40
42
 
41
43
  req = mock_req(':method' => 'GET', ':path' => '/?q=bar&v=foo')
42
44
  @app.call(req)
43
- assert_equal Qeweney::Status::METHOD_NOT_ALLOWED, req.response_status
45
+ assert_equal HTTP::METHOD_NOT_ALLOWED, req.response_status
44
46
 
45
47
  req = mock_req(':method' => 'GET', ':path' => '/?q=foo')
46
48
  @app.call(req)
47
- assert_equal Qeweney::Status::OK, req.response_status
49
+ assert_equal HTTP::OK, req.response_status
48
50
  assert_equal({ status: 'OK', response: 'foo' }, req.response_json)
49
51
 
50
52
  req = mock_req(':method' => 'GET', ':path' => '/?q=foo')
51
53
  @app.call(req)
52
- assert_equal Qeweney::Status::OK, req.response_status
54
+ assert_equal HTTP::OK, req.response_status
53
55
  assert_equal({ status: 'OK', response: 'foo' }, req.response_json)
54
56
 
55
57
  req = mock_req(':method' => 'GET', ':path' => '/?q=xxx')
56
58
  @app.call(req)
57
- assert_equal Qeweney::Status::NOT_FOUND, req.response_status
59
+ assert_equal HTTP::NOT_FOUND, req.response_status
58
60
  end
59
61
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ class MockAdapterTest < Minitest::Test
6
+ def test_mock_adapter
7
+ adapter = Syntropy::MockAdapter.new(nil)
8
+ req = Syntropy::Request.new({ ':path' => '/foo' }, adapter)
9
+ req.respond('bar', 'Content-Type' => 'baz')
10
+
11
+ assert_equal 'bar', adapter.response_body
12
+ assert_equal({'Content-Type' => 'baz'}, adapter.response_headers)
13
+ end
14
+
15
+ def test_mock_adapter_with_body
16
+ adapter = Syntropy::MockAdapter.new('barbaz')
17
+ req = Syntropy::Request.new({ ':path' => '/foo' }, adapter)
18
+ assert_equal false, req.complete?
19
+
20
+ body = req.read
21
+ assert_equal 'barbaz', body
22
+ assert_equal true, req.complete?
23
+ end
24
+
25
+ def test_mock_adapter_with_chunked_body
26
+ adapter = Syntropy::MockAdapter.new(['bar', 'baz'])
27
+ req = Syntropy::Request.new({ ':path' => '/foo' }, adapter)
28
+ assert_equal false, req.complete?
29
+
30
+ chunk = req.next_chunk
31
+ assert_equal 'bar', chunk
32
+ assert_equal false, req.complete?
33
+
34
+ chunk = req.next_chunk
35
+ assert_equal 'baz', chunk
36
+ assert_equal true, req.complete?
37
+ end
38
+
39
+ def test_mock_adapter_each_chunk
40
+ chunks = []
41
+ adapter = Syntropy::MockAdapter.new(['bar', 'baz'])
42
+ req = Syntropy::Request.new({ ':path' => '/foo' }, adapter)
43
+ assert_equal false, req.complete?
44
+
45
+ req.each_chunk { chunks << _1 }
46
+ assert_equal ['bar', 'baz'], chunks
47
+ assert_equal true, req.complete?
48
+ end
49
+
50
+ def test_set_response_headers
51
+ adapter = Syntropy::MockAdapter.new(nil)
52
+ req = Syntropy::Request.new({ ':path' => '/foo' }, adapter)
53
+ adapter.set_response_headers('Foo' => 'bar')
54
+ req.respond('hi', 'Bar' => 'baz')
55
+
56
+ assert_equal 'hi', adapter.response_body
57
+ assert_equal({'Foo' => 'bar', 'Bar' => 'baz'}, adapter.response_headers)
58
+ end
59
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ class RequestInfoTest < Minitest::Test
6
+ def test_uri
7
+ r = Syntropy::MockAdapter.mock(':path' => '/test/path')
8
+ assert_equal '/test/path', r.path
9
+ assert_equal({}, r.query)
10
+
11
+ r = Syntropy::MockAdapter.mock(':path' => '/test/path?a=1&b=2&c=3%2f4')
12
+ assert_equal '/test/path', r.path
13
+ assert_equal({ a: '1', b: '2', c: '3/4' }, r.query)
14
+ end
15
+
16
+ def test_query
17
+ r = Syntropy::MockAdapter.mock(':path' => '/GponForm/diag_Form?images/')
18
+ assert_equal '/GponForm/diag_Form', r.path
19
+ assert_equal({:'images/' => true}, r.query)
20
+
21
+ r = Syntropy::MockAdapter.mock(':path' => '/?a=1&b=2')
22
+ assert_equal '/', r.path
23
+ assert_equal({a: '1', b: '2'}, r.query)
24
+
25
+ r = Syntropy::MockAdapter.mock(':path' => '/?l=a&t=&x=42')
26
+ assert_equal({l: 'a', t: '', x: '42'}, r.query)
27
+ end
28
+
29
+ def test_host
30
+ r = Syntropy::MockAdapter.mock(':path' => '/')
31
+ assert_nil r.host
32
+ assert_nil r.authority
33
+
34
+ r = Syntropy::MockAdapter.mock('host' => 'my.example.com')
35
+ assert_equal 'my.example.com', r.host
36
+ assert_equal 'my.example.com', r.authority
37
+
38
+ r = Syntropy::MockAdapter.mock(':authority' => 'my.foo.com')
39
+ assert_equal 'my.foo.com', r.host
40
+ assert_equal 'my.foo.com', r.authority
41
+ end
42
+
43
+ def test_full_uri
44
+ r = Syntropy::MockAdapter.mock(
45
+ ':scheme' => 'https',
46
+ 'host' => 'foo.bar',
47
+ ':path' => '/hey?a=b&c=d'
48
+ )
49
+
50
+ assert_equal 'https://foo.bar/hey?a=b&c=d', r.full_uri
51
+ end
52
+
53
+ def test_cookies
54
+ r = Syntropy::MockAdapter.mock
55
+
56
+ assert_equal({}, r.cookies)
57
+
58
+ r = Syntropy::MockAdapter.mock(
59
+ 'cookie' => 'uaid=a%2Fb; lastLocus=settings; signin_ref=/'
60
+ )
61
+
62
+ assert_equal({
63
+ 'uaid' => 'a/b',
64
+ 'lastLocus' => 'settings',
65
+ 'signin_ref' => '/'
66
+ }, r.cookies)
67
+ end
68
+
69
+ def test_rewrite!
70
+ r = Syntropy::MockAdapter.mock(
71
+ ':scheme' => 'https',
72
+ 'host' => 'foo.bar',
73
+ ':path' => '/hey/ho?a=b&c=d'
74
+ )
75
+
76
+ assert_equal '/hey/ho', r.path
77
+ assert_equal URI.parse('/hey/ho?a=b&c=d'), r.uri
78
+ assert_equal 'https://foo.bar/hey/ho?a=b&c=d', r.full_uri
79
+
80
+ r.rewrite!('/hhh', '/')
81
+ assert_equal '/hey/ho', r.path
82
+ assert_equal URI.parse('/hey/ho?a=b&c=d'), r.uri
83
+ assert_equal 'https://foo.bar/hey/ho?a=b&c=d', r.full_uri
84
+
85
+ r.rewrite!('/hey', '/')
86
+ assert_equal '/ho', r.path
87
+ assert_equal URI.parse('/ho?a=b&c=d'), r.uri
88
+ assert_equal 'https://foo.bar/ho?a=b&c=d', r.full_uri
89
+ end
90
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ class RedirectTest < Minitest::Test
6
+ def test_redirect
7
+ r = Syntropy::MockAdapter.mock
8
+ r.redirect('/foo')
9
+
10
+ assert_equal [
11
+ [:respond, r, nil, {":status"=>302, "Location"=>"/foo"}]
12
+ ], r.adapter.calls
13
+ end
14
+
15
+ def test_redirect_wirth_status
16
+ r = Syntropy::MockAdapter.mock
17
+ r.redirect('/bar', Syntropy::HTTP::MOVED_PERMANENTLY)
18
+
19
+ assert_equal [
20
+ [:respond, r, nil, {":status"=>301, "Location"=>"/bar"}]
21
+ ], r.adapter.calls
22
+ end
23
+ end
24
+
25
+ class StaticFileResponeTest < Minitest::Test
26
+ def setup
27
+ @path = File.join(__dir__, 'helper.rb')
28
+ @stat = File.stat(@path)
29
+
30
+ @etag = Syntropy::StaticFileCaching.file_stat_to_etag(@stat)
31
+ @last_modified = Syntropy::StaticFileCaching.file_stat_to_last_modified(@stat)
32
+ @content = IO.read(@path)
33
+ end
34
+
35
+ def test_serve_file_non_existent
36
+ r = Syntropy::MockAdapter.mock
37
+ r.serve_file('foo.rb', base_path: __dir__)
38
+ assert_equal [
39
+ [:respond, r, nil, { ':status' => Syntropy::HTTP::NOT_FOUND }]
40
+ ], r.adapter.calls
41
+ end
42
+ end
43
+
44
+ class UpgradeTest < Minitest::Test
45
+ def test_upgrade
46
+ r = Syntropy::MockAdapter.mock
47
+ r.upgrade('df')
48
+
49
+ assert_equal [
50
+ [
51
+ :respond,
52
+ r,
53
+ nil,
54
+ {
55
+ ':status' => 101,
56
+ 'Upgrade' => 'df',
57
+ 'Connection' => 'upgrade'
58
+ }
59
+ ],
60
+ [
61
+ :with_stream
62
+ ]
63
+ ], r.adapter.calls
64
+
65
+
66
+ r = Syntropy::MockAdapter.mock
67
+ r.upgrade('df', { 'foo' => 'bar' })
68
+
69
+ assert_equal [
70
+ [
71
+ :respond,
72
+ r,
73
+ nil,
74
+ {
75
+ ':status' => 101,
76
+ 'Upgrade' => 'df',
77
+ 'Connection' => 'upgrade',
78
+ 'foo' => 'bar'
79
+ }
80
+ ],
81
+ [
82
+ :with_stream
83
+ ]
84
+ ], r.adapter.calls
85
+ end
86
+
87
+ def test_websocket_upgrade
88
+ r = Syntropy::MockAdapter.mock(
89
+ 'connection' => 'upgrade',
90
+ 'upgrade' => 'websocket',
91
+ 'sec-websocket-version' => '23',
92
+ 'sec-websocket-key' => 'abcdefghij'
93
+ )
94
+
95
+ assert_equal 'websocket', r.upgrade_protocol
96
+
97
+ r.upgrade_to_websocket('foo' => 'baz')
98
+ accept = Digest::SHA1.base64digest('abcdefghij258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
99
+
100
+ assert_equal [
101
+ [:respond, r, nil, {
102
+ ':status' => 101,
103
+ 'Upgrade' => 'websocket',
104
+ 'Connection' => 'upgrade',
105
+ 'foo' => 'baz',
106
+ 'Sec-WebSocket-Accept' => accept
107
+ }],
108
+ [:with_stream],
109
+ [:websocket_connection, r]
110
+ ], r.adapter.calls
111
+ end
112
+ end
@@ -0,0 +1,336 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './helper'
4
+
5
+ class ServerTest < Minitest::Test
6
+ def make_socket_pair
7
+ port = 10000 + rand(30000)
8
+ server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
9
+ @machine.setsockopt(server_fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
10
+ @machine.bind(server_fd, '127.0.0.1', port)
11
+ @machine.listen(server_fd, UM::SOMAXCONN)
12
+
13
+ client_conn_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
14
+ @machine.connect(client_conn_fd, '127.0.0.1', port)
15
+
16
+ server_conn_fd = @machine.accept(server_fd)
17
+
18
+ @machine.close(server_fd)
19
+ [client_conn_fd, server_conn_fd]
20
+ end
21
+
22
+ class STOP < StandardError
23
+ end
24
+
25
+ def setup(opts = {})
26
+ @machine = UM.new
27
+ @port = 10000 + rand(30000)
28
+ @env = { bind: "127.0.0.1:#{@port}" }.merge(opts)
29
+ @server = Syntropy::HTTP::Server.new(@machine, @env) { @app&.call(it) }
30
+ @f_server = @machine.spin { run_server }
31
+
32
+ # let server spin and listen to incoming connections
33
+ @machine.sleep(0.01)
34
+
35
+ @client_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
36
+ @machine.connect(@client_fd, '127.0.0.1', @port)
37
+ end
38
+
39
+ def run_server
40
+ @server.run
41
+ ensure
42
+ @server_done = true
43
+ end
44
+
45
+ def teardown
46
+ @machine.close(@client_fd) rescue nil
47
+ @server.stop!
48
+ @machine.snooze until @server_done
49
+ end
50
+
51
+ def write_http_request(msg)
52
+ @machine.send(@client_fd, msg, msg.bytesize, UM::MSG_WAITALL)
53
+ end
54
+
55
+ def write_client_side(msg)
56
+ @machine.send(@client_fd, msg, msg.bytesize, UM::MSG_WAITALL)
57
+ end
58
+
59
+ def read_client_side(len = 65536)
60
+ buf = +''
61
+ res = @machine.recv(@client_fd, buf, len, 0)
62
+ res == 0 ? nil : buf
63
+ end
64
+
65
+ def test_http_1_0_response
66
+ @app = ->(req) {
67
+ req.respond('Hello, world!', {})
68
+ }
69
+
70
+ write_http_request "GET / HTTP/1.0\r\n\r\n"
71
+ response = read_client_side
72
+ expected = "HTTP/1.1 505\r\nTransfer-Encoding: chunked\r\n\r\n1a\r\nHTTP version not supported\r\n0\r\n\r\n"
73
+ assert_equal(expected, response)
74
+ end
75
+
76
+ def test_basic_app_response
77
+ @app = ->(req) {
78
+ req.respond('Hello, world!', {})
79
+ }
80
+
81
+ write_http_request "GET / HTTP/1.1\r\n\r\n"
82
+ response = read_client_side
83
+ expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\nd\r\nHello, world!\r\n0\r\n\r\n"
84
+ assert_equal(expected, response)
85
+ end
86
+
87
+ def test_pipelined_requests
88
+ @app = ->(req) {
89
+ req.respond("method: #{req.method}")
90
+ }
91
+
92
+ write_http_request "GET /foo HTTP/1.1\r\nServer: foo.com\r\n\r\nSCHmet /bar HTTP/1.1\r\n\r\n"
93
+
94
+ @machine.sleep(0.1)
95
+ response = read_client_side
96
+ expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\nb\r\nmethod: get\r\n0\r\n\r\n" +
97
+ "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\ne\r\nmethod: schmet\r\n0\r\n\r\n"
98
+ assert_equal(expected, response)
99
+ end
100
+
101
+ def test_graceful_shutdown
102
+ @app = ->(req) do
103
+ @machine.sleep(1)
104
+ req.respond('Hello, world!', {})
105
+ rescue UM::Terminate
106
+ req.respond('Terminated!', {})
107
+ raise
108
+ end
109
+
110
+ write_http_request "GET /foo HTTP/1.1\r\nServer: foo.com\r\n\r\nSCHMET /bar HTTP/1.1\r\n\r\n"
111
+
112
+ @machine.sleep(0.01)
113
+ @server.stop!
114
+ @machine.snooze
115
+
116
+ response = read_client_side
117
+ expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\nb\r\nTerminated!\r\n0\r\n\r\n"
118
+ assert_equal(expected, response)
119
+ end
120
+
121
+ def test_pipelined_requests_with_body
122
+ @bodies = []
123
+ @reqs = []
124
+ @app = ->(req) {
125
+ @reqs << req
126
+ @bodies << req.read
127
+ req.respond("method: #{req.method}")
128
+ }
129
+
130
+ msg = <<~HTTP.crlf_lines
131
+ POST /foo HTTP/1.1
132
+ Server: foo.com
133
+ Content-Length: 3
134
+
135
+ abcSCHMOST /bar HTTP/1.1
136
+ Server: bar.com
137
+ Content-Length: 6
138
+
139
+ defghi
140
+ HTTP
141
+ write_http_request msg
142
+
143
+ read_client_side
144
+ assert_equal 2, @bodies.size
145
+
146
+ req0 = @reqs.shift
147
+ headers = req0.headers
148
+ assert_equal({
149
+ ':method' => 'post',
150
+ ':path' => '/foo',
151
+ ':protocol' => 'http/1.1',
152
+ 'server' => 'foo.com',
153
+ 'content-length' => '3',
154
+ ':body-done-reading' => true,
155
+ ':tx' => 56,
156
+ }, headers)
157
+ body = @bodies.shift
158
+ assert_equal 'abc', body
159
+
160
+ req1 = @reqs.shift
161
+ headers = req1.headers
162
+ assert_equal({
163
+ ':method' => 'schmost',
164
+ ':path' => '/bar',
165
+ ':protocol' => 'http/1.1',
166
+ 'server' => 'bar.com',
167
+ 'content-length' => '6',
168
+ ':body-done-reading' => true,
169
+ ':tx' => 59,
170
+ }, headers)
171
+ body = @bodies.shift
172
+ assert_equal 'defghi', body
173
+ end
174
+
175
+ def test_pipelined_requests_with_body_chunked
176
+ @bodies = []
177
+ @reqs = []
178
+ @app = ->(req) do
179
+ @reqs << req
180
+ @bodies << (b = req.read)
181
+ req.respond("method: #{req.method}")
182
+ rescue => e
183
+ p e
184
+ p e.backtrace
185
+ exit!
186
+ end
187
+
188
+ msg = <<~HTTP.crlf_lines
189
+ POST /foo HTTP/1.1
190
+ Server: foo.com
191
+ Transfer-Encoding: chunked
192
+
193
+ 3
194
+ abc
195
+ 2
196
+ de
197
+ 0
198
+
199
+ SCHMOST /bar HTTP/1.1
200
+ Server: bar.com
201
+ Transfer-Encoding: chunked
202
+
203
+ 1f
204
+ 123456789abcdefghijklmnopqrstuv
205
+ 0
206
+
207
+
208
+
209
+ HTTP
210
+
211
+ write_http_request msg
212
+ read_client_side
213
+ read_client_side
214
+ assert_equal 2, @bodies.size
215
+
216
+ req0 = @reqs.shift
217
+ headers = req0.headers
218
+ assert_equal({
219
+ ':method' => 'post',
220
+ ':path' => '/foo',
221
+ ':protocol' => 'http/1.1',
222
+ 'server' => 'foo.com',
223
+ 'transfer-encoding' => 'chunked',
224
+ ':body-done-reading' => true,
225
+ ':tx' => 56
226
+ }, headers)
227
+ body = @bodies.shift
228
+ assert_equal 'abcde', body
229
+
230
+ req1 = @reqs.shift
231
+ headers = req1.headers
232
+ assert_equal({
233
+ ':method' => 'schmost',
234
+ ':path' => '/bar',
235
+ ':protocol' => 'http/1.1',
236
+ 'server' => 'bar.com',
237
+ 'transfer-encoding' => 'chunked',
238
+ ':body-done-reading' => true,
239
+ ':tx' => 59
240
+ }, headers)
241
+ body = @bodies.shift
242
+ assert_equal '123456789abcdefghijklmnopqrstuv', body
243
+ end
244
+
245
+ class TestLogger
246
+ attr_reader :entries
247
+
248
+ def initialize
249
+ @entries = []
250
+ end
251
+
252
+ def info(o)
253
+ @entries << o.merge(level: :INFO)
254
+ end
255
+
256
+ def error(o)
257
+ @entries << o.merge(level: :ERROR)
258
+ end
259
+ end
260
+
261
+ def test_logging
262
+ skip
263
+ reqs = []
264
+ @env[:logger] = TestLogger.new
265
+ @app = ->(req) { reqs << req; req.respond('Hello, world!', {}) }
266
+
267
+ write_http_request "GET / HTTP/1.1\r\n\r\n"
268
+ response = read_client_side
269
+ expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\nd\r\nHello, world!\r\n0\r\n\r\n"
270
+ assert_equal(expected, response)
271
+
272
+ entries = @env[:logger].entries
273
+ assert_equal 1, entries.size
274
+ assert_equal 1, reqs.size
275
+
276
+ assert_equal reqs.first, entries.first[:request]
277
+ end
278
+
279
+ def test_server_headers
280
+ @env[:server_headers] = "Server: Tipi\r\n"
281
+
282
+ @app = ->(req) {
283
+ req.respond('Hello, world!', {})
284
+ }
285
+
286
+ write_http_request "GET / HTTP/1.1\r\n\r\n"
287
+ response = read_client_side
288
+ expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nServer: Tipi\r\n\r\nd\r\nHello, world!\r\n0\r\n\r\n"
289
+ assert_equal(expected, response)
290
+ end
291
+
292
+ def test_server_headers_date
293
+ setup({ server_extensions: { date: true } })
294
+ @machine.sleep(0.1)
295
+ assert_kind_of Time, @env[:server_date]
296
+
297
+ @app = ->(req) {
298
+ req.respond('foo', {})
299
+ }
300
+
301
+ write_http_request "GET / HTTP/1.1\r\n\r\n"
302
+ response = read_client_side
303
+ expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nDate: #{@env[:server_date].httpdate}\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
304
+ assert_equal(expected, response)
305
+ end
306
+
307
+ def test_server_headers_date_and_server_name
308
+ setup({ server_extensions: { date: true, name: 'Foo' } })
309
+ @machine.sleep(0.1)
310
+ assert_kind_of Time, @env[:server_date]
311
+
312
+ @app = ->(req) {
313
+ req.respond('foo', {})
314
+ }
315
+
316
+ write_http_request "GET / HTTP/1.1\r\n\r\n"
317
+ response = read_client_side
318
+ expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nServer: Foo\r\nDate: #{@env[:server_date].httpdate}\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
319
+ assert_equal(expected, response)
320
+ end
321
+
322
+ def test_server_headers_server_name
323
+ setup({ server_extensions: { name: 'Bar' } })
324
+ @machine.sleep(0.1)
325
+ assert_nil @env[:server_date]
326
+
327
+ @app = ->(req) {
328
+ req.respond('foo', {})
329
+ }
330
+
331
+ write_http_request "GET / HTTP/1.1\r\n\r\n"
332
+ response = read_client_side
333
+ expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nServer: Bar\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
334
+ assert_equal(expected, response)
335
+ end
336
+ end