Skip to content

Commit 0be5181

Browse files
authored
Add tests for changes in #551
1 parent 1147e01 commit 0be5181

File tree

1 file changed

+163
-0
lines changed

1 file changed

+163
-0
lines changed
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# frozen_string_literal: true
2+
require "spec_helper"
3+
4+
describe "SecureHeaders::Railtie" do
5+
# Store initializer blocks so we can execute them in tests
6+
let(:initializer_blocks) { {} }
7+
let(:default_headers) { {} }
8+
9+
before do
10+
# Clean up any previous Rails/ActiveSupport definitions
11+
Object.send(:remove_const, :Rails) if defined?(Rails)
12+
Object.send(:remove_const, :ActiveSupport) if defined?(ActiveSupport)
13+
14+
# Remove the SecureHeaders::Railtie if it was previously defined
15+
SecureHeaders.send(:remove_const, :Railtie) if defined?(SecureHeaders::Railtie)
16+
17+
blocks = initializer_blocks
18+
headers = default_headers
19+
20+
# Mock ActiveSupport.on_load to immediately execute the block
21+
stub_const("ActiveSupport", Module.new do
22+
define_singleton_method(:on_load) do |name, &block|
23+
block.call if block
24+
end
25+
end)
26+
27+
# Create mock Rails module with application config
28+
rails_module = Module.new do
29+
# Create a mock Railtie base class
30+
const_set(:Railtie, Class.new do
31+
define_singleton_method(:isolate_namespace) { |*| }
32+
define_singleton_method(:initializer) do |name, &block|
33+
blocks[name] = block
34+
end
35+
define_singleton_method(:rake_tasks) { |&block| }
36+
end)
37+
38+
# Create mock application with config
39+
config_action_dispatch = Struct.new(:default_headers).new(headers)
40+
config_middleware = Object.new.tap do |mw|
41+
mw.define_singleton_method(:insert_before) { |*| }
42+
end
43+
config = Struct.new(:action_dispatch, :middleware).new(config_action_dispatch, config_middleware)
44+
application = Struct.new(:config).new(config)
45+
46+
define_singleton_method(:application) { application }
47+
end
48+
49+
stub_const("Rails", rails_module)
50+
end
51+
52+
def load_railtie
53+
# Load the railtie file fresh
54+
load File.expand_path("../../../../lib/secure_headers/railtie.rb", __FILE__)
55+
end
56+
57+
def run_action_controller_initializer
58+
initializer_blocks["secure_headers.action_controller"]&.call
59+
end
60+
61+
describe "case-insensitive header removal" do
62+
context "with Rails default headers (capitalized format like 'X-Frame-Options')" do
63+
let(:default_headers) do
64+
{
65+
"X-Frame-Options" => "SAMEORIGIN",
66+
"X-XSS-Protection" => "0",
67+
"X-Content-Type-Options" => "nosniff",
68+
"X-Permitted-Cross-Domain-Policies" => "none",
69+
"X-Download-Options" => "noopen",
70+
"Referrer-Policy" => "strict-origin-when-cross-origin"
71+
}
72+
end
73+
74+
it "removes capitalized conflicting headers from Rails defaults" do
75+
load_railtie
76+
run_action_controller_initializer
77+
78+
expect(default_headers).to be_empty
79+
end
80+
end
81+
82+
context "with lowercase headers (Rack 3+ format)" do
83+
let(:default_headers) do
84+
{
85+
"x-frame-options" => "SAMEORIGIN",
86+
"x-xss-protection" => "0",
87+
"x-content-type-options" => "nosniff"
88+
}
89+
end
90+
91+
it "removes lowercase conflicting headers from Rails defaults" do
92+
load_railtie
93+
run_action_controller_initializer
94+
95+
expect(default_headers).to be_empty
96+
end
97+
end
98+
99+
context "with mixed-case headers" do
100+
let(:default_headers) do
101+
{
102+
"X-FRAME-OPTIONS" => "SAMEORIGIN",
103+
"x-Xss-Protection" => "0",
104+
"X-Content-Type-OPTIONS" => "nosniff"
105+
}
106+
end
107+
108+
it "removes mixed-case conflicting headers from Rails defaults" do
109+
load_railtie
110+
run_action_controller_initializer
111+
112+
expect(default_headers).to be_empty
113+
end
114+
end
115+
116+
context "preserving non-conflicting headers" do
117+
let(:default_headers) do
118+
{
119+
"X-Frame-Options" => "SAMEORIGIN",
120+
"X-Custom-Header" => "custom-value",
121+
"My-Application-Header" => "app-value"
122+
}
123+
end
124+
125+
it "removes only conflicting headers and preserves custom headers" do
126+
load_railtie
127+
run_action_controller_initializer
128+
129+
expect(default_headers).to eq({
130+
"X-Custom-Header" => "custom-value",
131+
"My-Application-Header" => "app-value"
132+
})
133+
end
134+
end
135+
136+
context "with nil default_headers" do
137+
let(:default_headers) { nil }
138+
139+
it "handles nil default_headers gracefully" do
140+
load_railtie
141+
142+
expect { run_action_controller_initializer }.not_to raise_error
143+
end
144+
end
145+
146+
context "CSP and HSTS headers" do
147+
let(:default_headers) do
148+
{
149+
"Content-Security-Policy" => "default-src 'self'",
150+
"Content-Security-Policy-Report-Only" => "default-src 'self'",
151+
"Strict-Transport-Security" => "max-age=31536000"
152+
}
153+
end
154+
155+
it "removes CSP and HSTS headers regardless of case" do
156+
load_railtie
157+
run_action_controller_initializer
158+
159+
expect(default_headers).to be_empty
160+
end
161+
end
162+
end
163+
end

0 commit comments

Comments
 (0)