brut 0.0.8 → 0.0.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9fcc1f1288e34fb2c9a4c5a5e7fbc879ab9a4d56abbbaf1e8e3f3f5456cf38ad
4
- data.tar.gz: c8a659fdb48e8a36f74d34daa60a1e96d0a799828372c28fa5888e2e135ab3ac
3
+ metadata.gz: 2e624fb125bcb14a88b3b7adb148561c404befc86974a77423b6cd66293bba31
4
+ data.tar.gz: e2a20fc23b6aa7d0708ea7adf9852333b10616aa4e633d972031c726b9d43316
5
5
  SHA512:
6
- metadata.gz: f1297609ba764ce576b69d1d493466e005ff1e759464cb4e1025856311508f34dbb7abbb474a097de87a1c65ea4ddeab022c36221a41b0c763e88832512ba1d2
7
- data.tar.gz: d30fde661e26d6f0df839b5609264eb71cb34339e5609c610f590d6c82ebd25890daf22cfaf140349b1dea8d0862e8ce1a4a319b2bad5e35a9ba9abe7f87ddc7
6
+ metadata.gz: 59b7398fbf77fafd04c770e617b8fcb341887db4b14691828ab94775c4d36b44d8475889293a39a8cfc92c7b27ef935c487c60d8f7a7f25f96edbfa57db8b75b
7
+ data.tar.gz: aa3ce63a03296837fb5c425ce430a8a0658d732edf32fbed4ac7d01d74fb39dfde6e0b91e0aeadfb7f26d970ac3f23fcf6aab3216354152884fe9f86f4772412
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- brut (0.0.8)
4
+ brut (0.0.10)
5
5
  concurrent-ruby
6
6
  i18n
7
7
  irb
@@ -0,0 +1,17 @@
1
+ # Designed to flush all OTel spans after each job is processed. You likely only
2
+ # want this to be configured in development so you can see the results of individual
3
+ # job executions. Do not enable in production.
4
+ #
5
+ # When using, you want this to be inserted before OTel's sidekiq middleware:
6
+ #
7
+ # config.server_middleware do |chain|
8
+ # chain.insert_before OpenTelemetry::Instrumentation::Sidekiq::Middlewares::Server::TracerMiddleware,
9
+ # Brut::BackEnd::Sidekiq::Middlewares::Server::FlushSpans
10
+ # end
11
+ class Brut::BackEnd::Sidekiq::Middlewares::Server::FlushSpans
12
+ def call(worker, job, queue)
13
+ yield
14
+ ensure
15
+ OpenTelemetry.tracer_provider.force_flush
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ class Brut::BackEnd::Sidekiq::Middlewares::Server
2
+ autoload(:FlushSpans, "brut/back_end/sidekiq/middlewares/server/flush_spans")
3
+ end
@@ -0,0 +1,3 @@
1
+ class Brut::BackEnd::Sidekiq::Middlewares
2
+ autoload(:Server, "brut/back_end/sidekiq/middlewares/server")
3
+ end
@@ -0,0 +1,3 @@
1
+ class Brut::BackEnd::Sidekiq
2
+ autoload(:Middlewares, "brut/back_end/sidekiq/middlewares")
3
+ end
data/lib/brut/cli/app.rb CHANGED
@@ -208,7 +208,14 @@ class Brut::CLI::App
208
208
  return bootstrap_result
209
209
  end
210
210
  after_bootstrap
211
- as_execution_result(command.execute)
211
+ if self.class.configure_only?
212
+ as_execution_result(command.execute)
213
+ else
214
+ result = Brut.container.instrumentation.span("CLI.#{$0}", class: self.class.name) do |span|
215
+ as_execution_result(command.execute)
216
+ end
217
+ result
218
+ end
212
219
  rescue Brut::CLI::Error => ex
213
220
  abort_execution(ex.message)
214
221
  end
@@ -13,12 +13,10 @@ class Brut::CLI::Apps::DB < Brut::CLI::App
13
13
  def handle_bootstrap_exception(ex)
