syntropy 0.33.0 → 0.34.1

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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/cmd/console.rb +18 -7
  4. data/cmd/serve.rb +26 -18
  5. data/cmd/test.rb +37 -24
  6. data/examples/blog/.gitignore +1 -0
  7. data/examples/blog/app/_layout/default.rb +3 -0
  8. data/examples/blog/app/_lib/database.rb +13 -0
  9. data/examples/blog/app/_lib/{post_store.rb → posts.rb} +3 -1
  10. data/examples/blog/app/assets/style.css +20 -0
  11. data/examples/blog/app/index.rb +12 -2
  12. data/examples/blog/app/posts/[id]/edit.rb +2 -2
  13. data/examples/blog/app/posts/[id]/index.rb +4 -4
  14. data/examples/blog/app/posts/index.rb +4 -4
  15. data/examples/blog/app/posts/new.rb +1 -1
  16. data/examples/blog/app/test.rb +7 -0
  17. data/examples/blog/config/development.rb +5 -0
  18. data/examples/blog/config/production.rb +4 -0
  19. data/examples/blog/config/test.rb +5 -0
  20. data/examples/blog/test/test_posts.rb +65 -0
  21. data/examples/mcp-oauth/app/oauth/token.rb +1 -1
  22. data/examples/template/.gitignore +2 -0
  23. data/examples/template/Gemfile +3 -0
  24. data/examples/template/app/_layout/default.rb +14 -0
  25. data/examples/template/app/_lib/database.rb +13 -0
  26. data/examples/template/app/_schema/2026-01-01-initial.rb +9 -0
  27. data/examples/template/app/assets/style.css +25 -0
  28. data/examples/template/app/index.rb +27 -0
  29. data/examples/template/app/test.rb +7 -0
  30. data/examples/template/config/development.rb +5 -0
  31. data/examples/template/config/production.rb +4 -0
  32. data/examples/template/config/test.rb +5 -0
  33. data/examples/template/test/test_app.rb +14 -0
  34. data/lib/syntropy/app.rb +48 -40
  35. data/lib/syntropy/applets/builtin/auto_refresh/watch.sse.rb +1 -1
  36. data/lib/syntropy/applets/builtin/default_error_handler/style.css +4 -8
  37. data/lib/syntropy/applets/builtin/default_error_handler.rb +18 -9
  38. data/lib/syntropy/db/schema.rb +1 -1
  39. data/lib/syntropy/db/store.rb +2 -0
  40. data/lib/syntropy/errors.rb +6 -2
  41. data/lib/syntropy/http/client.rb +1 -0
  42. data/lib/syntropy/http/server_connection.rb +0 -4
  43. data/lib/syntropy/json_api.rb +27 -1
  44. data/lib/syntropy/logger.rb +81 -27
  45. data/lib/syntropy/markdown.rb +61 -32
  46. data/lib/syntropy/mime_types.rb +9 -5
  47. data/lib/syntropy/module_loader.rb +31 -9
  48. data/lib/syntropy/papercraft_extensions.rb +2 -2
  49. data/lib/syntropy/request/mock_adapter.rb +10 -8
  50. data/lib/syntropy/request/request_info.rb +91 -0
  51. data/lib/syntropy/request/response.rb +1 -12
  52. data/lib/syntropy/request/validation.rb +1 -0
  53. data/lib/syntropy/request.rb +51 -19
  54. data/lib/syntropy/routing_tree.rb +27 -28
  55. data/lib/syntropy/session.rb +198 -0
  56. data/lib/syntropy/side_run.rb +25 -2
  57. data/lib/syntropy/test.rb +105 -10
  58. data/lib/syntropy/utils.rb +53 -18
  59. data/lib/syntropy/version.rb +1 -1
  60. data/lib/syntropy.rb +44 -10
  61. data/test/bm_router_proc.rb +4 -4
  62. data/test/fixtures/app/class_instance.rb +5 -0
  63. data/test/fixtures/app/http.rb +5 -0
  64. data/test/fixtures/app/post_ct.rb +5 -0
  65. data/test/fixtures/app/singleton.rb +3 -0
  66. data/test/test_app.rb +13 -52
  67. data/test/test_caching.rb +2 -2
  68. data/test/test_db_schema.rb +1 -1
  69. data/test/test_http_server_connection.rb +3 -3
  70. data/test/test_module_loader.rb +5 -2
  71. data/test/test_response.rb +0 -19
  72. data/test/test_routing_tree.rb +69 -69
  73. data/test/test_server.rb +5 -9
  74. data/test/test_test.rb +70 -0
  75. metadata +66 -42
  76. data/examples/blog/app/_setup.rb +0 -4
  77. data/lib/syntropy/request/session.rb +0 -113
  78. /data/test/{app → fixtures/app}/.well-known/foo.rb +0 -0
  79. /data/test/{app → fixtures/app}/_hook.rb +0 -0
  80. /data/test/{app → fixtures/app}/_layout/default.rb +0 -0
  81. /data/test/{app → fixtures/app}/_lib/callable.rb +0 -0
  82. /data/test/{app → fixtures/app}/_lib/dep.rb +0 -0
  83. /data/test/{app → fixtures/app}/_lib/env.rb +0 -0
  84. /data/test/{app → fixtures/app}/_lib/klass.rb +0 -0
  85. /data/test/{app → fixtures/app}/_lib/missing-export.rb +0 -0
  86. /data/test/{app → fixtures/app}/_lib/self.rb +0 -0
  87. /data/test/{app → fixtures/app}/about/_error.rb +0 -0
  88. /data/test/{app → fixtures/app}/about/foo.md +0 -0
  89. /data/test/{app → fixtures/app}/about/index.rb +0 -0
  90. /data/test/{app → fixtures/app}/about/raise.rb +0 -0
  91. /data/test/{app → fixtures/app}/api+.rb +0 -0
  92. /data/test/{app → fixtures/app}/assets/style.css +0 -0
  93. /data/test/{app → fixtures/app}/bad_mod.rb +0 -0
  94. /data/test/{app → fixtures/app}/bar.rb +0 -0
  95. /data/test/{app → fixtures/app}/baz.rb +0 -0
  96. /data/test/{app → fixtures/app}/by_method.rb +0 -0
  97. /data/test/{app → fixtures/app}/deps.rb +0 -0
  98. /data/test/{app → fixtures/app}/index.html +0 -0
  99. /data/test/{app → fixtures/app}/mod/bar/index+.rb +0 -0
  100. /data/test/{app → fixtures/app}/mod/foo/index.rb +0 -0
  101. /data/test/{app → fixtures/app}/mod/path/a.rb +0 -0
  102. /data/test/{app → fixtures/app}/mod/path/b.rb +0 -0
  103. /data/test/{app → fixtures/app}/params/[foo].rb +0 -0
  104. /data/test/{app → fixtures/app}/rss.rb +0 -0
  105. /data/test/{app → fixtures/app}/tmp.rb +0 -0
  106. /data/test/{app_custom → fixtures/app_custom}/_site.rb +0 -0
  107. /data/test/{app_multi_site → fixtures/app_multi_site}/_site.rb +0 -0
  108. /data/test/{app_multi_site → fixtures/app_multi_site}/bar.baz/index.html +0 -0
  109. /data/test/{app_multi_site → fixtures/app_multi_site}/foo.bar/index.html +0 -0
  110. /data/test/{app_setup → fixtures/app_setup}/_setup.rb +0 -0
  111. /data/test/{app_setup → fixtures/app_setup}/index.rb +0 -0
  112. /data/test/{app_with_schema → fixtures/app_with_schema}/_schema/2026-01-02-foo.rb +0 -0
  113. /data/test/{app_with_schema → fixtures/app_with_schema}/_schema/2026-05-30-bar.rb +0 -0
  114. /data/test/{schema → fixtures/schema}/2026-01-02-foo.rb +0 -0
  115. /data/test/{schema → fixtures/schema}/2026-05-30-bar.rb +0 -0
