syntropy 0.31.0 → 0.33.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 +20 -0
- data/TODO.md +7 -1
- data/cmd/console.rb +77 -0
- data/cmd/serve.rb +1 -3
- data/cmd/test.rb +76 -20
- 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 +61 -0
- data/examples/blog/app/posts/index.rb +40 -0
- data/examples/blog/app/posts/new.rb +29 -0
- data/examples/mcp-oauth/README.md +3 -3
- data/examples/mcp-oauth/app/mcp.rb +55 -8
- data/examples/mcp-oauth/app/oauth/authorize.rb +0 -8
- data/examples/mcp-oauth/app/oauth/register.rb +0 -1
- data/examples/mcp-oauth/test/test_app.rb +2 -20
- data/examples/mcp-oauth/test/test_oauth.rb +93 -217
- data/lib/syntropy/app.rb +23 -9
- 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/http/io_extensions.rb +33 -5
- data/lib/syntropy/http/server_connection.rb +21 -62
- data/lib/syntropy/{module.rb → module_loader.rb} +48 -8
- data/lib/syntropy/request/request_info.rb +3 -4
- data/lib/syntropy/request/response.rb +2 -2
- data/lib/syntropy/request/session.rb +113 -0
- data/lib/syntropy/request/validation.rb +1 -2
- data/lib/syntropy/request.rb +9 -0
- data/lib/syntropy/test.rb +84 -1
- data/lib/syntropy/version.rb +1 -1
- data/lib/syntropy.rb +4 -2
- data/syntropy.gemspec +3 -1
- 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/schema/2026-01-02-foo.rb +12 -0
- data/test/schema/2026-05-30-bar.rb +7 -0
- data/test/test_app.rb +58 -3
- 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_protocol.rb +250 -0
- data/test/test_http_server_connection.rb +18 -24
- data/test/test_json_api.rb +1 -1
- data/test/{test_module.rb → test_module_loader.rb} +31 -0
- data/test/test_request.rb +7 -4
- data/test/test_request_session.rb +254 -0
- data/test/test_server.rb +9 -13
- metadata +63 -12
- data/examples/mcp-oauth/test/helper.rb +0 -9
- data/lib/syntropy/connection_pool.rb +0 -61
|
@@ -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
|
|
@@ -84,8 +84,7 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
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,7 +93,7 @@ class HTTPServerConnectionTest < 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
|
|
|
@@ -108,16 +107,14 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
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 HTTPServerConnectionTest < 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
|
|
|
@@ -145,7 +142,6 @@ class HTTPServerConnectionTest < 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 HTTPServerConnectionTest < 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 HTTPServerConnectionTest < 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
|
|
|
@@ -203,7 +198,6 @@ class HTTPServerConnectionTest < 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 HTTPServerConnectionTest < 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 HTTPServerConnectionTest < 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
|
|
|
@@ -255,7 +248,6 @@ class HTTPServerConnectionTest < 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
|
|
@@ -269,9 +261,8 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
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
|
|
@@ -605,7 +596,7 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
605
596
|
|
|
606
597
|
def test_set_cookie_single
|
|
607
598
|
@hook = ->(req) {
|
|
608
|
-
req.set_cookie('foo
|
|
599
|
+
req.set_cookie('foo', 'bar; HttpOnly')
|
|
609
600
|
req.respond('foo')
|
|
610
601
|
}
|
|
611
602
|
|
|
@@ -619,7 +610,8 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
619
610
|
|
|
620
611
|
def test_set_cookie_multi1
|
|
621
612
|
@hook = ->(req) {
|
|
622
|
-
req.set_cookie('foo
|
|
613
|
+
req.set_cookie('foo', 'bar; HttpOnly')
|
|
614
|
+
req.set_cookie('bar', 'baz')
|
|
623
615
|
req.respond('foo')
|
|
624
616
|
}
|
|
625
617
|
|
|
@@ -633,9 +625,11 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
633
625
|
|
|
634
626
|
def test_set_cookie_multi2
|
|
635
627
|
@hook = ->(req) {
|
|
636
|
-
req.set_cookie('a
|
|
637
|
-
req.set_cookie('
|
|
638
|
-
req.set_cookie('
|
|
628
|
+
req.set_cookie('a', '1')
|
|
629
|
+
req.set_cookie('b', '2')
|
|
630
|
+
req.set_cookie('c', '3')
|
|
631
|
+
req.set_cookie('d', '4')
|
|
632
|
+
req.set_cookie('e', '5')
|
|
639
633
|
req.respond('foo')
|
|
640
634
|
}
|
|
641
635
|
|
data/test/test_json_api.rb
CHANGED
|
@@ -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
|
data/test/test_request.rb
CHANGED
|
@@ -10,20 +10,20 @@ class RequestInfoTest < Minitest::Test
|
|
|
10
10
|
|
|
11
11
|
r = Syntropy::MockAdapter.mock(':path' => '/test/path?a=1&b=2&c=3%2f4')
|
|
12
12
|
assert_equal '/test/path', r.path
|
|
13
|
-
assert_equal({ a
|
|
13
|
+
assert_equal({ 'a' => '1', 'b' => '2', 'c' => '3/4' }, r.query)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def test_query
|
|
17
17
|
r = Syntropy::MockAdapter.mock(':path' => '/GponForm/diag_Form?images/')
|
|
18
18
|
assert_equal '/GponForm/diag_Form', r.path
|
|
19
|
-
assert_equal({
|
|
19
|
+
assert_equal({ 'images/' => true }, r.query)
|
|
20
20
|
|
|
21
21
|
r = Syntropy::MockAdapter.mock(':path' => '/?a=1&b=2')
|
|
22
22
|
assert_equal '/', r.path
|
|
23
|
-
assert_equal({a
|
|
23
|
+
assert_equal({ 'a' => '1', 'b' => '2'}, r.query)
|
|
24
24
|
|
|
25
25
|
r = Syntropy::MockAdapter.mock(':path' => '/?l=a&t=&x=42')
|
|
26
|
-
assert_equal({l
|
|
26
|
+
assert_equal({ 'l' => 'a', 't' => '', 'x' => '42'}, r.query)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def test_host
|
|
@@ -157,6 +157,9 @@ class ValidationTest < Minitest::Test
|
|
|
157
157
|
assert_equal 'foo', @req.validate('foo', [String, nil])
|
|
158
158
|
assert_nil @req.validate(nil, [String, nil])
|
|
159
159
|
|
|
160
|
+
assert_equal 'foo', @req.validate('foo', String, /.+/)
|
|
161
|
+
assert_raises(VE) { @req.validate('', String, /.+/) }
|
|
162
|
+
|
|
160
163
|
assert_equal 123, @req.validate('123', Integer)
|
|
161
164
|
assert_raises(VE) { @req.validate('a123', Integer) }
|
|
162
165
|
assert_equal 123, @req.validate('123', Integer, 120..125)
|