Read all environment config through ProTacts::Config
ProTacts::Config is now the single place that reads ENV; Sentry init and the debug-logging toggle go through ProTacts.config instead of touching the environment directly.
Assisted-by: GLM 5.2 via pi
diff --git a/lib/pro_tacts.rb b/lib/pro_tacts.rb
index b6f7e50..882af77 100644
--- a/lib/pro_tacts.rb
+++ b/lib/pro_tacts.rb
@@ -1,11 +1,13 @@
# frozen_string_literal: true
+require "pro_tacts/config"
+
module ProTacts
- # Whether debug logging is on. When true, every request and response is
- # dumped in full — headers and bodies on both sides. Off by default because
- # it logs contact data; see ProTacts::DebugLogger.
- def self.debug_logging?
- value = ENV["PRO_TACTS_DEBUG"]
- !value.nil? && value.match?(/\A(1|true|yes)\z/i)
+ class << self
+ def config
+ @config ||= Config.new
+ end
+
+ attr_writer :config
end
end
diff --git a/lib/pro_tacts/config.rb b/lib/pro_tacts/config.rb
new file mode 100644
index 0000000..09909bb
--- /dev/null
+++ b/lib/pro_tacts/config.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module ProTacts
+ # Single source of truth for configuration read from the environment.
+ # Nothing else in the app should read ENV directly; add a method here and
+ # read it through ProTacts.config instead.
+ class Config
+ TRUTHY = /\A(1|true|yes)\z/i
+
+ def initialize(env = ENV)
+ @env = env
+ end
+
+ # Sentry DSN. Required at boot; raises if unset so the failure is loud.
+ def sentry_dsn
+ @env.fetch("SENTRY_DSN")
+ end
+
+ # Whether to dump full request/response exchanges to the log. Off by
+ # default because it logs contact data. See ProTacts::DebugLogger.
+ def debug?
+ value = @env.fetch("PRO_TACTS_DEBUG", nil)
+ !value.nil? && value.match?(TRUTHY)
+ end
+ end
+end
diff --git a/lib/pro_tacts/web.rb b/lib/pro_tacts/web.rb
index fed393e..445a180 100644
--- a/lib/pro_tacts/web.rb
+++ b/lib/pro_tacts/web.rb
@@ -1,26 +1,25 @@
# frozen_string_literal: true
+require "pro_tacts"
require "sentry-ruby"
-Sentry.init do |config|
- # TODO: Consolidate configuration
- config.dsn = ENV.fetch("SENTRY_DSN")
+Sentry.init do |sentry|
+ sentry.dsn = ProTacts.config.sentry_dsn
# Get breadcrumbs from logs
- config.breadcrumbs_logger = [:sentry_logger, :http_logger]
+ sentry.breadcrumbs_logger = [:sentry_logger, :http_logger]
# Add data like request headers and IP for users, if applicable;
# see https://docs.sentry.io/platforms/ruby/data-management/data-collected/ for more info
- config.send_default_pii = true
+ sentry.send_default_pii = true
# Trace all the things!
- config.traces_sample_rate = 1.0
+ sentry.traces_sample_rate = 1.0
end
require "rack/rewindable_input"
require "roda"
-require "pro_tacts"
require "pro_tacts/debug_logger"
require "roda/plugins/dav_verbs"
@@ -30,7 +29,7 @@ module ProTacts
# and then rewind it so the application can still access it.
use Rack::RewindableInput::Middleware
use Sentry::Rack::CaptureExceptions
- use ProTacts::DebugLogger if ProTacts.debug_logging?
+ use ProTacts::DebugLogger if ProTacts.config.debug?
plugin :all_verbs
plugin :dav_verbs
diff --git a/test/pro_tacts/test_config.rb b/test/pro_tacts/test_config.rb
new file mode 100644
index 0000000..0faf130
--- /dev/null
+++ b/test/pro_tacts/test_config.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require "minitest/autorun"
+
+require "pro_tacts/config"
+
+class ConfigTest < Minitest::Test
+ def test_sentry_dsn_is_passed_through
+ assert_equal "https://example/1", ProTacts::Config.new("SENTRY_DSN" => "https://example/1").sentry_dsn
+ end
+
+ def test_sentry_dsn_is_required
+ assert_raises(KeyError) { ProTacts::Config.new({}).sentry_dsn }
+ end
+
+ def test_debug_defaults_off
+ refute ProTacts::Config.new({}).debug?
+ refute ProTacts::Config.new("PRO_TACTS_DEBUG" => nil).debug?
+ end
+
+ def test_debug_turns_on_for_truthy_values
+ assert ProTacts::Config.new("PRO_TACTS_DEBUG" => "1").debug?
+ assert ProTacts::Config.new("PRO_TACTS_DEBUG" => "true").debug?
+ assert ProTacts::Config.new("PRO_TACTS_DEBUG" => "YES").debug?
+ end
+
+ def test_debug_ignores_other_values
+ refute ProTacts::Config.new("PRO_TACTS_DEBUG" => "no").debug?
+ refute ProTacts::Config.new("PRO_TACTS_DEBUG" => "0").debug?
+ end
+end
diff --git a/test/pro_tacts/test_debug_logger.rb b/test/pro_tacts/test_debug_logger.rb
index 377977e..e253a0a 100644
--- a/test/pro_tacts/test_debug_logger.rb
+++ b/test/pro_tacts/test_debug_logger.rb
@@ -3,7 +3,6 @@
require "minitest/autorun"
require "stringio"
-require "pro_tacts"
require "pro_tacts/debug_logger"
class DebugLoggerTest < Minitest::Test
@@ -82,34 +81,4 @@ class DebugLoggerTest < Minitest::Test
assert_equal "text/xml", headers["Content-Type"]
assert_equal ["<multistatus/>"], body
end
-
- class ToggleTest < Minitest::Test
- def test_defaults_off_when_unset
- with_env("PRO_TACTS_DEBUG" => nil) do
- refute ProTacts.debug_logging?
- end
- end
-
- def test_turns_on_with_truthy_values
- with_env("PRO_TACTS_DEBUG" => "1") do
- assert ProTacts.debug_logging?
- end
- end
-
- def test_ignores_other_values
- with_env("PRO_TACTS_DEBUG" => "no") do
- refute ProTacts.debug_logging?
- end
- end
-
- private
-
- def with_env(vars)
- saved = vars.keys.to_h { |k| [k, ENV[k]] }
- vars.each { |k, v| v.nil? ? ENV.delete(k) : ENV[k] = v }
- yield
- ensure
- saved.each { |k, v| v.nil? ? ENV.delete(k) : ENV[k] = v }
- end
- end
end