data/lib/syntropy/test.rb CHANGED
@@ -7,27 +7,49 @@ require 'json'
7
7
  require 'uri'
8
8
 
9
9
  module Syntropy
10
+ # Test provides a class for testing a Syntropy app, based on Minitest.
10
11
  class Test < Minitest::Test
11
12
  HTTP = Syntropy::HTTP
12
13
 
14
+ # Sets the app environment for all Syntropy tests.
15
+ #
16
+ # @param env [Hash] app environment hash
17
+ # @return [void]
13
18
  def self.env=(env)
14
19
  @@env = env
15
20
  end
16
21
 
17
22
  attr_reader :machine, :app
18
23
 
24
+ # Returns the test environment.
25
+ #
26
+ # @return [Hash] test app environment
19
27
  def env
20
28
  @@env
21
29
  end
22
30
 
23
- def load_module(ref)
24
- app.module_loader.load(ref)
31
+ # Loads and returns a module with the given reference.
32
+ #
33
+ # @param ref [String] module reference
34
+ # @return [any] module
35
+ def load_module(ref, raise_on_missing: true)
36
+ app.module_loader.load(ref, raise_on_missing:)
25
37
  end
26
38
 
39
+ # Makes an HTTP request to the test app.
40
+ #
41
+ # @param headers [Hash] request headers
42
+ # @param body [String, nil] request body
43
+ # @return [Syntropy::Request]
27
44
  def http_request(headers, body = nil)