14
14
  case ex
15
15
  when Sequel::DatabaseConnectionError
16
- err.puts "Database needs to be created"
17
- stop_execution
16
+ abort_execution("Database needs to be created")
18
17
  when Sequel::DatabaseError
19
18
  if ex.cause.kind_of?(PG::UndefinedTable)
20
- err.puts "Migrations need to be run"
21
- stop_execution
19
+ abort_execution("Migrations need to be run")
22
20
  else
23
21
  super
24
22
  end
@@ -87,6 +85,7 @@ class Brut::CLI::Apps::DB < Brut::CLI::App
87
85
  stop_execution
88
86
  when Sequel::DatabaseError
89
87
  if ex.cause.kind_of?(PG::UndefinedTable)
88
+ out.puts ex.message
90
89
  out.puts "Migrations need to be run"
91
90
  continue_execution
92
91
  else
@@ -151,8 +150,7 @@ class Brut::CLI::Apps::DB < Brut::CLI::App
151
150
  def handle_bootstrap_exception(ex)
152
151
  case ex
153
152
  when Sequel::DatabaseConnectionError
154
- err.puts "Database does not exist. Create it first"
155
- stop_execution
153
+ abort_execution("Database does not exist. Create it first")
156
154
  when Sequel::DatabaseError
157
155
  if ex.cause.kind_of?(PG::UndefinedTable)
158
156
  # ignoring - we are running migrations which will address this
@@ -189,7 +187,9 @@ class Brut::CLI::Apps::DB < Brut::CLI::App
189
187
  formatted
190
188
  }
191
189
  Brut.container.sequel_db_handle.logger = logger
192
- Sequel::Migrator.run(Brut.container.sequel_db_handle,migrations_dir)
190
+ Brut.container.instrumentation.span("migrations.run") do
191
+ Sequel::Migrator.run(Brut.container.sequel_db_handle,migrations_dir)
192
+ end
193
193
  out.puts "Migrations applied"
194
194
  end
195
195
  end
@@ -40,21 +40,29 @@ class Brut::CLI::Apps::Test < Brut::CLI::App
40
40
  def execute
41
41
  Brut.container.sequel_db_handle.disconnect
42
42
  if options.rebuild?(default: rebuild_by_default?)
43
- out.puts "Rebuilding test database schema"
44
- system! "bin/db rebuild --env=test"
43
+ Brut.container.instrumentation.span("schema.rebuild.before") do
44
+ out.puts "Rebuilding test database schema"
45
+ system! "bin/db rebuild --env=test"
46
+ end
45
47
  end
46
- if args.empty?
47
- out.puts "Running all tests"
48
- system! "#{rspec_command} #{Brut.container.app_specs_dir}/"
49
- else
50
- test_args = args.map { |_|
51
- '"' + Shellwords.escape(_) + '"'
52
- }.join(" ")
53
- system! "#{rspec_command} #{test_args}"
48
+ Brut.container.instrumentation.span("tests.run") do |span|
49
+ if args.empty?
50
+ span.add_attributes(tests: :all)
51
+ out.puts "Running all tests"
52
+ system! "#{rspec_command} #{Brut.container.app_specs_dir}/"
53
+ else
54
+ span.add_attributes(tests: args.length)
55
+ test_args = args.map { |_|
56
+ '"' + Shellwords.escape(_) + '"'
57
+ }.join(" ")
58
+ system! "#{rspec_command} #{test_args}"
59
+ end
54
60
  end
55
61
  if options.rebuild_after?(default: rebuild_after_by_default?)
56
- out.puts "Re-Rebuilding test database schema"
57
- system! "bin/db rebuild --env=test"
62
+ Brut.container.instrumentation.span("schema.rebuild.after") do
63
+ out.puts "Re-Rebuilding test database schema"
64
+ system! "bin/db rebuild --env=test"
65
+ end
58
66
  end
59
67
  0
60
68
  end
@@ -424,6 +424,15 @@ class Brut::Framework::Config
424
424
  allow_nil: true,
425
425
  )
