Gate Sentry and the access log on config
RACK_ENV's only job was to mark the process as running tests; a nil
SENTRY_DSN and an injected null access logger do that directly. The
trade: a missing DSN no longer raises at boot.
Assisted-by: GLM-5.3 via pi
diff --git a/Rakefile b/Rakefile
index 9e6f52f..1d93de9 100644
--- a/Rakefile
+++ b/Rakefile
@@ -17,10 +17,13 @@ end
desc "Regenerate macOS exchange response fixtures from current responses"
task :fixtures do
- ENV["RACK_ENV"] = "test"
# Mirrors test/test_helper.rb, which cannot be required here without
# minitest/autorun running its at_exit hook inside rake.
+ ENV.delete("SENTRY_DSN")
ENV["PRO_TACTS_DATA_DIR"] = (Pathname.new(__dir__) / "test/fixtures").to_s
+ require "logger"
+ require "pro_tacts"
+ ProTacts.config.access_logger = Logger.new(IO::NULL)
require "pro_tacts/web"
require_relative "test/pro_tacts/exchange_fixtures"
ExchangeFixtures.record_responses(ProTacts::Web)
diff --git a/lib/pro_tacts/config.rb b/lib/pro_tacts/config.rb
index 667a78d..b85b7ca 100644
--- a/lib/pro_tacts/config.rb
+++ b/lib/pro_tacts/config.rb
@@ -12,19 +12,11 @@ module ProTacts
@env = env
end
- # The deployment environment: APP_ENV wins, then RACK_ENV, then
- # "development".
- def environment
- @env.fetch("APP_ENV") { @env.fetch("RACK_ENV", "development") }
- end
-
- def test?
- environment == "test"
- end
-
- # Sentry DSN. Required at boot; raises if unset so the failure is loud.
+ # Sentry DSN; nil when unset. Presence is the switch for Sentry:
+ # without it the app runs uninitialized, where capture_message and
+ # the rack middleware are no-ops.
def sentry_dsn
- @env.fetch("SENTRY_DSN")
+ @env.fetch("SENTRY_DSN", nil)
end
# Whether to dump full request/response exchanges to the log. Off by
@@ -47,6 +39,10 @@ module ProTacts
data_dir / "contacts"
end
+ # Override for Roda's common_logger target; nil keeps the plugin's
+ # stderr default. Tests swap in a null logger for quiet runs.
+ attr_accessor :access_logger
+
# Where the debug logger writes. A path, overridable with
# PRO_TACTS_DEBUG_LOG; "stderr" keeps it on the process's stderr.
def debug_log_path
diff --git a/lib/pro_tacts/web.rb b/lib/pro_tacts/web.rb
index c10e12d..e83f671 100644
--- a/lib/pro_tacts/web.rb
+++ b/lib/pro_tacts/web.rb
@@ -2,9 +2,9 @@
require "pro_tacts"
require "sentry-ruby"
-# Tests skip Sentry entirely: capture_message and the rack middleware are
-# no-ops while Sentry is uninitialized, so no DSN is needed there.
-unless ProTacts.config.test?
+# Without a DSN, Sentry stays uninitialized and capture_message and
+# the rack middleware are no-ops, so there is nothing to skip in tests.
+if ProTacts.config.sentry_dsn
Sentry.init do |sentry|
sentry.dsn = ProTacts.config.sentry_dsn
@@ -41,7 +41,7 @@ module ProTacts
plugin :all_verbs
plugin :dav_verbs
- plugin :common_logger unless ProTacts.config.test?
+ plugin :common_logger, ProTacts.config.access_logger
plugin :not_found do
Sentry.capture_message("404 Not Found", level: :warning)
diff --git a/test/pro_tacts/test_config.rb b/test/pro_tacts/test_config.rb
index bcfb1cc..4437bb7 100644
--- a/test/pro_tacts/test_config.rb
+++ b/test/pro_tacts/test_config.rb
@@ -1,28 +1,18 @@
+require "logger"
+
require "minitest/autorun"
require "pro_tacts/config"
class ConfigTest < Minitest::Test
- def test_environment_defaults_to_development
- assert_equal "development", ProTacts::Config.new({}).environment
- assert_equal "production", ProTacts::Config.new("RACK_ENV" => "production").environment
- assert_equal "production", ProTacts::Config.new("RACK_ENV" => "development", "APP_ENV" => "production").environment
- end
-
- def test_testenv
- assert ProTacts::Config.new("RACK_ENV" => "test").test?
- assert ProTacts::Config.new("APP_ENV" => "test").test?
- refute ProTacts::Config.new({}).test?
- refute ProTacts::Config.new("RACK_ENV" => "production").test?
- end
-
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 }
+ def test_sentry_dsn_is_nil_when_unset
+ assert_nil ProTacts::Config.new({}).sentry_dsn
+ assert_nil ProTacts::Config.new("SENTRY_DSN" => nil).sentry_dsn
end
def test_data_dir_defaults_to_data
@@ -54,6 +44,19 @@ class ConfigTest < Minitest::Test
refute ProTacts::Config.new("PRO_TACTS_DEBUG" => "0").debug?
end
+ def test_access_logger_defaults_to_nil
+ assert_nil ProTacts::Config.new({}).access_logger
+ end
+
+ def test_access_logger_is_overridable
+ config = ProTacts::Config.new({})
+ null_logger = Logger.new(IO::NULL)
+
+ config.access_logger = null_logger
+
+ assert_same null_logger, config.access_logger
+ end
+
def test_debug_log_path_defaults_to_a_file
assert_equal "log/debug.log", ProTacts::Config.new({}).debug_log_path
end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 6d59f00..2ef8f9e 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -1,13 +1,18 @@
-# Marks the process as running tests before anything requires the app, so
-# web.rb skips Sentry.init and no SENTRY_DSN is needed. The fixtures
-# directory serves as the data directory: its contacts/ holds the card the
-# recorded macOS exchange asked for, so the replay resolves the same hrefs
-# the client did.
-ENV["RACK_ENV"] = "test"
+# Drops an ambient SENTRY_DSN (e.g. exported by direnv) before anything
+# requires the app, so Sentry stays uninitialized here and the 404 tests
+# cannot ship events. The fixtures directory serves as the data directory:
+# its contacts/ holds the card the recorded macOS exchange asked for, so
+# the replay resolves the same hrefs the client did.
+ENV.delete("SENTRY_DSN")
require "pathname"
ENV["PRO_TACTS_DATA_DIR"] = (Pathname.new(__dir__) / "fixtures").to_s
+# Silence the request log; web.rb reads this when it is required.
+require "logger"
+require "pro_tacts"
+ProTacts.config.access_logger = Logger.new(IO::NULL)
+
require "minitest/autorun"