Files
discourse/spec/requests/default_headers_spec.rb
Alan Guo Xiang Tan b3881d42d0 DEV: Disallow the use of Rails.logger= in RSpec tests (#31920)
Setting Rails.logger after the application has been initialized does not
seem to be safe anymore and can lead to flaky tests. This commit
disallows the reassignment of `Rails.logger` going forward and updates
the affected test.

### Reviewer notes

Reassigning `Rails.logger` from within RSpec tests is causing tests
which uses `Rails.logger.broadcast_to(FakeLogger.new)` to flake.
Example:
https://github.com/discourse/discourse/actions/runs/13951116847/job/39050616967

```
  1) invalid requests handles NotFound with invalid json body
     Failure/Error: expect(fake_logger.errors).to have_attributes(size: 1)
     
       expected [] to have attributes {:size => 1} but had attributes {:size => 0}
       Diff:
       @@ -1 +1 @@
       -:size => 1,
       +:size => 0,
       
     # ./spec/integration/invalid_request_spec.rb:18:in `block (2 levels) in <main>'
     # ./spec/rails_helper.rb:619:in `block (3 levels) in <top (required)>'
     # /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/benchmark-0.4.0/lib/benchmark.rb:304:in `measure'
     # ./spec/rails_helper.rb:619:in `block (2 levels) in <top (required)>'
     # ./spec/rails_helper.rb:580:in `block (3 levels) in <top (required)>'
     # /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/timeout-0.4.3/lib/timeout.rb:185:in `block in timeout'
     # /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/timeout-0.4.3/lib/timeout.rb:192:in `timeout'
     # ./spec/rails_helper.rb:570:in `block (2 levels) in <top (required)>'
     # ./spec/rails_helper.rb:527:in `block (2 levels) in <top (required)>'
     # /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/webmock-3.25.1/lib/webmock/rspec.rb:39:in `block (2 levels) in <top (required)>'
```
2025-03-21 08:48:38 +08:00

75 lines
2.6 KiB
Ruby

# frozen_string_literal: true
RSpec.describe Middleware::DefaultHeaders do
let(:mock_default_headers) do
{
"X-XSS-Protection" => "0",
"X-Content-Type-Options" => "nosniff",
"X-Permitted-Cross-Domain-Policies" => "none",
"Referrer-Policy" => "strict-origin-when-cross-origin",
}
end
let(:html_only_headers) { described_class::HTML_ONLY_HEADERS }
let(:universal_headers) { Set.new(mock_default_headers.keys) - html_only_headers }
before do
allow(Rails.application.config.action_dispatch).to receive(:default_headers).and_return(
mock_default_headers,
)
end
context "when a public exception(like RoutingError) is raised" do
context "when requesting an HTML page" do
let(:html_path) { "/nonexistent" }
it "sets the Cross-Origin-Opener-Policy header" do
SiteSetting.bootstrap_error_pages = true
get html_path # triggers a RoutingError, handled by the exceptions_app
expect(response.headers).to have_key("Cross-Origin-Opener-Policy")
expect(response.headers["Cross-Origin-Opener-Policy"]).to eq("same-origin-allow-popups")
end
it "sets all default Rails headers for HTML responses" do
get html_path
mock_default_headers.each { |name, value| expect(response.headers[name]).to eq(value) }
end
end
context "when requesting a JSON response for an invalid URL" do
let(:json_path) { "/nonexistent.json" }
it "adds only universal default headers to non-HTML responses" do
get json_path
universal_headers.each do |name|
expect(response.headers[name]).to eq(mock_default_headers[name])
end
html_only_headers.each { |name| expect(response.headers[name]).to be_nil }
expect(response.headers["Cross-Origin-Opener-Policy"]).to be_nil
end
end
end
context "when a rescued exception is raised" do
let(:fake_logger) { FakeLogger.new }
before { Rails.logger.broadcast_to(fake_logger) }
after { Rails.logger.stop_broadcasting_to(fake_logger) }
it "adds default headers to the response" do
bad_str = (+"d\xDE").force_encoding("utf-8")
expect(bad_str.valid_encoding?).to eq(false)
get "/latest", params: { test: bad_str }
expect(fake_logger.warnings.length).to eq(0)
expect(response.status).to eq(400)
expect(response.headers).to have_key("Cross-Origin-Opener-Policy")
expect(response.headers["Cross-Origin-Opener-Policy"]).to eq("same-origin-allow-popups")
mock_default_headers.each { |name, value| expect(response.headers[name]).to eq(value) }
end
end
end