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.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/CHANGELOG.md +30 -0
  4. data/TODO.md +46 -1
  5. data/bin/syntropy +8 -86
  6. data/cmd/_banner.rb +16 -0
  7. data/cmd/console.rb +77 -0
  8. data/cmd/help.rb +12 -0
  9. data/cmd/serve.rb +95 -0
  10. data/cmd/test.rb +40 -0
  11. data/examples/{counter.rb → basic/counter.rb} +1 -1
  12. data/examples/{templates.rb → basic/templates.rb} +1 -1
  13. data/examples/blog/app/_layout/default.rb +11 -0
  14. data/examples/blog/app/_lib/post_store.rb +47 -0
  15. data/examples/blog/app/_schema/2026-01-01-initial.rb +9 -0
  16. data/examples/blog/app/_setup.rb +4 -0
  17. data/examples/blog/app/index.rb +7 -0
  18. data/examples/blog/app/posts/[id]/edit.rb +33 -0
  19. data/examples/blog/app/posts/[id]/index.rb +58 -0
  20. data/examples/blog/app/posts/index.rb +38 -0
  21. data/examples/blog/app/posts/new.rb +29 -0
  22. data/examples/mcp-oauth/.ruby-version +1 -0
  23. data/examples/mcp-oauth/Gemfile +8 -0
  24. data/examples/mcp-oauth/README.md +128 -0
  25. data/examples/mcp-oauth/app/.well-known/oauth-authorization-server.rb +18 -0
  26. data/examples/mcp-oauth/app/.well-known/oauth-protected-resource.rb +10 -0
  27. data/examples/mcp-oauth/app/_lib/auth_store.rb +23 -0
  28. data/examples/mcp-oauth/app/index.md +1 -0
  29. data/examples/mcp-oauth/app/mcp.rb +85 -0
  30. data/examples/mcp-oauth/app/oauth/authorize.rb +18 -0
  31. data/examples/mcp-oauth/app/oauth/consent.rb +86 -0
  32. data/examples/mcp-oauth/app/oauth/register.rb +14 -0
  33. data/examples/mcp-oauth/app/oauth/token.rb +79 -0
  34. data/examples/mcp-oauth/app/signin.rb +85 -0
  35. data/examples/mcp-oauth/test/helper.rb +9 -0
  36. data/examples/mcp-oauth/test/test_app.rb +27 -0
  37. data/examples/mcp-oauth/test/test_oauth.rb +628 -0
  38. data/lib/syntropy/app.rb +34 -9
  39. data/lib/syntropy/applets/builtin/default_error_handler.rb +3 -3
  40. data/lib/syntropy/applets/builtin/req.rb +1 -1
  41. data/lib/syntropy/db/connection_pool.rb +71 -0
  42. data/lib/syntropy/db/schema.rb +92 -0
  43. data/lib/syntropy/db/store.rb +31 -0
  44. data/lib/syntropy/dev_mode.rb +1 -1
  45. data/lib/syntropy/errors.rb +6 -0
  46. data/lib/syntropy/http/client.rb +43 -0
  47. data/lib/syntropy/http/client_connection.rb +36 -0
  48. data/lib/syntropy/http/io_extensions.rb +176 -0
  49. data/lib/syntropy/http/server.rb +5 -5
  50. data/lib/syntropy/http/{connection.rb → server_connection.rb} +15 -91
  51. data/lib/syntropy/http.rb +3 -1
  52. data/lib/syntropy/logger.rb +5 -1
  53. data/lib/syntropy/{module.rb → module_loader.rb} +47 -8
  54. data/lib/syntropy/papercraft_extensions.rb +1 -1
  55. data/lib/syntropy/request/mock_adapter.rb +2 -0
  56. data/lib/syntropy/request/request_info.rb +22 -4
  57. data/lib/syntropy/request/response.rb +2 -2
  58. data/lib/syntropy/request/validation.rb +11 -5
  59. data/lib/syntropy/routing_tree.rb +2 -1
  60. data/lib/syntropy/test.rb +77 -0
  61. data/lib/syntropy/version.rb +1 -1
  62. data/lib/syntropy.rb +5 -23
  63. data/syntropy.gemspec +3 -3
  64. data/test/app/.well-known/foo.rb +3 -0
  65. data/test/app/_hook.rb +1 -1
  66. data/test/app/by_method.rb +9 -0
  67. data/test/app_setup/_setup.rb +7 -0
  68. data/test/app_setup/index.rb +1 -0
  69. data/test/app_with_schema/_schema/2026-01-02-foo.rb +12 -0
  70. data/test/app_with_schema/_schema/2026-05-30-bar.rb +7 -0
  71. data/test/helper.rb +1 -25
  72. data/test/schema/2026-01-02-foo.rb +12 -0
  73. data/test/schema/2026-05-30-bar.rb +7 -0
  74. data/test/test_app.rb +110 -70
  75. data/test/test_caching.rb +1 -1
  76. data/test/{test_connection_pool.rb → test_db_connection_pool.rb} +7 -2
  77. data/test/test_db_schema.rb +96 -0
  78. data/test/test_db_store.rb +24 -0
  79. data/test/test_http_client.rb +52 -0
  80. data/test/test_http_client_connection.rb +43 -0
  81. data/test/test_http_protocol.rb +250 -0
  82. data/test/{test_connection.rb → test_http_server_connection.rb} +39 -48
  83. data/test/test_json_api.rb +5 -5
  84. data/test/{test_module.rb → test_module_loader.rb} +31 -0
  85. data/test/{test_request_extensions.rb → test_request.rb} +153 -18
  86. data/test/test_routing_tree.rb +15 -3
  87. data/test/test_server.rb +9 -13
  88. metadata +84 -36
  89. data/lib/syntropy/connection_pool.rb +0 -61
  90. data/test/test_request_info.rb +0 -90
  91. /data/examples/{bad.rb → basic/bad.rb} +0 -0
  92. /data/examples/{card.rb → basic/card.rb} +0 -0
  93. /data/examples/{counter.js → basic/counter.js} +0 -0
  94. /data/examples/{counter_api.rb → basic/counter_api.rb} +0 -0
  95. /data/examples/{favicon.ico → basic/favicon.ico} +0 -0
  96. /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 ConnectionTest < Minitest::Test
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
- @adapter = Syntropy::HTTP::Connection.new(nil, @machine, @s_fd, @env, &@app)
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
- @adapter.serve_request
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
- @adapter.serve_request
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
- @adapter.serve_request
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
- @adapter.serve_request
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
- SCHMET /bar HTTP/1.1
96
+ GET /bar HTTP/1.1
98
97
 