426
426
 
427
+ Brut.container.store(
428
+ "fallback_host",
429
+ URI,
430
+ "Hostname to use in situations where the request is not available",
431
+ nil,
432
+ allow_app_override: true,
433
+ allow_nil: true
434
+ )
435
+
427
436
  c.store(
428
437
  "local_hostname",
429
438
  String,
@@ -0,0 +1,11 @@
1
+ # Raised when Brut configuration is missing an expected value. This is mostly raised when values that must be set per app
2
+ # have not been set.
3
+ class Brut::Framework::Errors::MissingConfiguration < Brut::Framework::Error
4
+ # Create the exception
5
+ #
6
+ # @param [String|Symbol] config_name the name of the missing configuration parameter
7
+ # @param [String] context Any additional context to understand the error
8
+ def initialize(config_name, context)
9
+ super("Configuration parameter '#{config_name}' did not have a value, but was expected to. #{context}")
10
+ end
11
+ end
@@ -7,6 +7,7 @@ module Brut
7
7
  autoload(:NotImplemented,"brut/framework/errors/not_implemented")
8
8
  autoload(:NotFound,"brut/framework/errors/not_found")
9
9
  autoload(:MissingParameter,"brut/framework/errors/missing_parameter")
10
+ autoload(:MissingConfiguration,"brut/framework/errors/missing_configuration")
10
11
  autoload(:AbstractMethod,"brut/framework/errors/abstract_method")
11
12
  autoload(:NoClassForPath,"brut/framework/errors/no_class_for_path")
12
13
  # Raises {Brut::Framework::Errors::Bug}
@@ -17,6 +17,9 @@ require "opentelemetry/exporter/otlp"
17
17
  # intended to use or interact with this class at all. End of line.
18
18
  class Brut::Framework::MCP
19
19
 
20
+ @otel_shutdown = Concurrent::AtomicBoolean.new(false)
21
+ def self.otel_shutdown = @otel_shutdown
22
+
20
23
  # Create and configure the MCP. The app will not work until {#boot!} has been called, however most of the core configuration
21
24
  # will be available via `Brut.container`.
22
25
  #
@@ -33,7 +36,7 @@ class Brut::Framework::MCP
33
36
  #
34
37
  # * Create the instance and *do not* call `boot!`. This is what you'd do if you can't or don't want to connect to external services
35
38
  # like the database. For example, when Brut builds assets, it does not call `boot!`.
36
- # * Create the intance and immediately call `boot!`. This is what happens most of the time, in particular when the app is started
39
+ # * Create the instance and immediately call `boot!`. This is what happens most of the time, in particular when the app is started
37
40
  # up by Puma to start serving requests.
38
41
  #
39
42
  # What you should avoid doing is creating an instance of this class and performing logic before later calling `boot!`.
@@ -66,49 +69,34 @@ class Brut::Framework::MCP
66
69
  begin
67
70
  Brut.container.sequel_db_handle.disconnect
68
71
  rescue Sequel::DatabaseConnectionError
69
- SemanticLogger["Sequel::Database"].info "Not connected to database, so not disconnecting"
72
+ SemanticLogger[self.class].info "Not connected to database, so not disconnecting"
73
+ end
74
+ otel_configured = OpenTelemetry.tracer_provider.is_a?(OpenTelemetry::SDK::Trace::TracerProvider)
75
+ if otel_configured
76
+ if $PROGRAM_NAME =~ /^sidekiq/
77
+ SemanticLogger[self.class].info "Assuming we are sidekiq, which will shutdown OpenTelemetry, so doing nothing", program: $PROGRAM_NAME
78
+ else
79
+ if self.class.otel_shutdown.make_true
80
+ SemanticLogger[self.class].info "Shutting down OpenTelemetry", program: $PROGRAM_NAME
81
+ OpenTelemetry.tracer_provider.shutdown
82
+ else
83
+ SemanticLogger[self.class].info "OpenTelemetry already shutdown", program: $PROGRAM_NAME
84
+ end
85
+ end
86
+ else
87
+ SemanticLogger[self.class].info "OpenTelemetry was not configured, so no shutdown needed", program: $PROGRAM_NAME
70
88
  end
71
89
  end
72
- Sequel::Database.extension :pg_array
73
-
74
- sequel_db = Brut.container.sequel_db_handle
75
-
76
- Sequel::Model.db = sequel_db
77
90
 
78
- Sequel::Model.plugin :find_bang
79
- Sequel::Model.plugin :created_at
80
- Sequel::Model.plugin :table_select
81
- Sequel::Model.plugin :skip_saving_columns
91
+ boot_postgres!
92
+ boot_otel!
82
93
 
83
- if !Brut.container.external_id_prefix.nil?
84
- Sequel::Model.plugin :external_id, global_prefix: Brut.container.external_id_prefix
85
- end
86
94
  if Brut.container.eager_load_classes?
87
95
  SemanticLogger["Brut"].info("Eagerly loading app's classes")
88
96
  @loader.eager_load
89
97
  else
90
98
  SemanticLogger["Brut"].info("Lazily loading app's classes")
91
99
  end
92
- OpenTelemetry::SDK.configure do |c|
93
- c.service_name = @app.id
94
- if ENV["OTEL_TRACES_EXPORTER"]
95
- SemanticLogger[self.class].info "OTEL_TRACES_EXPORTER was set (to '#{ENV['OTEL_TRACES_EXPORTER']}'), so Brut's OTel logging is disabled"
96
- else
97
- c.add_span_processor(
98
- OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(
99
- Brut::Instrumentation::LoggerSpanExporter.new
100
- )
101
- )
102
- end
103
- end
104
-
105
- Brut.container.store(
106
- "tracer",
107
- OpenTelemetry::SDK::Trace::Tracer,
108
- "Tracer for Open Telemetry",
109
- OpenTelemetry.tracer_provider.tracer(@app.id)
110
- )
111
- Sequel::Database.extension :brut_instrumentation
112
100
 
113
101
  @app.boot!
114
102
 
@@ -295,4 +283,53 @@ private
295
283
 
296
284
  @loader.setup
297
285
  end
286
+
287
+ def boot_postgres!
288
+ Sequel::Database.extension :pg_array
289
+ Sequel::Database.extension :pg_json
290
+
291
+ sequel_db = Brut.container.sequel_db_handle
292
+
293
+ Sequel::Model.db = sequel_db
294
+
295
+ Sequel::Model.plugin :find_bang
296
+ Sequel::Model.plugin :created_at
297
+ Sequel::Model.plugin :table_select
298
+ Sequel::Model.plugin :skip_saving_columns
299
+
300
+ if !Brut.container.external_id_prefix.nil?
301
+ Sequel::Model.plugin :external_id, global_prefix: Brut.container.external_id_prefix
302
+ end
303
+ Sequel::Database.extension :brut_instrumentation
304
+ end
305
+
306
+ def boot_otel!
307
+ OpenTelemetry::SDK.configure do |c|
308
+ c.service_name = @app.id
309
+ if ENV["OTEL_TRACES_EXPORTER"]
310
+ SemanticLogger[self.class].info "OTEL_TRACES_EXPORTER was set (to '#{ENV['OTEL_TRACES_EXPORTER']}'), so Brut's OTel logging is disabled"
311
+ else
312
+ c.add_span_processor(
313
+ OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(
314
+ Brut::Instrumentation::LoggerSpanExporter.new
315
+ )
316
+ )
317
+ end
318
+
319
+ if defined?(OpenTelemetry::Instrumentation::Sidekiq)
320
+ c.use 'OpenTelemetry::Instrumentation::Sidekiq', {
321
+ span_naming: :job_class,
322
+ }
323
+ else
324
+ SemanticLogger[self.class].info "OpenTelemetry::Instrumentation::Sidekiq is not loaded, so Sidekiq traces will not be captured"
325
+ end
326
+ end
327
+
328
+ Brut.container.store(
329
+ "tracer",
330
+ OpenTelemetry::SDK::Trace::Tracer,
331
+ "Tracer for Open Telemetry",
332
+ OpenTelemetry.tracer_provider.tracer(@app.id)
333
+ )
334
+ end
298
335
  end
@@ -15,7 +15,7 @@ module Brut::FrontEnd::HandlingResults
15
15
  if !klass.kind_of?(Class)
16
16
  raise ArgumentError,"redirect_to should be given a Class, not a #{klass.class}"
17
17
  end
18
- Brut.container.routing.uri(klass,with_method: :get,**query_string_params)
18
+ Brut.container.routing.path(klass,with_method: :get,**query_string_params)
19
19
  end
20
20
 
21
21
  # Return this to return an HTTP status code from a number or string containing the code.
@@ -15,13 +15,15 @@ class Brut::FrontEnd::RequestContext
15
15
  # @param [Brut::FrontEnd::Flash] flash the current flash
16
16
  # @param [true|false] xhr true if this is an XHR request.
17
17
  # @param [Object] body the `request.body` as provided by Rack
18
- def initialize(env:,session:,flash:,xhr:,body:)
18
+ # @param [URI] host URI the `request.host` and `request.scheme`, and `request.port` as provided by Rack
19
+ def initialize(env:,session:,flash:,xhr:,body:,host:)
19
20
  @hash = {
20
21
  env:,
21
22
  session:,
22
23
  flash:,
23
24
  xhr:,
24
25
  body:,
26
+ host:,
25
27
  csrf_token: Rack::Protection::AuthenticityToken.token(env["rack.session"]),
26
28
  clock: Clock.new(session.timezone),
27
29
  }
@@ -5,9 +5,10 @@ class Brut::FrontEnd::RouteHooks::SetupRequestContext < Brut::FrontEnd::RouteHoo
5
5
  def before(session:,request:,env:)
6
6
  flash = session.flash
7
7
  session[:_flash] ||= flash
8
+ host_uri = URI.parse("#{request.scheme}://#{request.host}:#{request.port}")
8
9
  Thread.current.thread_variable_set(
9
10
  :request_context,
10
- Brut::FrontEnd::RequestContext.new(env:,session:session,flash:,xhr: request.xhr?,body: request.body)
11
+ Brut::FrontEnd::RequestContext.new(env:,session:session,flash:,xhr: request.xhr?,body: request.body, host: host_uri)
11
12
  )
12
13
  continue
13
14
  end
@@ -116,7 +116,25 @@ class Brut::FrontEnd::Routing
116
116
  route
117
117
  end
118
118
 
119
- def uri(handler_class, with_method: :any, **rest)
119
+ def path(handler_class, with_method: :any, **rest)
120
+ route = self.route_for(handler_class, with_method:)
121
+ route.path(**rest)
122
+ end
123
+
124
+ def url(handler_class, with_method: :any, **rest)
125
+ route = self.route_for(handler_class, with_method:)
126
+ route.url(**rest)
127
+ end
128
+
129
+ def inspect
130
+ @routes.map { |route|
131
+ "#{route.http_method}:#{route.path_template} - #{route.handler_class.name}"
132
+ }.join("\n")
133
+ end
134
+
135
+ private
136
+
137
+ def route_for(handler_class, with_method: :any)
120
138
  route = self.route(handler_class)
121
139
  route_allowed_for_method = if with_method == :any
122
140
  true
@@ -128,14 +146,9 @@ class Brut::FrontEnd::Routing
128
146
  if !route_allowed_for_method
129
147
  raise ArgumentError,"The route for '#{handler_class}' (#{route.path}) is not supported by HTTP method '#{with_method}'"
130
148
  end
131
- route.path(**rest)
149
+ route
132
150
  end
133
151
 
134
- def inspect
135
- @routes.map { |route|
136
- "#{route.http_method}:#{route.path_template} - #{route.handler_class.name}"
137
- }.join("\n")
138
- end
139
152
 
140
153
  def add_routing_method(route)
141
154
  handler_class = route.handler_class
@@ -143,7 +156,10 @@ class Brut::FrontEnd::Routing
143
156
  [ handler_class, form_class ].compact.each do |klass|
144
157
  klass.class_eval do
145
158
  def self.routing(**args)
146
- Brut.container.routing.uri(self,**args)
159
+ Brut.container.routing.path(self,**args)
160
+ end
161
+ def self.full_routing(**args)
162
+ Brut.container.routing.url(self,**args)
147
163
  end
148
164
  end
149
165
  end
@@ -205,11 +221,31 @@ class Brut::FrontEnd::Routing
205
221
  uri
206
222
  end
207
223
 
224
+ def url(**query_string_params)
225
+ request_context = Thread.current.thread_variable_get(:request_context)
226
+ path = self.path(**query_string_params)
227
+ host = if request_context
228
+ request_context[:host]
229
+ end
230
+ host ||= Brut.container.fallback_host
231
+ host.merge(path)
232
+ rescue ArgumentError => ex
233
+ request_context_note = if request_context
234
+ "the RequestContext did not contain request.host, which is unusual"
235
+ else
236
+ "the RequestContext was not available (likely due to calling `full_routing` outside an HTTP request)"
237
+ end
238
+ raise Brut::Framework::Errors::MissingConfiguration(
239
+ :fallback_host,
240
+ "Attempting to get the full URL for route #{self.path_template}, #{request_context_note}, and Brut.container.fallback_host was not set. You must set this value if you expect to generate complete URLs outside of an HTTP request")
241
+ end
242
+
208
243
  def ==(other)
209
244
  self.method == other.method && self.path == other.path
210
245
  end
211
246
 
212
247
  private
248
+
213
249
  def locate_handler_class(suffix,preposition, on_missing: :raise)
214
250
  if @path_template == "/"
215
251
  return Module.const_get("HomePage") # XXX Needs error handling
@@ -2,10 +2,23 @@
2
2
  module Brut::SpecSupport::ClockSupport
3
3
  # Return a real lock in UTC
4
4
  def real_clock = Clock.new(TZInfo::Timezone.get("UTC"))
5
- # Return a clock whose value for now is `now`
5
+
6
+ # Return a clock whose value for now is `now`, in UTC
6
7
  #
7
8
  # @param [String] now a string containing the value you want for {Clock#now} to return.
8
9
  def clock_at(now:)
9
- Clock.new(TZInfo::Timezone.get("UTC"), now: Time.parse(now))
10
+ self.clock_in_timezone_at(timezone_name: "UTC", now: now)
11
+ end
12
+
13
+ # Return a clock whose value for now is `now` in the given timezone
14
+ #
15
+ # @param [String] timezone_name a string that is the name of the timezone to use.
16
+ # @param [String] now a string containing the value you want for {Clock#now} to return.
17
+ def clock_in_timezone_at(timezone_name:, now:)
18
+ time = Time.parse(now)
19
+ timezone = TZInfo::Timezone.get(timezone_name)
20
+ same_time_in_timezone = timezone.local_time(time.year, time.month, time.day, time.hour, time.min, time.sec)
21
+
22
+ Clock.new(TZInfo::Timezone.get(timezone_name), now: same_time_in_timezone)
10
23
  end
11
24
  end
@@ -78,7 +78,7 @@ module Brut::SpecSupport::ComponentSupport
78
78
 
79
79
  # @!visibility private
80
80
  def routing_for(klass,**args)
81
- Brut.container.routing.uri(klass,**args)
81
+ Brut.container.routing.path(klass,**args)
82
82
  end
83
83
 
84
84
  # Escape HTML using the same code Brut uses for rendering templates.
@@ -5,6 +5,16 @@ module Brut::SpecSupport::GeneralSupport
5
5
  end
6
6
 
7
7
  module ClassMethods
8
+ def implementation_is_needed(check_again_at:)
9
+ check_again_at = if check_again_at.kind_of?(Time)
10
+ check_again_at
11
+ else
12
+ check_again_at = Date.parse(check_again_at).to_time
13
+ end
14
+ it "has no tests for now, but they are needed eventually" do
15
+ expect(Time.now < check_again_at).to eq(true),"It's after #{check_again_at}. Implementation is needed"
16
+ end
17
+ end
8
18
  # To pass bin/test audit with a class whose implementation is trivial, call this inside the RSpec `describe` block. This is better
9
19
  # than an empty test as it makes it more explicit that you believe the implementation is trivial enough to not require a test. You
10
20
  # can also set an expiration for this thinking.
@@ -23,7 +33,7 @@ module Brut::SpecSupport::GeneralSupport
23
33
  if check_again_at.nil?
24
34
  expect(true).to eq(true)
25
35
  else
26
- expect(Time.now < check_again_at).to eq(true),"I'ts after #{check_again_at}. Check that the implementation of the class under test is still trivial. If it is, update or remove check_again_at:"
36
+ expect(Time.now < check_again_at).to eq(true),"It's after #{check_again_at}. Check that the implementation of the class under test is still trivial. If it is, update or remove check_again_at:"
27
37
  end
28
38
  end
29
39
  end
@@ -1,10 +1,10 @@
1
1
  RSpec::Matchers.define :be_routing_for do |klass,**args|
2
2
  match do |uri|
3
- uri == Brut.container.routing.uri(klass,**args)
3
+ uri == Brut.container.routing.path(klass,**args)
4
4
  end
5
5
 
6
6
  failure_message do |uri|
7
- expected = Brut.container.routing.uri(klass,**args)
7
+ expected = Brut.container.routing.path(klass,**args)
8
8
  "Expected route for #{klass}: #{expected}, but got #{uri}"
9
9
  end
10
10
 
@@ -103,6 +103,7 @@ class Brut::SpecSupport::RSpecSetup
103
103
  flash: empty_flash,
104
104
  body: nil,
105
105
  xhr: false,
106
+ host: URI("https://example.com")
106
107
  )
