syntropy 0.30.0 → 0.32.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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.md +30 -0
- data/TODO.md +46 -1
- data/bin/syntropy +8 -86
- data/cmd/_banner.rb +16 -0
- data/cmd/console.rb +77 -0
- data/cmd/help.rb +12 -0
- data/cmd/serve.rb +95 -0
- data/cmd/test.rb +40 -0
- data/examples/{counter.rb → basic/counter.rb} +1 -1
- data/examples/{templates.rb → basic/templates.rb} +1 -1
- data/examples/blog/app/_layout/default.rb +11 -0
- data/examples/blog/app/_lib/post_store.rb +47 -0
- data/examples/blog/app/_schema/2026-01-01-initial.rb +9 -0
- data/examples/blog/app/_setup.rb +4 -0
- data/examples/blog/app/index.rb +7 -0
- data/examples/blog/app/posts/[id]/edit.rb +33 -0
- data/examples/blog/app/posts/[id]/index.rb +58 -0
- data/examples/blog/app/posts/index.rb +38 -0
- data/examples/blog/app/posts/new.rb +29 -0
- data/examples/mcp-oauth/.ruby-version +1 -0
- data/examples/mcp-oauth/Gemfile +8 -0
- data/examples/mcp-oauth/README.md +128 -0
- data/examples/mcp-oauth/app/.well-known/oauth-authorization-server.rb +18 -0
- data/examples/mcp-oauth/app/.well-known/oauth-protected-resource.rb +10 -0
- data/examples/mcp-oauth/app/_lib/auth_store.rb +23 -0
- data/examples/mcp-oauth/app/index.md +1 -0
- data/examples/mcp-oauth/app/mcp.rb +85 -0
- data/examples/mcp-oauth/app/oauth/authorize.rb +18 -0
- data/examples/mcp-oauth/app/oauth/consent.rb +86 -0
- data/examples/mcp-oauth/app/oauth/register.rb +14 -0
- data/examples/mcp-oauth/app/oauth/token.rb +79 -0
- data/examples/mcp-oauth/app/signin.rb +85 -0
- data/examples/mcp-oauth/test/helper.rb +9 -0
- data/examples/mcp-oauth/test/test_app.rb +27 -0
- data/examples/mcp-oauth/test/test_oauth.rb +628 -0
- data/lib/syntropy/app.rb +34 -9
- data/lib/syntropy/applets/builtin/default_error_handler.rb +3 -3
- data/lib/syntropy/applets/builtin/req.rb +1 -1
- data/lib/syntropy/db/connection_pool.rb +71 -0
- data/lib/syntropy/db/schema.rb +92 -0
- data/lib/syntropy/db/store.rb +31 -0
- data/lib/syntropy/dev_mode.rb +1 -1
- data/lib/syntropy/errors.rb +6 -0
- data/lib/syntropy/http/client.rb +43 -0
- data/lib/syntropy/http/client_connection.rb +36 -0
- data/lib/syntropy/http/io_extensions.rb +176 -0
- data/lib/syntropy/http/server.rb +5 -5
- data/lib/syntropy/http/{connection.rb → server_connection.rb} +15 -91
- data/lib/syntropy/http.rb +3 -1
- data/lib/syntropy/logger.rb +5 -1
- data/lib/syntropy/{module.rb → module_loader.rb} +47 -8
- data/lib/syntropy/papercraft_extensions.rb +1 -1
- data/lib/syntropy/request/mock_adapter.rb +2 -0
- data/lib/syntropy/request/request_info.rb +22 -4
- data/lib/syntropy/request/response.rb +2 -2
- data/lib/syntropy/request/validation.rb +11 -5
- data/lib/syntropy/routing_tree.rb +2 -1
- data/lib/syntropy/test.rb +77 -0
- data/lib/syntropy/version.rb +1 -1
- data/lib/syntropy.rb +5 -23
- data/syntropy.gemspec +3 -3
- data/test/app/.well-known/foo.rb +3 -0
- data/test/app/_hook.rb +1 -1
- data/test/app/by_method.rb +9 -0
- data/test/app_setup/_setup.rb +7 -0
- data/test/app_setup/index.rb +1 -0
- data/test/app_with_schema/_schema/2026-01-02-foo.rb +12 -0
- data/test/app_with_schema/_schema/2026-05-30-bar.rb +7 -0
- data/test/helper.rb +1 -25
- data/test/schema/2026-01-02-foo.rb +12 -0
- data/test/schema/2026-05-30-bar.rb +7 -0
- data/test/test_app.rb +110 -70
- data/test/test_caching.rb +1 -1
- data/test/{test_connection_pool.rb → test_db_connection_pool.rb} +7 -2
- data/test/test_db_schema.rb +96 -0
- data/test/test_db_store.rb +24 -0
- data/test/test_http_client.rb +52 -0
- data/test/test_http_client_connection.rb +43 -0
- data/test/test_http_protocol.rb +250 -0
- data/test/{test_connection.rb → test_http_server_connection.rb} +39 -48
- data/test/test_json_api.rb +5 -5
- data/test/{test_module.rb → test_module_loader.rb} +31 -0
- data/test/{test_request_extensions.rb → test_request.rb} +153 -18
- data/test/test_routing_tree.rb +15 -3
- data/test/test_server.rb +9 -13
- metadata +84 -36
- data/lib/syntropy/connection_pool.rb +0 -61
- data/test/test_request_info.rb +0 -90
- /data/examples/{bad.rb → basic/bad.rb} +0 -0
- /data/examples/{card.rb → basic/card.rb} +0 -0
- /data/examples/{counter.js → basic/counter.js} +0 -0
- /data/examples/{counter_api.rb → basic/counter_api.rb} +0 -0
- /data/examples/{favicon.ico → basic/favicon.ico} +0 -0
- /data/examples/{index.md → basic/index.md} +0 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative './helper'
|
|
4
|
+
|
|
5
|
+
class HTTPProtocolTest < Minitest::Test
|
|
6
|
+
def setup
|
|
7
|
+
@machine = UM.new
|
|
8
|
+
@r, @w = UM.pipe
|
|
9
|
+
@io = @machine.io(@r)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def teardown
|
|
13
|
+
@machine.close(@r) rescue nil
|
|
14
|
+
@machine.close(@w) rescue nil
|
|
15
|
+
@io = nil
|
|
16
|
+
@machine = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def write(str)
|
|
20
|
+
@machine.write(@w, str)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class HTTPProtocolRequestTest < HTTPProtocolTest
|
|
25
|
+
def test_http_request_headers_basic
|
|
26
|
+
write("GET /foo HTTP/1.1\r\nHost: bar.baz\r\n\r\n")
|
|
27
|
+
h = @io.http_read_request_headers
|
|
28
|
+
assert_equal({
|
|
29
|
+
':method' => 'get',
|
|
30
|
+
':path' => '/foo',
|
|
31
|
+
'host' => 'bar.baz'
|
|
32
|
+
}, h)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_http_request_headers_bad_http_method
|
|
36
|
+
write("foo /foo HTTP/1.1\r\n\r\n")
|
|
37
|
+
assert_raises(Syntropy::ProtocolError) { @io.http_read_request_headers }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_http_request_headers_bad_path
|
|
41
|
+
write("get HTTP/1.1\r\n\r\n")
|
|
42
|
+
assert_raises(Syntropy::ProtocolError) { @io.http_read_request_headers }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_http_request_headers_bad_protocol
|
|
46
|
+
write("get / HTTP/1.0\r\n\r\n")
|
|
47
|
+
assert_raises(Syntropy::ProtocolError) { @io.http_read_request_headers }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_http_request_headers_bad_header_missing_value
|
|
51
|
+
write("GET /foo HTTP/1.1\r\nHost: \r\n\r\n")
|
|
52
|
+
assert_raises(Syntropy::ProtocolError) { @io.http_read_request_headers }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_http_request_headers_bad_header
|
|
56
|
+
write("GET /foo HTTP/1.1\r\nHost\r\n\r\n")
|
|
57
|
+
assert_raises(Syntropy::ProtocolError) { @io.http_read_request_headers }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_http_request_with_body_cl
|
|
61
|
+
write("POST /foo HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc")
|
|
62
|
+
|
|
63
|
+
h = @io.http_read_request_headers
|
|
64
|
+
assert_equal({
|
|
65
|
+
':method' => 'post',
|
|
66
|
+
':path' => '/foo',
|
|
67
|
+
'content-length' => '3'
|
|
68
|
+
}, h)
|
|
69
|
+
|
|
70
|
+
b = @io.http_read_body(h)
|
|
71
|
+
assert_equal 'abc', b
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_http_request_with_body_te
|
|
75
|
+
write("POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n11\r\nabcdefghijKLMNOPQ\r\n3\r\nfoo\r\n0\r\n\r\n")
|
|
76
|
+
|
|
77
|
+
h = @io.http_read_request_headers
|
|
78
|
+
assert_equal({
|
|
79
|
+
':method' => 'post',
|
|
80
|
+
':path' => '/foo',
|
|
81
|
+
'transfer-encoding' => 'chunked'
|
|
82
|
+
}, h)
|
|
83
|
+
|
|
84
|
+
b = @io.http_read_body(h)
|
|
85
|
+
assert_equal 'abcdefghijKLMNOPQfoo', b
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_http_request_pipelining
|
|
89
|
+
write(
|
|
90
|
+
"GET /a HTTP/1.1\r\n\r\n" +
|
|
91
|
+
"POST /b HTTP/1.1\r\nHost: foo.com\r\nContent-Length: 2\r\n\r\nab" +
|
|
92
|
+
"PATCH /c HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n3\r\nabc\r\n10\r\n#{'*' * 16}\r\n0\r\n\r\n" +
|
|
93
|
+
"GET /d HTTP/1.1\r\nFoo: bar\r\n\r\n"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
reqs = 4.times.map {
|
|
97
|
+
h = @io.http_read_request_headers
|
|
98
|
+
b = @io.http_read_body(h)
|
|
99
|
+
[h, b]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
assert_equal [
|
|
103
|
+
[
|
|
104
|
+
{
|
|
105
|
+
':method' => 'get',
|
|
106
|
+
':path' => '/a',
|
|
107
|
+
},
|
|
108
|
+
nil
|
|
109
|
+
],
|
|
110
|
+
[
|
|
111
|
+
{
|
|
112
|
+
':method' => 'post',
|
|
113
|
+
':path' => '/b',
|
|
114
|
+
'host' => 'foo.com',
|
|
115
|
+
'content-length' => '2'
|
|
116
|
+
},
|
|
117
|
+
'ab'
|
|
118
|
+
],
|
|
119
|
+
[
|
|
120
|
+
{
|
|
121
|
+
':method' => 'patch',
|
|
122
|
+
':path' => '/c',
|
|
123
|
+
'transfer-encoding' => 'chunked'
|
|
124
|
+
},
|
|
125
|
+
"abc#{'*' * 16}"
|
|
126
|
+
],
|
|
127
|
+
[
|
|
128
|
+
{
|
|
129
|
+
':method' => 'get',
|
|
130
|
+
':path' => '/d',
|
|
131
|
+
'foo' => 'bar'
|
|
132
|
+
},
|
|
133
|
+
nil
|
|
134
|
+
],
|
|
135
|
+
], reqs
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def test_http_request_pipelining_skip_body
|
|
139
|
+
write(
|
|
140
|
+
"GET /a HTTP/1.1\r\n\r\n" +
|
|
141
|
+
"POST /b HTTP/1.1\r\nHost: foo.com\r\nContent-Length: 2\r\n\r\nab" +
|
|
142
|
+
"PATCH /c HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n3\r\nabc\r\n10\r\n#{'*' * 16}\r\n0\r\n\r\n" +
|
|
143
|
+
"GET /d HTTP/1.1\r\nFoo: bar\r\n\r\n"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
reqs = 4.times.map {
|
|
147
|
+
h = @io.http_read_request_headers
|
|
148
|
+
@io.http_skip_body(h)
|
|
149
|
+
h
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
assert_equal [
|
|
153
|
+
{
|
|
154
|
+
':method' => 'get',
|
|
155
|
+
':path' => '/a'
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
':method' => 'post',
|
|
159
|
+
':path' => '/b',
|
|
160
|
+
'host' => 'foo.com',
|
|
161
|
+
'content-length' => '2'
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
':method' => 'patch',
|
|
165
|
+
':path' => '/c',
|
|
166
|
+
'transfer-encoding' => 'chunked'
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
':method' => 'get',
|
|
170
|
+
':path' => '/d',
|
|
171
|
+
'foo' => 'bar'
|
|
172
|
+
}
|
|
173
|
+
], reqs
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def test_http_request_desync1
|
|
177
|
+
write(
|
|
178
|
+
"POST / HTTP/1.1\r\nHost: foo.com\r\nTransfer-Encoding: chunked\r\nContent-length: 35\r\n\r\n0\r\n\r\n" +
|
|
179
|
+
"GET /robots.txt HTTP/1.1\r\nX: y\r\n\r\n"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
h = @io.http_read_request_headers
|
|
183
|
+
assert_equal({
|
|
184
|
+
':method' => 'post',
|
|
185
|
+
':path' => '/',
|
|
186
|
+
'host' => 'foo.com',
|
|
187
|
+
'transfer-encoding' => 'chunked',
|
|
188
|
+
'content-length' => '35'
|
|
189
|
+
}, h)
|
|
190
|
+
|
|
191
|
+
@io.http_skip_body(h)
|
|
192
|
+
|
|
193
|
+
assert_raises(Syntropy::ProtocolError) { @io.http_read_request_headers }
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
class HTTPProtocolReadChunkTest < HTTPProtocolTest
|
|
198
|
+
def test_http_read_body_chunk_no_body
|
|
199
|
+
write("GET /foo HTTP/1.1\r\Host: bar.baz\r\n\r\n")
|
|
200
|
+
h = @io.http_read_request_headers
|
|
201
|
+
assert_nil @io.http_read_body_chunk(h)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def test_http_read_body_chunk_cl
|
|
205
|
+
write("POST /foo HTTP/1.1\r\Host: bar.baz\r\nContent-Length: 5\r\n\r\nabcde")
|
|
206
|
+
h = @io.http_read_request_headers
|
|
207
|
+
assert_equal 'abcde', @io.http_read_body_chunk(h)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def test_http_read_body_chunk_te
|
|
211
|
+
write("POST /foo HTTP/1.1\r\Host: bar.baz\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nabcde\r\n0\r\n\r\n")
|
|
212
|
+
h = @io.http_read_request_headers
|
|
213
|
+
assert_equal 'abcde', @io.http_read_body_chunk(h)
|
|
214
|
+
assert_nil @io.http_read_body_chunk(h)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def test_http_read_body_chunk_te2
|
|
218
|
+
write("POST /foo HTTP/1.1\r\Host: bar.baz\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nabcde\r\n3\r\nfgh\r\n0\r\n\r\n")
|
|
219
|
+
h = @io.http_read_request_headers
|
|
220
|
+
assert_equal 'abcde', @io.http_read_body_chunk(h)
|
|
221
|
+
assert_equal 'fgh', @io.http_read_body_chunk(h)
|
|
222
|
+
assert_nil @io.http_read_body_chunk(h)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
class HTTPProtocolResponseTest < HTTPProtocolTest
|
|
227
|
+
def test_http_response_headers_basic
|
|
228
|
+
write("HTTP/1.1 200 OK\r\nHost: bar.baz\r\n\r\n")
|
|
229
|
+
h = @io.http_read_response_headers
|
|
230
|
+
assert_equal({
|
|
231
|
+
':status' => 200,
|
|
232
|
+
'host' => 'bar.baz'
|
|
233
|
+
}, h)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def test_http_response_headers_invalid_status_line1
|
|
237
|
+
write("HTTP 200 OK\r\nHost: bar.baz\r\n\r\n")
|
|
238
|
+
assert_raises(Syntropy::ProtocolError) { @io.http_read_response_headers }
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def test_http_response_headers_invalid_status_line2
|
|
242
|
+
write("HTTP/1.1\r\nHost: bar.baz\r\n\r\n")
|
|
243
|
+
assert_raises(Syntropy::ProtocolError) { @io.http_read_response_headers }
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def test_http_response_headers_invalid_status_line3
|
|
247
|
+
write("HTTP/1.1 ok\r\nBlahblah\r\n\r\n")
|
|
248
|
+
assert_raises(Syntropy::ProtocolError) { @io.http_read_response_headers }
|
|
249
|
+
end
|
|
250
|
+
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require_relative './helper'
|
|
4
4
|
require 'securerandom'
|
|
5
5
|
|
|
6
|
-
class
|
|
6
|
+
class HTTPServerConnectionTest < Minitest::Test
|
|
7
7
|
def make_socket_pair
|
|
8
8
|
port = SecureRandom.random_number(10000..40000)
|
|
9
9
|
server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
|
@@ -29,7 +29,7 @@ class ConnectionTest < Minitest::Test
|
|
|
29
29
|
@hook = nil
|
|
30
30
|
@app = ->(req) { @reqs << req; @hook&.call(req) }
|
|
31
31
|
@env = {}
|
|
32
|
-
@
|
|
32
|
+
@connection = Syntropy::HTTP::ServerConnection.new(@machine, @s_fd, @env, &@app)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def teardown
|
|
@@ -54,14 +54,14 @@ class ConnectionTest < Minitest::Test
|
|
|
54
54
|
|
|
55
55
|
def test_http_unsupported_versions
|
|
56
56
|
write_http_request "GET / HTTP/0.9\r\n\r\n"
|
|
57
|
-
@
|
|
57
|
+
@connection.serve_request
|
|
58
58
|
response = read_client_side
|
|
59
59
|
assert_equal "HTTP/1.1 505\r\nTransfer-Encoding: chunked\r\n\r\n1a\r\nHTTP version not supported\r\n0\r\n\r\n", response
|
|
60
60
|
|
|
61
61
|
setup
|
|
62
62
|
|
|
63
63
|
write_http_request "GET / HTTP/1.0\r\n\r\n"
|
|
64
|
-
@
|
|
64
|
+
@connection.serve_request
|
|
65
65
|
response = read_client_side
|
|
66
66
|
assert_equal "HTTP/1.1 505\r\nTransfer-Encoding: chunked\r\n\r\n1a\r\nHTTP version not supported\r\n0\r\n\r\n", response
|
|
67
67
|
|
|
@@ -69,7 +69,7 @@ class ConnectionTest < Minitest::Test
|
|
|
69
69
|
|
|
70
70
|
@hook = ->(req) { req.respond('hi') }
|
|
71
71
|
write_http_request "GET / HTTP/1.1\r\n\r\n"
|
|
72
|
-
@
|
|
72
|
+
@connection.serve_request
|
|
73
73
|
@machine.close(@s_fd)
|
|
74
74
|
response = read_client_side
|
|
75
75
|
assert_equal "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n2\r\nhi\r\n0\r\n\r\n", response
|
|
@@ -78,14 +78,13 @@ class ConnectionTest < Minitest::Test
|
|
|
78
78
|
def test_basic_request_parsing
|
|
79
79
|
write_http_request "GET / HTTP/1.1\r\n\r\n"
|
|
80
80
|
|
|
81
|
-
@
|
|
81
|
+
@connection.serve_request
|
|
82
82
|
assert_equal 1, @reqs.size
|
|
83
83
|
req = @reqs.shift
|
|
84
84
|
headers = req.headers
|
|
85
85
|
assert_equal({
|
|
86
86
|
':method' => 'get',
|
|
87
|
-
':path' => '/'
|
|
88
|
-
':protocol' => 'http/1.1'
|
|
87
|
+
':path' => '/'
|
|
89
88
|
}, headers)
|
|
90
89
|
end
|
|
91
90
|
|
|
@@ -94,30 +93,28 @@ class ConnectionTest < Minitest::Test
|
|
|
94
93
|
GET /foo HTTP/1.1
|
|
95
94
|
Server: foo.com
|
|
96
95
|
|
|
97
|
-
|
|
96
|
+
GET /bar HTTP/1.1
|
|
98
97
|
|
|
99
98
|
|
|
100
99
|
|
|
101
100
|
HTTP
|
|
102
101
|
write_http_request msg
|
|
103
102
|
|
|
104
|
-
@
|
|
103
|
+
@connection.run
|
|
105
104
|
assert_equal 2, @reqs.size
|
|
106
105
|
req0 = @reqs.shift
|
|
107
106
|
headers = req0.headers
|
|
108
107
|
assert_equal({
|
|
109
108
|
':method' => 'get',
|
|
110
109
|
':path' => '/foo',
|
|
111
|
-
':protocol' => 'http/1.1',
|
|
112
110
|
'server' => 'foo.com'
|
|
113
111
|
}, headers)
|
|
114
112
|
|
|
115
113
|
req1 = @reqs.shift
|
|
116
114
|
headers = req1.headers
|
|
117
115
|
assert_equal({
|
|
118
|
-
':method' => '
|
|
119
|
-
':path' => '/bar'
|
|
120
|
-
':protocol' => 'http/1.1'
|
|
116
|
+
':method' => 'get',
|
|
117
|
+
':path' => '/bar'
|
|
121
118
|
}, headers)
|
|
122
119
|
end
|
|
123
120
|
|
|
@@ -127,7 +124,7 @@ class ConnectionTest < Minitest::Test
|
|
|
127
124
|
Server: foo.com
|
|
128
125
|
Content-Length: 3
|
|
129
126
|
|
|
130
|
-
|
|
127
|
+
abcPOST /bar HTTP/1.1
|
|
131
128
|
Server: bar.com
|
|
132
129
|
Content-Length: 6
|
|
133
130
|
|
|
@@ -137,7 +134,7 @@ class ConnectionTest < Minitest::Test
|
|
|
137
134
|
@bodies = []
|
|
138
135
|
@hook = ->(req) { @bodies << req.read }
|
|
139
136
|
|
|
140
|
-
@
|
|
137
|
+
@connection.run
|
|
141
138
|
assert_equal 2, @reqs.size
|
|
142
139
|
|
|
143
140
|
req0 = @reqs.shift
|
|
@@ -145,7 +142,6 @@ class ConnectionTest < Minitest::Test
|
|
|
145
142
|
assert_equal({
|
|
146
143
|
':method' => 'post',
|
|
147
144
|
':path' => '/foo',
|
|
148
|
-
':protocol' => 'http/1.1',
|
|
149
145
|
'server' => 'foo.com',
|
|
150
146
|
'content-length' => '3',
|
|
151
147
|
':body-done-reading' => true
|
|
@@ -156,9 +152,8 @@ class ConnectionTest < Minitest::Test
|
|
|
156
152
|
req1 = @reqs.shift
|
|
157
153
|
headers = req1.headers
|
|
158
154
|
assert_equal({
|
|
159
|
-
':method' => '
|
|
155
|
+
':method' => 'post',
|
|
160
156
|
':path' => '/bar',
|
|
161
|
-
':protocol' => 'http/1.1',
|
|
162
157
|
'server' => 'bar.com',
|
|
163
158
|
'content-length' => '6',
|
|
164
159
|
':body-done-reading' => true
|
|
@@ -179,7 +174,7 @@ class ConnectionTest < Minitest::Test
|
|
|
179
174
|
de
|
|
180
175
|
0
|
|
181
176
|
|
|
182
|
-
|
|
177
|
+
POST /bar HTTP/1.1
|
|
183
178
|
Server: bar.com
|
|
184
179
|
Transfer-Encoding: chunked
|
|
185
180
|
|
|
@@ -195,7 +190,7 @@ class ConnectionTest < Minitest::Test
|
|
|
195
190
|
@bodies = []
|
|
196
191
|
@hook = ->(req) { @bodies << req.read }
|
|
197
192
|
|
|
198
|
-
@
|
|
193
|
+
@connection.run
|
|
199
194
|
assert_equal 2, @reqs.size
|
|
200
195
|
|
|
201
196
|
req0 = @reqs.shift
|
|
@@ -203,7 +198,6 @@ class ConnectionTest < Minitest::Test
|
|
|
203
198
|
assert_equal({
|
|
204
199
|
':method' => 'post',
|
|
205
200
|
':path' => '/foo',
|
|
206
|
-
':protocol' => 'http/1.1',
|
|
207
201
|
'server' => 'foo.com',
|
|
208
202
|
'transfer-encoding' => 'chunked',
|
|
209
203
|
':body-done-reading' => true
|
|
@@ -214,9 +208,8 @@ class ConnectionTest < Minitest::Test
|
|
|
214
208
|
req1 = @reqs.shift
|
|
215
209
|
headers = req1.headers
|
|
216
210
|
assert_equal({
|
|
217
|
-
':method' => '
|
|
211
|
+
':method' => 'post',
|
|
218
212
|
':path' => '/bar',
|
|
219
|
-
':protocol' => 'http/1.1',
|
|
220
213
|
'server' => 'bar.com',
|
|
221
214
|
'transfer-encoding' => 'chunked',
|
|
222
215
|
':body-done-reading' => true
|
|
@@ -237,7 +230,7 @@ class ConnectionTest < Minitest::Test
|
|
|
237
230
|
de
|
|
238
231
|
0
|
|
239
232
|
|
|
240
|
-
|
|
233
|
+
POST /bar HTTP/1.1
|
|
241
234
|
Server: bar.com
|
|
242
235
|
Content-Length: 31
|
|
243
236
|
|
|
@@ -247,7 +240,7 @@ class ConnectionTest < Minitest::Test
|
|
|
247
240
|
chunks = []
|
|
248
241
|
@hook = ->(req) { req.each_chunk { chunks << it } }
|
|
249
242
|
|
|
250
|
-
@
|
|
243
|
+
@connection.serve_request
|
|
251
244
|
assert_equal 1, @reqs.size
|
|
252
245
|
|
|
253
246
|
req0 = @reqs.shift
|
|
@@ -255,7 +248,6 @@ class ConnectionTest < Minitest::Test
|
|
|
255
248
|
assert_equal({
|
|
256
249
|
':method' => 'post',
|
|
257
250
|
':path' => '/foo',
|
|
258
|
-
':protocol' => 'http/1.1',
|
|
259
251
|
'server' => 'foo.com',
|
|
260
252
|
'transfer-encoding' => 'chunked',
|
|
261
253
|
':body-done-reading' => true
|
|
@@ -263,15 +255,14 @@ class ConnectionTest < Minitest::Test
|
|
|
263
255
|
assert_equal ['abc', 'de'], chunks
|
|
264
256
|
|
|
265
257
|
chunks.clear
|
|
266
|
-
@
|
|
258
|
+
@connection.serve_request
|
|
267
259
|
assert_equal 1, @reqs.size
|
|
268
260
|
|
|
269
261
|
req1 = @reqs.shift
|
|
270
262
|
headers = req1.headers
|
|
271
263
|
assert_equal({
|
|
272
|
-
':method' => '
|
|
264
|
+
':method' => 'post',
|
|
273
265
|
':path' => '/bar',
|
|
274
|
-
':protocol' => 'http/1.1',
|
|
275
266
|
'server' => 'bar.com',
|
|
276
267
|
'content-length' => '31',
|
|
277
268
|
':body-done-reading' => true
|
|
@@ -285,7 +276,7 @@ class ConnectionTest < Minitest::Test
|
|
|
285
276
|
}
|
|
286
277
|
|
|
287
278
|
write_http_request "GET / HTTP/1.1\r\n\r\n"
|
|
288
|
-
@
|
|
279
|
+
@connection.run
|
|
289
280
|
response = read_client_side
|
|
290
281
|
|
|
291
282
|
expected = <<~HTTP.crlf_lines
|
|
@@ -305,7 +296,7 @@ class ConnectionTest < Minitest::Test
|
|
|
305
296
|
|
|
306
297
|
# using HTTP 1.0, server should close connection after responding
|
|
307
298
|
write_http_request "GET / HTTP/1.1\r\n\r\n"
|
|
308
|
-
@
|
|
299
|
+
@connection.run
|
|
309
300
|
|
|
310
301
|
response = read_client_side
|
|
311
302
|
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\nd\r\nHello, world!\r\n0\r\n\r\n"
|
|
@@ -318,14 +309,14 @@ class ConnectionTest < Minitest::Test
|
|
|
318
309
|
}
|
|
319
310
|
|
|
320
311
|
write_http_request "GET / HTTP/1.1\r\nConnection: close\r\n\r\n", false
|
|
321
|
-
res = @
|
|
312
|
+
res = @connection.serve_request
|
|
322
313
|
assert_equal false, res
|
|
323
314
|
|
|
324
315
|
response = read_client_side
|
|
325
316
|
assert_equal("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n2\r\nHi\r\n0\r\n\r\n", response)
|
|
326
317
|
|
|
327
318
|
write_http_request "GET / HTTP/1.1\r\n\r\n", false
|
|
328
|
-
res = @
|
|
319
|
+
res = @connection.serve_request
|
|
329
320
|
assert_equal true, res
|
|
330
321
|
|
|
331
322
|
response = read_client_side
|
|
@@ -344,7 +335,7 @@ class ConnectionTest < Minitest::Test
|
|
|
344
335
|
}
|
|
345
336
|
|
|
346
337
|
write_http_request "GET / HTTP/1.1\r\n\r\nGET / HTTP/1.1\r\nFoo: bar\r\n\r\n"
|
|
347
|
-
@
|
|
338
|
+
@connection.run
|
|
348
339
|
response = read_client_side
|
|
349
340
|
|
|
350
341
|
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\nd\r\nHello, world!\r\n0\r\n\r\n" +
|
|
@@ -368,7 +359,7 @@ class ConnectionTest < Minitest::Test
|
|
|
368
359
|
|
|
369
360
|
msg = "POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n"
|
|
370
361
|
write_http_request msg, false
|
|
371
|
-
@machine.spin { @
|
|
362
|
+
@machine.spin { @connection.serve_request rescue nil }
|
|
372
363
|
@machine.sleep(0.01)
|
|
373
364
|
|
|
374
365
|
assert request
|
|
@@ -417,7 +408,7 @@ class ConnectionTest < Minitest::Test
|
|
|
417
408
|
|
|
418
409
|
msg = "GET / HTTP/1.1\r\nUpgrade: echo\r\nConnection: upgrade\r\n\r\n"
|
|
419
410
|
write_http_request(msg, false)
|
|
420
|
-
@machine.spin { @
|
|
411
|
+
@machine.spin { @connection.serve_request rescue nil }
|
|
421
412
|
@machine.sleep(0.01)
|
|
422
413
|
|
|
423
414
|
response = read_client_side
|
|
@@ -462,7 +453,7 @@ class ConnectionTest < Minitest::Test
|
|
|
462
453
|
|
|
463
454
|
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
|
464
455
|
@machine.spin do
|
|
465
|
-
@
|
|
456
|
+
@connection.serve_request
|
|
466
457
|
rescue => e
|
|
467
458
|
p e
|
|
468
459
|
p e.backtrace
|
|
@@ -498,7 +489,7 @@ class ConnectionTest < Minitest::Test
|
|
|
498
489
|
|
|
499
490
|
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
|
500
491
|
@machine.spin do
|
|
501
|
-
@
|
|
492
|
+
@connection.serve_request
|
|
502
493
|
rescue => e
|
|
503
494
|
p e
|
|
504
495
|
p e.backtrace
|
|
@@ -531,7 +522,7 @@ class ConnectionTest < Minitest::Test
|
|
|
531
522
|
count = 0
|
|
532
523
|
|
|
533
524
|
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
|
534
|
-
@machine.spin { @
|
|
525
|
+
@machine.spin { @connection.serve_request }
|
|
535
526
|
|
|
536
527
|
while (data = read_client_side(65536))
|
|
537
528
|
response << data
|
|
@@ -551,7 +542,7 @@ class ConnectionTest < Minitest::Test
|
|
|
551
542
|
end
|
|
552
543
|
|
|
553
544
|
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
|
554
|
-
@
|
|
545
|
+
@connection.serve_request
|
|
555
546
|
response = read_client_side(65536)
|
|
556
547
|
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nServer: Syntropy\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
|
|
557
548
|
assert_equal expected, response
|
|
@@ -559,7 +550,7 @@ class ConnectionTest < Minitest::Test
|
|
|
559
550
|
@env[:server_headers] = "Server: TP3\r\n"
|
|
560
551
|
|
|
561
552
|
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
|
562
|
-
@
|
|
553
|
+
@connection.serve_request
|
|
563
554
|
response = read_client_side(65536)
|
|
564
555
|
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nServer: TP3\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
|
|
565
556
|
assert_equal expected, response
|
|
@@ -572,7 +563,7 @@ class ConnectionTest < Minitest::Test
|
|
|
572
563
|
}
|
|
573
564
|
|
|
574
565
|
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
|
575
|
-
@
|
|
566
|
+
@connection.serve_request
|
|
576
567
|
response = read_client_side(65536)
|
|
577
568
|
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nSet-Cookie: foo=bar\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
|
|
578
569
|
assert_equal expected, response
|
|
@@ -583,7 +574,7 @@ class ConnectionTest < Minitest::Test
|
|
|
583
574
|
}
|
|
584
575
|
|
|
585
576
|
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
|
586
|
-
@
|
|
577
|
+
@connection.serve_request
|
|
587
578
|
response = read_client_side(65536)
|
|
588
579
|
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nSet-Cookie: foo=bar\r\nContent-Type: text/plain\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
|
|
589
580
|
assert_equal expected, response
|
|
@@ -597,7 +588,7 @@ class ConnectionTest < Minitest::Test
|
|
|
597
588
|
}
|
|
598
589
|
|
|
599
590
|
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
|
600
|
-
@
|
|
591
|
+
@connection.serve_request
|
|
601
592
|
response = read_client_side(65536)
|
|
602
593
|
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nSet-Cookie: foo=bar\r\nFoo: bar\r\nContent-Type: text/plain\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
|
|
603
594
|
assert_equal expected, response
|
|
@@ -610,7 +601,7 @@ class ConnectionTest < Minitest::Test
|
|
|
610
601
|
}
|
|
611
602
|
|
|
612
603
|
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
|
613
|
-
@
|
|
604
|
+
@connection.serve_request
|
|
614
605
|
response = read_client_side(65536)
|
|
615
606
|
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nSet-Cookie: foo=bar; HttpOnly\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
|
|
616
607
|
assert_equal expected, response
|
|
@@ -624,7 +615,7 @@ class ConnectionTest < Minitest::Test
|
|
|
624
615
|
}
|
|
625
616
|
|
|
626
617
|
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
|
627
|
-
@
|
|
618
|
+
@connection.serve_request
|
|
628
619
|
response = read_client_side(65536)
|
|
629
620
|
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nSet-Cookie: foo=bar; HttpOnly\r\nSet-Cookie: bar=baz\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
|
|
630
621
|
assert_equal expected, response
|
|
@@ -640,7 +631,7 @@ class ConnectionTest < Minitest::Test
|
|
|
640
631
|
}
|
|
641
632
|
|
|
642
633
|
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
|
643
|
-
@
|
|
634
|
+
@connection.serve_request
|
|
644
635
|
response = read_client_side(65536)
|
|
645
636
|
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nSet-Cookie: a=1\r\nSet-Cookie: b=2\r\nSet-Cookie: c=3\r\nSet-Cookie: d=4\r\nSet-Cookie: e=5\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
|
|
646
637
|
assert_equal expected, response
|
data/test/test_json_api.rb
CHANGED
|
@@ -11,7 +11,7 @@ class JSONAPITest < Minitest::Test
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def bar!(req)
|
|
14
|
-
@value = req.query[
|
|
14
|
+
@value = req.query['v']
|
|
15
15
|
true
|
|
16
16
|
end
|
|
17
17
|
end
|
|
@@ -28,7 +28,7 @@ class JSONAPITest < Minitest::Test
|
|
|
28
28
|
req = mock_req(':method' => 'GET', ':path' => '/?q=foo')
|
|
29
29
|
@app.call(req)
|
|
30
30
|
assert_equal HTTP::OK, req.response_status
|
|
31
|
-
assert_equal({ status
|
|
31
|
+
assert_equal({ 'status' => 'OK', 'response' => nil }, req.response_json)
|
|
32
32
|
|
|
33
33
|
req = mock_req(':method' => 'POST', ':path' => '/?q=foo')
|
|
34
34
|
@app.call(req)
|
|
@@ -38,7 +38,7 @@ class JSONAPITest < Minitest::Test
|
|
|
38
38
|
req = mock_req(':method' => 'POST', ':path' => '/?q=bar&v=foo')
|
|
39
39
|
@app.call(req)
|
|
40
40
|
assert_equal HTTP::OK, req.response_status
|
|
41
|
-
assert_equal({ status
|
|
41
|
+
assert_equal({ 'status' => 'OK', 'response' => true }, req.response_json)
|
|
42
42
|
|
|
43
43
|
req = mock_req(':method' => 'GET', ':path' => '/?q=bar&v=foo')
|
|
44
44
|
@app.call(req)
|
|
@@ -47,12 +47,12 @@ class JSONAPITest < Minitest::Test
|
|
|
47
47
|
req = mock_req(':method' => 'GET', ':path' => '/?q=foo')
|
|
48
48
|
@app.call(req)
|
|
49
49
|
assert_equal HTTP::OK, req.response_status
|
|
50
|
-
assert_equal({ status
|
|
50
|
+
assert_equal({ 'status' => 'OK', 'response' => 'foo' }, req.response_json)
|
|
51
51
|
|
|
52
52
|
req = mock_req(':method' => 'GET', ':path' => '/?q=foo')
|
|
53
53
|
@app.call(req)
|
|
54
54
|
assert_equal HTTP::OK, req.response_status
|
|
55
|
-
assert_equal({ status
|
|
55
|
+
assert_equal({ 'status' => 'OK', 'response' => 'foo' }, req.response_json)
|
|
56
56
|
|
|
57
57
|
req = mock_req(':method' => 'GET', ':path' => '/?q=xxx')
|
|
58
58
|
@app.call(req)
|
|
@@ -79,4 +79,35 @@ class ModuleTest < Minitest::Test
|
|
|
79
79
|
mod = @loader.load('mod/foo/index')
|
|
80
80
|
assert_equal '/mod/foo', mod.env[:ref]
|
|
81
81
|
end
|
|
82
|
+
|
|
83
|
+
def test_list
|
|
84
|
+
list = @loader.list('_layout')
|
|
85
|
+
assert_equal ['_layout/default'], list
|
|
86
|
+
|
|
87
|
+
list = @loader.list('_lib')
|
|
88
|
+
assert_equal [
|
|
89
|
+
'_lib/callable',
|
|
90
|
+
'_lib/dep',
|
|
91
|
+
'_lib/env',
|
|
92
|
+
'_lib/klass',
|
|
93
|
+
'_lib/missing-export',
|
|
94
|
+
'_lib/self',
|
|
95
|
+
], list
|
|
96
|
+
|
|
97
|
+
list = @loader.list('about')
|
|
98
|
+
assert_equal [
|
|
99
|
+
'about/_error',
|
|
100
|
+
'about/index',
|
|
101
|
+
'about/raise'
|
|
102
|
+
], list
|
|
103
|
+
|
|
104
|
+
list = @loader.list('assets')
|
|
105
|
+
assert_equal [], list
|
|
106
|
+
|
|
107
|
+
list = @loader.list('mod')
|
|
108
|
+
assert_equal [], list
|
|
109
|
+
|
|
110
|
+
list = @loader.list('mod/bar')
|
|
111
|
+
assert_equal ['mod/bar/index+'], list
|
|
112
|
+
end
|
|
82
113
|
end
|