28
45
  @test_harness.request(headers, body)
29
46
  end
30
47
 
48
+ # Makes an HTTP GET request to the test app.
49
+ #
50
+ # @param path [String] request path
51
+ # @param headers [Hash] request headers
52
+ # @return [Syntropy::Request]
31
53
  def get(path, **headers)
32
54
  http_request(
33
55
  headers.merge(
@@ -37,6 +59,13 @@ module Syntropy
37
59
  )
38
60
  end
39
61
 
62
+ # Makes an HTTP POST request to the test app.
63
+ #
64
+ # @param path [String] request path
65
+ # @param content_type [String, nil] content MIME type
66
+ # @param body [String] request body
67
+ # @param headers [Hash] request headers
68
+ # @return [Syntropy::Request]
40
69
  def post(path, content_type, body, **headers)
41
70
  headers = headers.merge('content-type' => content_type) if content_type
42
71
  http_request(
@@ -50,26 +79,52 @@ module Syntropy
50
79
  )
51
80
  end
52
81
 
53
- def post_json(path, obj, **)
54
- post(path, 'application/json', JSON.dump(obj), **)
82
+ # Makes an HTTP POST request to the test app with a "application/json"
83
+ # content type. The given object is converted to JSON and sent as the
84
+ # request body.
85
+ #
86
+ # @param path [String] request path
87
+ # @param data [any] data
88
+ # @return [Syntropy::Request]
89
+ def post_json(path, data, **)
90
+ post(path, 'application/json', JSON.dump(data), **)
55
91
  end
56
92
 
57
- def post_form(path, form, **)
58
- post(path, 'application/x-www-form-urlencoded', URI.encode_www_form(form), **)
93
+ # Makes an HTTP POST request to the test app with a
94
+ # "application/x-www-form-urlencoded" content type. The given data is
95
+ # converted to URL Encoded form format and sent as the request body.
96
+ #
97
+ # @param path [String] request path
98
+ # @param data [Hash] form data
99
+ # @return [Syntropy::Request]
100
+ def post_form(path, data, **)
101
+ post(path, 'application/x-www-form-urlencoded', URI.encode_www_form(data), **)
59
102
  end
60
103
 
104
+ # Sets up a test instance.
105
+ #
106
+ # @return [void]
61
107
  def setup
62
108
  raise 'Environment not set' if !@@env
63
109
 
110
+ Syntropy.load_config(@@env)
111
+
64
112
  @machine = UM.new
65
113
  @app = Syntropy::App.new(
66
- root_dir: @@env[:root_dir],
67
- mount_path: '/',
68
- machine: @machine
114
+ **@@env.merge(
115
+ machine: @machine,
116
+ test_mode: true
117
+ )
69
118
  )
70
119
  @test_harness = Syntropy::TestHarness.new(@app)
120
+
121
+ @db = load_module('/_lib/database', raise_on_missing: false)
122
+ @db&.migrate!
71
123
  end
72
124
 
125
+ # Cleans up a test instance.
126
+ #
127
+ # @return [void]
73
128
  def teardown
74
129
  @machine = nil
75
130
  @app = nil
@@ -77,18 +132,31 @@ module Syntropy
77
132
  end
78
133
  end
79
134
 
135
+ # TestHarness provides glue code for performing HTTP requests against a
136
+ # Syntropy app.
80
137
  class TestHarness
138
+ # Initializes the test harness with the given app.
139
+ #
140
+ # @param app [Syntropy::App]
141
+ # @return [void]
81
142
  def initialize(app)
82
143
  @app = app
83
144
  @app.raise_internal_server_error = true if @app.respond_to?(:raise_internal_server_error=)
84
145
  end
85
146
 
147
+ # Perfrms a request against the associated app.
148
+ #
149
+ # @param headers [Hash] request headers
150
+ # @param body [String, nil] request body
151
+ # @return [Syntropy::Request]
86
152
  def request(headers, body = nil)
87
153
  req = mock_req(headers, body)
88
154
  @app.call(req)
89
155
  req
90
156
  end
91
157
 
158
+ # Temporarily disables raising an exception in case of an internal server
159
+ # error while running the given block.
92
160
  def no_raise_internal_server_error
93
161
  return yield if !@app.respond_to?(:raise_internal_server_error=)
94
162
 
@@ -102,29 +170,50 @@ module Syntropy
102
170
 
103
171
  private
104
172
 
173
+ # Creates a Syntropy request running on a mock adapter.
174
+ #
175
+ # @param headers [Hash] request headers
176
+ # @param body [String, nil] request body
177
+ # @return [Syntropy::Request]
105
178
  def mock_req(headers, body = nil)
106
179
  Syntropy::MockAdapter.mock(headers, body)
107
180
  end
108
181
  end
109
182
 
110
- class Request
183
+ # Extensions to Syntropy::Request for testing.
184
+ module TestRequestExtensions
185
+ # Returns the response headers.
186
+ #
187
+ # @return [Hash]
111
188
  def response_headers
112
189
  adapter.response_headers
113
190
  end
114
191
 
192
+ # Returns the response status
193
+ #
194
+ # @return [Integer]
115
195
  def response_status
116
196
  adapter.status
117
197
  end
118
198
 
199
+ # Returns the response body
200
+ #
201
+ # @return [String, nil]
119
202
  def response_body
120
203
  adapter.response_body
121
204
  end
122
205
 
206
+ # Parses the response body from JSON.
207
+ #
208
+ # @return [any] parsed JSON object
123
209
  def response_json
124
210
  raise if response_content_type != 'application/json'
125
211
  JSON.parse(response_body)
126
212
  end
127
213
 
214
+ # Returns the response content MIME type.
215
+ #
216
+ # @return [String, nil]
128
217
  def response_content_type
129
218
  ct = response_headers['Content-Type']
130
219
  return nil if !ct
@@ -135,6 +224,10 @@ module Syntropy
135
224
  m[1]
136
225
  end
137
226
 
227
+ # Returns the cookie value for the given cookie name from the response.
228
+ #
229
+ # @param name [String, Symbol] cookie name
230
+ # @return [String, nil] cookie value
138
231
  def response_cookie(name)
139
232
  sc = response_headers['Set-Cookie']
140
233
  return nil if !sc
@@ -145,4 +238,6 @@ module Syntropy
145
238
  m[1]
146
239
  end
147
240
  end
241
+
242
+ Request.include TestRequestExtensions
148
243
  end
@@ -1,52 +1,87 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'securerandom'
4
+
3
5
  module Syntropy
4
6
  # Utilities for use in modules
5
7
  module Utilities
6
- # Returns a request handler that routes request according to
8
+ def tmp_path(prefix = 'syntropy')
9
+ "/tmp/#{prefix}-#{SecureRandom.hex(16)}"
10
+ end
11
+
12
+ # Returns a request handler that routes request according to the host
13
+ # header. Looks for site directories (named by host name) in the app's root
14
+ # directory. A map may be given in order to provide additional hostnames to
15
+ # site directories.
16
+ #
17
+ # @param env [Hash] app environment hash
18
+ # @param map [Hash, nil] additional hostname map
19
+ # @return [Proc] router proc
7
20
  def route_by_host(env, map = nil)
8
- root = env[:root_dir]
9
- sites = Dir[File.join(root, '*')]
10
- .reject { File.basename(it) =~ /^_/ }
11
- .select { File.directory?(it) }
12
- .each_with_object({}) { |fn, h|
13
- name = File.basename(fn)
14
- h[name] = Syntropy::App.new(**env.merge(root_dir: fn))
15
- }
16
-
17
- # copy over map refs
21
+ sites = find_hostname_sites(env)
22
+
23
+ # add map refs
18
24
  map&.each { |k, v| sites[k] = sites[v] }
19
25
 
20
- #
21
26
  lambda { |req|
22
27
  site = sites[req.host]
23
28
  site ? site.call(req) : req.respond(nil, ':status' => HTTP::BAD_REQUEST)
24
29
  }
25
30
  end
26
31
 
32
+ # Returns a list of parsed markdown pages at the given path.
33
+ #
34
+ # @param env [Hash] app environment hash
35
+ # @param ref [String] directory path
36
+ # @return [Array<Hash>] array of page entries
27
37
  def page_list(env, ref)
28
- full_path = File.join(env[:root_dir], ref)
38
+ full_path = File.join(env[:app_root], ref)
29
39
  raise 'Not a directory' if !File.directory?(full_path)
30
40
 
31
41
  Dir[File.join(full_path, '*.md')].sort.map {
32
- atts, markdown = Syntropy.parse_markdown_file(it, env)
42
+ atts, markdown = Syntropy::Markdown.parse(it, env)
33
43
  { atts:, markdown: }
34
44
  }
35
45
  end
36
46
 
37
- def app(**env)
38
- Syntropy::App.new(**env)
47
+ # Instantiates a Syntropy app for the given environment hash.
48
+ #
49
+ # @return [Syntropy::App]
50
+ def app(**)
51
+ Syntropy::App.new(**)
39
52
  end
40
53
 
41
- BUILTIN_APPLET_ROOT_DIR = File.expand_path(File.join(__dir__, 'applets/builtin'))
54
+ BUILTIN_APPLET_app_root = File.expand_path(File.join(__dir__, 'applets/builtin'))
55
+
56
+ # Creates a builtin applet with the given environment hash. By default the
57
+ # builtin applet is mounted at /.syntropy.
58
+ #
59
+ # @param env [Hash] app environment
60
+ # @param mount_path [String] mount path for the builtin applet
61
+ # @return [Syntropy::App] applet
42
62
  def builtin_applet(env, mount_path: '/.syntropy')
43
63
  app(
44
64
  machine: env[:machine],
45
- root_dir: BUILTIN_APPLET_ROOT_DIR,
65
+ app_root: BUILTIN_APPLET_app_root,
46
66
  mount_path: mount_path,
47
67
  builtin_applet_path: nil,
48
68
  watch_files: nil
49
69
  )
50
70
  end
71
+
72
+ private
73
+
74
+ # Finds sites in the root directory for the given environment hash.
75
+ #
76
+ # @param env [Hash] app environment hash
77
+ # @return [Hash] hash mapping hostname to app
78
+ def find_hostname_sites(env)
79
+ Dir[File.join(env[:app_root], '*')]
80
+ .select { File.directory?(it) && File.basename(it) !~ /^_/ }
81
+ .each_with_object({}) { |fn, h|
82
+ name = File.basename(fn)
83
+ h[name] = Syntropy::App.new(**env.merge(app_root: fn))
84
+ }
85
+ end
51
86
  end
52
87
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Syntropy
4
- VERSION = '0.33.0'
4
+ VERSION = '0.34.1'
5
5
  end
data/lib/syntropy.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'uringmachine'
4
4
  require 'papercraft'
5
+ require 'yaml'
5
6
 
6
7
  require 'syntropy/request'
7
8
  require 'syntropy/logger'
@@ -21,20 +22,31 @@ require 'syntropy/side_run'
21
22
  require 'syntropy/utils'
22
23
  require 'syntropy/version'
23
24
 
25
+ # Syntropy is a web framework for building web apps in Ruby. Syntropy uses
26
+ # UringMachine for I/O and concurrency, and provides a comprehensive and
27
+ # flexible solution for writing web apps with minimal boilerplate.
24
28
  module Syntropy
25
29
  extend Utilities
26
30
 
27
31
  class << self
28
- attr_accessor :machine
32
+ attr_accessor :machine, :dev_mode
29
33
 
34
+ # Runs the given block on a separate thread. Use this method for running
35
+ # code that is not fiber-aware (i.e. does not use UringMachine).
36
+ #
37
+ # @return [any] operation return value
30
38
  def side_run(&block)
31
39
  raise 'Syntropy.machine not set' if !@machine
32
40
 
33
41
  SideRun.call(@machine, &block)
34
42
  end
35
- end
36
43
 
37
- class << self
44
+ # Runs a web app with the given environment hash. The given block is either
45
+ # an instance of Syntropy::App, or a Proc/callable that takes a request as
46
+ # argument.
47
+ #
48
+ # @param env [Hash] environment
49
+ # @return [void]
38
50
  def run(env = {}, &app)
39
51
  if @in_run
40
52
  @env = env
@@ -47,7 +59,14 @@ module Syntropy
47
59
  @in_run = true
48
60
  machine = env[:machine] || UM.new
49
61
 
50
- env[:logger]&.info(message: "Running Syntropy #{Syntropy::VERSION}, UringMachine #{UM::VERSION}, Ruby #{RUBY_VERSION}")
62
+ if (logger = env[:logger])
63
+ logger.info(
64
+ message: "Syntropy #{Syntropy::VERSION}, UringMachine #{UM::VERSION}, Ruby #{RUBY_VERSION}"
65
+ )
66
+ logger.info(
67
+ message: "Running in #{env[:mode]} mode"
68
+ )
69
+ end
51
70
 
52
71
  server = HTTP::Server.new(machine, env, &app)
53
72
 
@@ -58,23 +77,38 @@ module Syntropy
58
77
  end
59
78
  end
60
79
 
61
- def env(env = nil, &app)
62
- return @env if !env && !app
80
+ def load_config(env)
81
+ return if !env[:config_root]
82
+
83
+ loader_env = env.merge(
84
+ app_root: env[:config_root],
85
+ logger: nil
86
+ )
87
+ loader = ModuleLoader.new(loader_env)
88
+ config = loader.load(env[:mode])
63
89
 
64
- @env = env || {}
65
- @env[:app] = app if app
90
+ env[:config] = config
66
91
  end
67
92
 
68
93
  private
69
94
 
95
+ # Sets up asynchronous SIGINT handling.
96
+ #
97
+ # @param machine [UringMachine] machine instance
98
+ # @param fiber [Fiber] fiber to terminate on SIGINT
99
+ # @return [void]
70
100
  def setup_signal_handling(machine, fiber)
71
101
  queue = UM::Queue.new
72
102
  trap('SIGINT') { machine.push(queue, :SIGINT) }
73
103
  machine.spin { watch_for_int_signal(machine, queue, fiber) }
74
104
  end
75
105
 
76
- # waits for signal from queue, then terminates given fiber
77
- # to be done
106
+ # Waits for signal from queue, then terminates the given fiber.
107
+ #
108
+ # @param machine [UringMachine] machine instance
109
+ # @param queue [UringMachine::Queue] queue to wait on
110
+ # @param fiber [Fiber] fiber to terminate
111
+ # @return [void]
78
112
  def watch_for_int_signal(machine, queue, fiber)
79
113
  machine.shift(queue)
80
114
  machine.schedule(fiber, UM::Terminate.new)
@@ -138,8 +138,8 @@ def make_tmp_file_tree(dir, spec)
138
138
  dir
139
139
  end
140
140
 
141
- ROOT_DIR = "/tmp/#{__FILE__.gsub('/', '-')}-#{SecureRandom.hex}"
142
- make_tmp_file_tree(ROOT_DIR, {
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
- root_dir: ROOT_DIR,
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
- root_dir: ROOT_DIR,
188
+ app_root: app_root,
189
189
  mount_path: '/',
190
190
  # watch_files: 0.05,
191
191
  machine: machine
@@ -0,0 +1,5 @@
1
+ class Foo
2
+ def bar; :baz; end
3
+ end
4
+
5
+ export Foo.new
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ export ->(req) {
4
+ req.respond('hi', ':status' => HTTP::TEAPOT)
5
+ }
@@ -0,0 +1,5 @@
1
+ export http_methods
2
+
3
+ def post(req)
4
+ req.respond("#{req.content_type}:#{req.read}")
5
+ end
@@ -0,0 +1,3 @@
1
+ class Bar; end
2
+
3
+ export self
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
- root_dir: APP_ROOT,
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
- root_dir: APP_ROOT,
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
- root_dir: APP_ROOT,
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
- root_dir: APP_ROOT,
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
- root_dir: APP_ROOT,
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
- root_dir: APP_ROOT,
35
+ app_root: APP_ROOT,
36
36
  mount_path: '/test',
37
37
  watch_files: 0.05
38
38
  }
@@ -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
- root_dir: File.join(__dir__, 'schema')
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