107
108
  Thread.current.thread_variable_set(:request_context, request_context)
108
109
  example.example_group.let(:request_context) { request_context }
data/lib/brut/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Brut
2
2
  # @!visibility private
3
- VERSION = "0.0.8"
3
+ VERSION = "0.0.10"
4
4
  end
data/lib/brut.rb CHANGED
@@ -50,6 +50,7 @@ module Brut
50
50
  # will be in the back end, Brut is far less prescriptive about how to manage that than it is the front end.
51
51
  module BackEnd
52
52
  autoload(:Validators, "brut/back_end/validator")
53
+ autoload(:Sidekiq, "brut/back_end/sidekiq")
53
54
  # Do not put SeedData here - it must be loaded only when needed
54
55
  end
55
56
  # I18n is where internationalization and localization support lives.
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brut
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Bryant Copeland
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-03-15 00:00:00.000000000 Z
10
+ date: 2025-04-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: irb
@@ -455,6 +455,10 @@ files:
455
455
  - dx/stop
456
456
  - lib/brut.rb
457
457
  - lib/brut/back_end/seed_data.rb
458
+ - lib/brut/back_end/sidekiq.rb
459
+ - lib/brut/back_end/sidekiq/middlewares.rb
460
+ - lib/brut/back_end/sidekiq/middlewares/server.rb
461
+ - lib/brut/back_end/sidekiq/middlewares/server/flush_spans.rb
458
462
  - lib/brut/back_end/validator.rb
459
463
  - lib/brut/back_end/validators/form_validator.rb
460
464
  - lib/brut/cli.rb
@@ -478,6 +482,7 @@ files:
478
482
  - lib/brut/framework/errors.rb
479
483
  - lib/brut/framework/errors/abstract_method.rb
480
484
  - lib/brut/framework/errors/bug.rb
485
+ - lib/brut/framework/errors/missing_configuration.rb
481
486
  - lib/brut/framework/errors/missing_parameter.rb
482
487
  - lib/brut/framework/errors/no_class_for_path.rb
483
488
  - lib/brut/framework/errors/not_found.rb