syntropy 0.32.0 → 0.34.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/CHANGELOG.md +14 -0
- data/TODO.md +0 -39
- data/cmd/console.rb +18 -7
- data/cmd/serve.rb +26 -20
- data/cmd/test.rb +90 -21
- data/examples/blog/.gitignore +1 -0
- data/examples/blog/app/_lib/database.rb +13 -0
- data/examples/blog/app/_lib/{post_store.rb → posts.rb} +3 -1
- data/examples/blog/app/posts/[id]/edit.rb +2 -2
- data/examples/blog/app/posts/[id]/index.rb +8 -5
- data/examples/blog/app/posts/index.rb +7 -5
- data/examples/blog/app/posts/new.rb +1 -1
- data/examples/blog/config/development.rb +5 -0
- data/examples/blog/config/production.rb +4 -0
- data/examples/blog/config/test.rb +5 -0
- data/examples/blog/test/test_posts.rb +65 -0
- data/examples/mcp-oauth/app/oauth/token.rb +1 -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 +48 -40
- data/lib/syntropy/applets/builtin/auto_refresh/watch.sse.rb +1 -1
- data/lib/syntropy/db/schema.rb +1 -1
- data/lib/syntropy/db/store.rb +2 -0
- data/lib/syntropy/errors.rb +6 -2
- data/lib/syntropy/http/client.rb +1 -0
- data/lib/syntropy/http/server_connection.rb +15 -13
- data/lib/syntropy/json_api.rb +27 -1
- data/lib/syntropy/logger.rb +81 -27
- data/lib/syntropy/markdown.rb +61 -32
- data/lib/syntropy/mime_types.rb +9 -5
- data/lib/syntropy/module_loader.rb +25 -13
- data/lib/syntropy/papercraft_extensions.rb +2 -2
- data/lib/syntropy/request/mock_adapter.rb +10 -8
- data/lib/syntropy/request/request_info.rb +91 -0
- data/lib/syntropy/request/response.rb +3 -14
- data/lib/syntropy/request/validation.rb +1 -0
- data/lib/syntropy/request.rb +55 -14
- data/lib/syntropy/routing_tree.rb +27 -28
- data/lib/syntropy/session.rb +198 -0
- data/lib/syntropy/side_run.rb +25 -2
- data/lib/syntropy/test.rb +168 -2
- data/lib/syntropy/utils.rb +53 -18
- data/lib/syntropy/version.rb +1 -1
- data/lib/syntropy.rb +44 -10
- data/syntropy.gemspec +1 -0
- data/test/bm_router_proc.rb +4 -4
- data/test/fixtures/app/class_instance.rb +5 -0
- data/test/fixtures/app/http.rb +5 -0
- data/test/fixtures/app/post_ct.rb +5 -0
- data/test/fixtures/app/singleton.rb +3 -0
- data/test/test_app.rb +13 -52
- data/test/test_caching.rb +2 -2
- data/test/test_db_schema.rb +1 -1
- data/test/test_http_server_connection.rb +11 -8
- data/test/test_module_loader.rb +5 -2
- data/test/test_request_session.rb +254 -0
- data/test/test_response.rb +0 -19
- data/test/test_routing_tree.rb +69 -69
- data/test/test_server.rb +5 -9
- data/test/test_test.rb +70 -0
- metadata +67 -42
- data/examples/blog/app/_setup.rb +0 -4
- data/examples/mcp-oauth/test/helper.rb +0 -9
- /data/test/{app → fixtures/app}/.well-known/foo.rb +0 -0
- /data/test/{app → fixtures/app}/_hook.rb +0 -0
- /data/test/{app → fixtures/app}/_layout/default.rb +0 -0
- /data/test/{app → fixtures/app}/_lib/callable.rb +0 -0
- /data/test/{app → fixtures/app}/_lib/dep.rb +0 -0
- /data/test/{app → fixtures/app}/_lib/env.rb +0 -0
- /data/test/{app → fixtures/app}/_lib/klass.rb +0 -0
- /data/test/{app → fixtures/app}/_lib/missing-export.rb +0 -0
- /data/test/{app → fixtures/app}/_lib/self.rb +0 -0
- /data/test/{app → fixtures/app}/about/_error.rb +0 -0
- /data/test/{app → fixtures/app}/about/foo.md +0 -0
- /data/test/{app → fixtures/app}/about/index.rb +0 -0
- /data/test/{app → fixtures/app}/about/raise.rb +0 -0
- /data/test/{app → fixtures/app}/api+.rb +0 -0
- /data/test/{app → fixtures/app}/assets/style.css +0 -0
- /data/test/{app → fixtures/app}/bad_mod.rb +0 -0
- /data/test/{app → fixtures/app}/bar.rb +0 -0
- /data/test/{app → fixtures/app}/baz.rb +0 -0
- /data/test/{app → fixtures/app}/by_method.rb +0 -0
- /data/test/{app → fixtures/app}/deps.rb +0 -0
- /data/test/{app → fixtures/app}/index.html +0 -0
- /data/test/{app → fixtures/app}/mod/bar/index+.rb +0 -0
- /data/test/{app → fixtures/app}/mod/foo/index.rb +0 -0
- /data/test/{app → fixtures/app}/mod/path/a.rb +0 -0
- /data/test/{app → fixtures/app}/mod/path/b.rb +0 -0
- /data/test/{app → fixtures/app}/params/[foo].rb +0 -0
- /data/test/{app → fixtures/app}/rss.rb +0 -0
- /data/test/{app → fixtures/app}/tmp.rb +0 -0
- /data/test/{app_custom → fixtures/app_custom}/_site.rb +0 -0
- /data/test/{app_multi_site → fixtures/app_multi_site}/_site.rb +0 -0
- /data/test/{app_multi_site → fixtures/app_multi_site}/bar.baz/index.html +0 -0
- /data/test/{app_multi_site → fixtures/app_multi_site}/foo.bar/index.html +0 -0
- /data/test/{app_setup → fixtures/app_setup}/_setup.rb +0 -0
- /data/test/{app_setup → fixtures/app_setup}/index.rb +0 -0
- /data/test/{app_with_schema → fixtures/app_with_schema}/_schema/2026-01-02-foo.rb +0 -0
- /data/test/{app_with_schema → fixtures/app_with_schema}/_schema/2026-05-30-bar.rb +0 -0
- /data/test/{schema → fixtures/schema}/2026-01-02-foo.rb +0 -0
- /data/test/{schema → fixtures/schema}/2026-05-30-bar.rb +0 -0
data/syntropy.gemspec
CHANGED
data/test/bm_router_proc.rb
CHANGED
|
@@ -138,8 +138,8 @@ def make_tmp_file_tree(dir, spec)
|
|
|
138
138
|
dir
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
make_tmp_file_tree(
|
|
141
|
+
app_root = "/tmp/#{__FILE__.gsub('/', '-')}-#{SecureRandom.hex}"
|
|
142
|
+
make_tmp_file_tree(app_root, {
|
|
143
143
|
'index.rb': "export ->(req) { req.redirect('/hello') }",
|
|
144
144
|
'hello': {
|
|
145
145
|
'index.rb': "export ->(req) { req.respond('Hello!', 'Content-Type' => 'text/html') }",
|
|
@@ -149,7 +149,7 @@ make_tmp_file_tree(ROOT_DIR, {
|
|
|
149
149
|
|
|
150
150
|
machine = UM.new
|
|
151
151
|
syntropy_app = Syntropy::App.new(
|
|
152
|
-
|
|
152
|
+
app_root: app_root,
|
|
153
153
|
mount_path: '/',
|
|
154
154
|
machine: machine
|
|
155
155
|
)
|
|
@@ -185,7 +185,7 @@ BM.run do
|
|
|
185
185
|
def setup
|
|
186
186
|
machine = UM.new
|
|
187
187
|
syntropy_app = Syntropy::App.new(
|
|
188
|
-
|
|
188
|
+
app_root: app_root,
|
|
189
189
|
mount_path: '/',
|
|
190
190
|
# watch_files: 0.05,
|
|
191
191
|
machine: machine
|
data/test/test_app.rb
CHANGED
|
@@ -5,7 +5,7 @@ require_relative 'helper'
|
|
|
5
5
|
class AppTest < Minitest::Test
|
|
6
6
|
HTTP = Syntropy::HTTP
|
|
7
7
|
|
|
8
|
-
APP_ROOT = File.join(__dir__, 'app')
|
|
8
|
+
APP_ROOT = File.join(__dir__, 'fixtures/app')
|
|
9
9
|
|
|
10
10
|
def setup
|
|
11
11
|
@machine = UM.new
|
|
@@ -14,7 +14,7 @@ class AppTest < Minitest::Test
|
|
|
14
14
|
@tmp_fn = File.join(APP_ROOT, 'tmp.rb')
|
|
15
15
|
|
|
16
16
|
@app = Syntropy::App.new(
|
|
17
|
-
|
|
17
|
+
app_root: APP_ROOT,
|
|
18
18
|
mount_path: '/test',
|
|
19
19
|
watch_files: 0.05,
|
|
20
20
|
machine: @machine
|
|
@@ -133,6 +133,9 @@ class AppTest < Minitest::Test
|
|
|
133
133
|
|
|
134
134
|
req = @test_harness.request(':method' => 'DELETE', ':path' => '/test/by_method')
|
|
135
135
|
assert_equal HTTP::METHOD_NOT_ALLOWED, req.response_status
|
|
136
|
+
|
|
137
|
+
req = @test_harness.request(':method' => 'GET', ':path' => '/test/http')
|
|
138
|
+
assert_equal HTTP::TEAPOT, req.response_status
|
|
136
139
|
end
|
|
137
140
|
|
|
138
141
|
def test_automatic_redirect_on_trailing_slash
|
|
@@ -183,13 +186,13 @@ end
|
|
|
183
186
|
class CustomAppTest < Minitest::Test
|
|
184
187
|
HTTP = Syntropy::HTTP
|
|
185
188
|
|
|
186
|
-
APP_ROOT = File.join(__dir__, 'app_custom')
|
|
189
|
+
APP_ROOT = File.join(__dir__, 'fixtures/app_custom')
|
|
187
190
|
|
|
188
191
|
def setup
|
|
189
192
|
@machine = UM.new
|
|
190
193
|
@app = Syntropy::App.load(
|
|
191
194
|
machine: @machine,
|
|
192
|
-
|
|
195
|
+
app_root: APP_ROOT,
|
|
193
196
|
mount_path: '/'
|
|
194
197
|
)
|
|
195
198
|
@test_harness = Syntropy::TestHarness.new(@app)
|
|
@@ -205,13 +208,13 @@ end
|
|
|
205
208
|
class MultiSiteAppTest < Minitest::Test
|
|
206
209
|
HTTP = Syntropy::HTTP
|
|
207
210
|
|
|
208
|
-
APP_ROOT = File.join(__dir__, 'app_multi_site')
|
|
211
|
+
APP_ROOT = File.join(__dir__, 'fixtures/app_multi_site')
|
|
209
212
|
|
|
210
213
|
def setup
|
|
211
214
|
@machine = UM.new
|
|
212
215
|
@app = Syntropy::App.load(
|
|
213
216
|
machine: @machine,
|
|
214
|
-
|
|
217
|
+
app_root: APP_ROOT,
|
|
215
218
|
mount_path: '/'
|
|
216
219
|
)
|
|
217
220
|
@test_harness = Syntropy::TestHarness.new(@app)
|
|
@@ -233,7 +236,7 @@ end
|
|
|
233
236
|
class AppAPITest < Minitest::Test
|
|
234
237
|
HTTP = Syntropy::HTTP
|
|
235
238
|
|
|
236
|
-
APP_ROOT = File.join(__dir__, 'app')
|
|
239
|
+
APP_ROOT = File.join(__dir__, 'fixtures/app')
|
|
237
240
|
|
|
238
241
|
def setup
|
|
239
242
|
@machine = UM.new
|
|
@@ -242,7 +245,7 @@ class AppAPITest < Minitest::Test
|
|
|
242
245
|
@tmp_fn = File.join(APP_ROOT, 'tmp.rb')
|
|
243
246
|
|
|
244
247
|
@app = Syntropy::App.new(
|
|
245
|
-
|
|
248
|
+
app_root: APP_ROOT,
|
|
246
249
|
mount_path: '/test',
|
|
247
250
|
watch_files: 0.05,
|
|
248
251
|
machine: @machine
|
|
@@ -297,7 +300,7 @@ end
|
|
|
297
300
|
class AppDependenciesTest < Minitest::Test
|
|
298
301
|
HTTP = Syntropy::HTTP
|
|
299
302
|
|
|
300
|
-
APP_ROOT = File.join(__dir__, 'app')
|
|
303
|
+
APP_ROOT = File.join(__dir__, 'fixtures/app')
|
|
301
304
|
|
|
302
305
|
def test_app_dependencies
|
|
303
306
|
foo = { foo: 'foo' }
|
|
@@ -309,7 +312,7 @@ class AppDependenciesTest < Minitest::Test
|
|
|
309
312
|
@tmp_fn = File.join(APP_ROOT, 'tmp.rb')
|
|
310
313
|
|
|
311
314
|
@app = Syntropy::App.new(
|
|
312
|
-
|
|
315
|
+
app_root: APP_ROOT,
|
|
313
316
|
mount_path: '/test',
|
|
314
317
|
machine: @machine,
|
|
315
318
|
foo: foo,
|
|
@@ -322,45 +325,3 @@ class AppDependenciesTest < Minitest::Test
|
|
|
322
325
|
assert_equal HTTP::OK, req.response_status
|
|
323
326
|
end
|
|
324
327
|
end
|
|
325
|
-
|
|
326
|
-
class AppDBSetupDBTest < Minitest::Test
|
|
327
|
-
HTTP = Syntropy::HTTP
|
|
328
|
-
|
|
329
|
-
APP_ROOT = File.join(__dir__, 'app_with_schema')
|
|
330
|
-
|
|
331
|
-
def test_app_setup_db
|
|
332
|
-
machine = UM.new
|
|
333
|
-
|
|
334
|
-
app = Syntropy::App.new(
|
|
335
|
-
root_dir: APP_ROOT,
|
|
336
|
-
mount_path: '/test',
|
|
337
|
-
machine: machine
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
assert_equal false, app.respond_to?(:connection_pool)
|
|
341
|
-
assert_equal false, app.respond_to?(:schema)
|
|
342
|
-
|
|
343
|
-
fn = "/tmp/#{rand(100000)}.db"
|
|
344
|
-
|
|
345
|
-
app.setup_db(
|
|
346
|
-
db_path: fn,
|
|
347
|
-
schema_root: '_schema'
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
-
assert_equal true, app.respond_to?(:connection_pool)
|
|
351
|
-
assert_equal fn, app.connection_pool.with_db { it.filename }
|
|
352
|
-
|
|
353
|
-
assert_equal true, app.respond_to?(:schema)
|
|
354
|
-
app.schema.apply(app.connection_pool)
|
|
355
|
-
assert_equal '2026-05-30-bar', app.schema.current_version(app.connection_pool)
|
|
356
|
-
|
|
357
|
-
assert_equal [
|
|
358
|
-
{
|
|
359
|
-
id: 1,
|
|
360
|
-
title: 'foo',
|
|
361
|
-
body: 'baz'
|
|
362
|
-
}
|
|
363
|
-
], app.connection_pool.query('select id, title, body from posts')
|
|
364
|
-
|
|
365
|
-
end
|
|
366
|
-
end
|
data/test/test_caching.rb
CHANGED
|
@@ -6,7 +6,7 @@ require 'digest/sha1'
|
|
|
6
6
|
class CachingTest < Minitest::Test
|
|
7
7
|
HTTP = Syntropy::HTTP
|
|
8
8
|
|
|
9
|
-
APP_ROOT = File.join(__dir__, 'app')
|
|
9
|
+
APP_ROOT = File.join(__dir__, 'fixtures/app')
|
|
10
10
|
|
|
11
11
|
def make_socket_pair
|
|
12
12
|
port = SecureRandom.random_number(10000..40000)
|
|
@@ -32,7 +32,7 @@ class CachingTest < Minitest::Test
|
|
|
32
32
|
|
|
33
33
|
@env = {
|
|
34
34
|
machine: @machine,
|
|
35
|
-
|
|
35
|
+
app_root: APP_ROOT,
|
|
36
36
|
mount_path: '/test',
|
|
37
37
|
watch_files: 0.05
|
|
38
38
|
}
|
data/test/test_db_schema.rb
CHANGED
|
@@ -77,7 +77,7 @@ class DBSchemaTest < Minitest::Test
|
|
|
77
77
|
|
|
78
78
|
def test_schema_from_module_files
|
|
79
79
|
module_loader = Syntropy::ModuleLoader.new({
|
|
80
|
-
|
|
80
|
+
app_root: File.join(__dir__, 'fixtures/schema')
|
|
81
81
|
})
|
|
82
82
|
schema = Syntropy::DB::Schema.new(module_loader:, schema_root: '/')
|
|
83
83
|
|
|
@@ -454,7 +454,7 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
454
454
|
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
|
455
455
|
@machine.spin do
|
|
456
456
|
@connection.serve_request
|
|
457
|
-
rescue => e
|
|
457
|
+
rescue StandardError => e
|
|
458
458
|
p e
|
|
459
459
|
p e.backtrace
|
|
460
460
|
end
|
|
@@ -479,7 +479,7 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
479
479
|
|
|
480
480
|
@hook = ->(req) do
|
|
481
481
|
req.respond_with_static_file(fn, nil, nil, nil)
|
|
482
|
-
rescue => e
|
|
482
|
+
rescue StandardError => e
|
|
483
483
|
p e
|
|
484
484
|
p e.backtrace
|
|
485
485
|
end
|
|
@@ -490,7 +490,7 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
490
490
|
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
|
491
491
|
@machine.spin do
|
|
492
492
|
@connection.serve_request
|
|
493
|
-
rescue => e
|
|
493
|
+
rescue StandardError => e
|
|
494
494
|
p e
|
|
495
495
|
p e.backtrace
|
|
496
496
|
end
|
|
@@ -596,7 +596,7 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
596
596
|
|
|
597
597
|
def test_set_cookie_single
|
|
598
598
|
@hook = ->(req) {
|
|
599
|
-
req.set_cookie('foo
|
|
599
|
+
req.set_cookie('foo', 'bar; HttpOnly')
|
|
600
600
|
req.respond('foo')
|
|
601
601
|
}
|
|
602
602
|
|
|
@@ -610,7 +610,8 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
610
610
|
|
|
611
611
|
def test_set_cookie_multi1
|
|
612
612
|
@hook = ->(req) {
|
|
613
|
-
req.set_cookie('foo
|
|
613
|
+
req.set_cookie('foo', 'bar; HttpOnly')
|
|
614
|
+
req.set_cookie('bar', 'baz')
|
|
614
615
|
req.respond('foo')
|
|
615
616
|
}
|
|
616
617
|
|
|
@@ -624,9 +625,11 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
624
625
|
|
|
625
626
|
def test_set_cookie_multi2
|
|
626
627
|
@hook = ->(req) {
|
|
627
|
-
req.set_cookie('a
|
|
628
|
-
req.set_cookie('
|
|
629
|
-
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')
|
|
630
633
|
req.respond('foo')
|
|
631
634
|
}
|
|
632
635
|
|
data/test/test_module_loader.rb
CHANGED
|
@@ -5,8 +5,8 @@ require_relative 'helper'
|
|
|
5
5
|
class ModuleTest < Minitest::Test
|
|
6
6
|
def setup
|
|
7
7
|
@machine = UM.new
|
|
8
|
-
@root = File.join(__dir__, 'app')
|
|
9
|
-
@env = {
|
|
8
|
+
@root = File.join(__dir__, 'fixtures/app')
|
|
9
|
+
@env = { app_root: @root, baz: 42, machine: @machine, app: 42 }
|
|
10
10
|
@loader = Syntropy::ModuleLoader.new(@env)
|
|
11
11
|
end
|
|
12
12
|
|
|
@@ -109,5 +109,8 @@ class ModuleTest < Minitest::Test
|
|
|
109
109
|
|
|
110
110
|
list = @loader.list('mod/bar')
|
|
111
111
|
assert_equal ['mod/bar/index+'], list
|
|
112
|
+
|
|
113
|
+
list = @loader.list('non-existent')
|
|
114
|
+
assert_equal [], list
|
|
112
115
|
end
|
|
113
116
|
end
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'helper'
|
|
4
|
+
|
|
5
|
+
class RequestSessionTest < Minitest::Test
|
|
6
|
+
def make_socket_pair
|
|
7
|
+
port = SecureRandom.random_number(10000..40000)
|
|
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
|
+
def setup
|
|
23
|
+
@machine = UM.new
|
|
24
|
+
@c_fd, @s_fd = make_socket_pair
|
|
25
|
+
# s = @machine.io(@s_fd, :socket)
|
|
26
|
+
|
|
27
|
+
@app = ->(req) { req.respond(nil, ':status' => Syntropy::HTTP::INTERNAL_SERVER_ERROR) }
|
|
28
|
+
@env = {}
|
|
29
|
+
@connection = Syntropy::HTTP::ServerConnection.new(@machine, @s_fd, @env) { |req| @app.(req) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def teardown
|
|
33
|
+
@machine.close(@c_fd) rescue nil
|
|
34
|
+
@machine.close(@s_fd) rescue nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def write_http_request(msg, shutdown_wr = true)
|
|
38
|
+
@machine.send(@c_fd, msg, msg.bytesize, UM::MSG_WAITALL)
|
|
39
|
+
@machine.shutdown(@c_fd, UM::SHUT_WR) if shutdown_wr
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def write_client_side(msg)
|
|
43
|
+
@machine.send(@c_fd, msg, msg.bytesize, UM::MSG_WAITALL)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def read_client_side(len = 65536)
|
|
47
|
+
buf = +''
|
|
48
|
+
res = @machine.recv(@c_fd, buf, len, 0)
|
|
49
|
+
res == 0 ? nil : buf
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_session_kv_access
|
|
53
|
+
current = :something
|
|
54
|
+
|
|
55
|
+
@app = ->(req) {
|
|
56
|
+
req.session['foo'] = 'bar'
|
|
57
|
+
current = req.session['foo']
|
|
58
|
+
req.respond(nil)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
write_http_request "GET / HTTP/1.1\r\n\r\n"
|
|
62
|
+
@connection.serve_request
|
|
63
|
+
|
|
64
|
+
assert_equal 'bar', current
|
|
65
|
+
|
|
66
|
+
response = read_client_side
|
|
67
|
+
data = Base64.strict_encode64(JSON.dump({ 'foo' => 'bar' }))
|
|
68
|
+
assert_equal "HTTP/1.1 204\r\nSet-Cookie: __syntropy_session__=#{data}; Path=/; HttpOnly\r\n\r\n", response
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_session_kv_multi
|
|
72
|
+
@app = ->(req) {
|
|
73
|
+
req.session['foo'] = 'bar'
|
|
74
|
+
req.session['bar'] = 'baz'
|
|
75
|
+
req.respond(nil)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
write_http_request "GET / HTTP/1.1\r\n\r\n"
|
|
79
|
+
@connection.serve_request
|
|
80
|
+
|
|
81
|
+
response = read_client_side
|
|
82
|
+
data = Base64.strict_encode64(JSON.dump({ 'foo' => 'bar', 'bar' => 'baz' }))
|
|
83
|
+
assert_equal "HTTP/1.1 204\r\nSet-Cookie: __syntropy_session__=#{data}; Path=/; HttpOnly\r\n\r\n", response
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_session_kv_sequence
|
|
87
|
+
counter = 0
|
|
88
|
+
|
|
89
|
+
@app = ->(req) {
|
|
90
|
+
counter += 1
|
|
91
|
+
case counter
|
|
92
|
+
when 1
|
|
93
|
+
req.session['foo'] = 'bar'
|
|
94
|
+
when 2
|
|
95
|
+
req.session['foo'] = req.session['foo'] + 'baz'
|
|
96
|
+
end
|
|
97
|
+
req.respond(nil)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
write_http_request "GET / HTTP/1.1\r\n\r\n", false
|
|
101
|
+
@connection.serve_request
|
|
102
|
+
response = read_client_side
|
|
103
|
+
data = Base64.strict_encode64(JSON.dump({ 'foo' => 'bar' }))
|
|
104
|
+
assert_equal "HTTP/1.1 204\r\nSet-Cookie: __syntropy_session__=#{data}; Path=/; HttpOnly\r\n\r\n", response
|
|
105
|
+
|
|
106
|
+
write_http_request "GET / HTTP/1.1\r\nCookie: __syntropy_session__=#{data}\r\n\r\n"
|
|
107
|
+
@connection.serve_request
|
|
108
|
+
response = read_client_side
|
|
109
|
+
data = Base64.strict_encode64(JSON.dump({ 'foo' => 'barbaz' }))
|
|
110
|
+
assert_equal "HTTP/1.1 204\r\nSet-Cookie: __syntropy_session__=#{data}; Path=/; HttpOnly\r\n\r\n", response
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def test_session_kv_delete
|
|
114
|
+
counter = 0
|
|
115
|
+
|
|
116
|
+
@app = ->(req) {
|
|
117
|
+
counter += 1
|
|
118
|
+
case counter
|
|
119
|
+
when 1
|
|
120
|
+
req.session['foo'] = 'bar'
|
|
121
|
+
when 2
|
|
122
|
+
req.session.delete('foo')
|
|
123
|
+
end
|
|
124
|
+
req.respond(nil)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
write_http_request "GET / HTTP/1.1\r\n\r\n", false
|
|
128
|
+
@connection.serve_request
|
|
129
|
+
response = read_client_side
|
|
130
|
+
data = Base64.strict_encode64(JSON.dump({ 'foo' => 'bar' }))
|
|
131
|
+
assert_equal "HTTP/1.1 204\r\nSet-Cookie: __syntropy_session__=#{data}; Path=/; HttpOnly\r\n\r\n", response
|
|
132
|
+
|
|
133
|
+
write_http_request "GET / HTTP/1.1\r\nCookie: __syntropy_session__=#{data}\r\n\r\n"
|
|
134
|
+
@connection.serve_request
|
|
135
|
+
response = read_client_side
|
|
136
|
+
assert_equal "HTTP/1.1 204\r\nSet-Cookie: __syntropy_session__=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/; Max-Age=0; HttpOnly\r\n\r\n", response
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def test_session_discard
|
|
140
|
+
counter = 0
|
|
141
|
+
|
|
142
|
+
@app = ->(req) {
|
|
143
|
+
counter += 1
|
|
144
|
+
case counter
|
|
145
|
+
when 1
|
|
146
|
+
req.session['foo'] = 'bar'
|
|
147
|
+
when 2
|
|
148
|
+
req.session.discard
|
|
149
|
+
end
|
|
150
|
+
req.respond(nil)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
write_http_request "GET / HTTP/1.1\r\n\r\n", false
|
|
154
|
+
@connection.serve_request
|
|
155
|
+
response = read_client_side
|
|
156
|
+
data = Base64.strict_encode64(JSON.dump({ 'foo' => 'bar' }))
|
|
157
|
+
assert_equal "HTTP/1.1 204\r\nSet-Cookie: __syntropy_session__=#{data}; Path=/; HttpOnly\r\n\r\n", response
|
|
158
|
+
|
|
159
|
+
write_http_request "GET / HTTP/1.1\r\nCookie: __syntropy_session__=#{data}\r\n\r\n"
|
|
160
|
+
@connection.serve_request
|
|
161
|
+
response = read_client_side
|
|
162
|
+
assert_equal "HTTP/1.1 204\r\nSet-Cookie: __syntropy_session__=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/; Max-Age=0; HttpOnly\r\n\r\n", response
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def test_flash_simple
|
|
166
|
+
counter = 0
|
|
167
|
+
flash_notices = []
|
|
168
|
+
|
|
169
|
+
@app = ->(req) do
|
|
170
|
+
counter += 1
|
|
171
|
+
case counter
|
|
172
|
+
when 1
|
|
173
|
+
req.session.flash[:notice] = "Hello flash!"
|
|
174
|
+
flash_notices << req.session.flash[:notice]
|
|
175
|
+
when 2
|
|
176
|
+
flash_notices << req.session.flash[:notice]
|
|
177
|
+
when 3
|
|
178
|
+
flash_notices << req.session.flash[:notice]
|
|
179
|
+
end
|
|
180
|
+
req.respond(nil)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
parse_cookie = ->(response) {
|
|
184
|
+
m = response.match(/Set-Cookie: __syntropy_session__=([^\s;]*)/)
|
|
185
|
+
m && m[1]
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
cookie = nil
|
|
189
|
+
|
|
190
|
+
3.times {
|
|
191
|
+
request = cookie ? "GET / HTTP/1.1\r\nCookie: __syntropy_session__=#{cookie}\r\n\r\n" : "GET / HTTP/1.1\r\n\r\n"
|
|
192
|
+
write_http_request request, false
|
|
193
|
+
@connection.serve_request
|
|
194
|
+
response = read_client_side
|
|
195
|
+
v = parse_cookie.(response)
|
|
196
|
+
if v
|
|
197
|
+
cookie = v.empty? ? nil : v
|
|
198
|
+
end
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
assert_equal [nil, 'Hello flash!', nil], flash_notices
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def test_flash_each
|
|
205
|
+
counter = 0
|
|
206
|
+
flash_content = []
|
|
207
|
+
|
|
208
|
+
@app = ->(req) do
|
|
209
|
+
counter += 1
|
|
210
|
+
case counter
|
|
211
|
+
when 1
|
|
212
|
+
req.session.flash[:notice] = "Hello flash!"
|
|
213
|
+
a = []
|
|
214
|
+
req.session.flash.each { |k, v| a << [k, v] }
|
|
215
|
+
flash_content << a
|
|
216
|
+
when 2
|
|
217
|
+
a = []
|
|
218
|
+
req.session.flash.each { |k, v| a << [k, v] }
|
|
219
|
+
flash_content << a
|
|
220
|
+
when 3
|
|
221
|
+
a = []
|
|
222
|
+
req.session.flash.each { |k, v| a << [k, v] }
|
|
223
|
+
flash_content << a
|
|
224
|
+
end
|
|
225
|
+
req.respond(nil)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
parse_cookie = ->(response) {
|
|
229
|
+
m = response.match(/Set-Cookie: __syntropy_session__=([^\s;]*)/)
|
|
230
|
+
m && m[1]
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
set_cookies = []
|
|
234
|
+
cookie = nil
|
|
235
|
+
|
|
236
|
+
3.times {
|
|
237
|
+
request = cookie ? "GET / HTTP/1.1\r\nCookie: __syntropy_session__=#{cookie}\r\n\r\n" : "GET / HTTP/1.1\r\n\r\n"
|
|
238
|
+
write_http_request request, false
|
|
239
|
+
@connection.serve_request
|
|
240
|
+
response = read_client_side
|
|
241
|
+
v = parse_cookie.(response)
|
|
242
|
+
if v
|
|
243
|
+
cookie = v.empty? ? nil : v
|
|
244
|
+
end
|
|
245
|
+
set_cookies << v ? cookie : nil
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
assert_equal [
|
|
249
|
+
[],
|
|
250
|
+
[[:notice, 'Hello flash!']],
|
|
251
|
+
[]
|
|
252
|
+
], flash_content
|
|
253
|
+
end
|
|
254
|
+
end
|
data/test/test_response.rb
CHANGED
|
@@ -22,25 +22,6 @@ class RedirectTest < Minitest::Test
|
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
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
25
|
class UpgradeTest < Minitest::Test
|
|
45
26
|
def test_upgrade
|
|
46
27
|
r = Syntropy::MockAdapter.mock
|