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
change musrqwzzqxnlyrsxvtnqqoorzvvrvmvk
commit e698d0a2f8802ff4a8f5bba3550eeec269d181f3
author Alpha Chen <alpha@kejadlen.dev>
date
parent mknxktsl
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"