99
98
 
100
99
 
101
100
  HTTP
102
101
  write_http_request msg
103
102
 
104
- @adapter.run
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' => 'schmet',
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
- abcSCHMOST /bar HTTP/1.1
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
- @adapter.run
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' => 'schmost',
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
- SCHMOST /bar HTTP/1.1
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
- @adapter.run
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' => 'schmost',
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
- SCHMOST /bar HTTP/1.1
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
- @adapter.serve_request
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
- @adapter.serve_request
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' => 'schmost',
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
- @adapter.run
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
- @adapter.run
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 = @adapter.serve_request
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 = @adapter.serve_request
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
- @adapter.run
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 { @adapter.serve_request rescue nil }
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 { @adapter.serve_request rescue nil }
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
- @adapter.serve_request
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
- @adapter.serve_request
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 { @adapter.serve_request }
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
- @adapter.serve_request
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
- @adapter.serve_request
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
- @adapter.serve_request
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
- @adapter.serve_request
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
- @adapter.serve_request
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
- @adapter.serve_request
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
- @adapter.serve_request
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
- @adapter.serve_request
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
@@ -11,7 +11,7 @@ class JSONAPITest < Minitest::Test
11
11
  end
12
12
 
13
13
  def bar!(req)
14
- @value = req.query[:v]
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: 'OK', response: nil }, req.response_json)
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: 'OK', response: true }, req.response_json)
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: 'OK', response: 'foo' }, req.response_json)
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: 'OK', response: 'foo' }, req.response_json)
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