Skip to content

Commit f47103c

Browse files
mclsandrew
authored andcommitted
CookieAdapter: Fix cookies when @context is an ActionController (#526)
* CookieAdapter: Fix cookies when @context is an ActionController Using ab_test from a controller was failing because "cookies" is a private method of the controller instance which is assigned to @context. It looks like this was broken in the commit that fixed the multiple "Set-Cookie" headers bug: 3523e1c Using `.send(:cookies)` again when using Rails was the easiest way I found to fix this. Note: I was able to add an integration test for the case when it's called from the controller, but I failed to a regression test that uses and ActionView context object. * Don't use to_h on CookieJar, it doesn't work on older rubies
1 parent 83a4a38 commit f47103c

File tree

2 files changed

+90
-43
lines changed

2 files changed

+90
-43
lines changed

lib/split/persistence/cookie_adapter.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ def set_cookie(value = {})
3434
cookie_key = :split.to_s
3535
cookie_value = default_options.merge(value: JSON.generate(value))
3636
if action_dispatch?
37-
@context.cookies[cookie_key] = cookie_value
37+
# The "send" is necessary when we call ab_test from the controller
38+
# and thus @context is a rails controller, because then "cookies" is
39+
# a private method.
40+
@context.send(:cookies)[cookie_key] = cookie_value
3841
else
3942
set_cookie_via_rack(cookie_key, cookie_value)
4043
end

spec/persistence/cookie_adapter_spec.rb

Lines changed: 86 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,60 +3,104 @@
33
require 'rack/test'
44

55
describe Split::Persistence::CookieAdapter do
6+
subject { described_class.new(context) }
67

7-
let(:env) { Rack::MockRequest.env_for("http://example.com:8080/") }
8-
let(:request) { Rack::Request.new(env) }
9-
let(:response) { Rack::MockResponse.new(200, {}, "") }
10-
let(:context) { double(request: request, response: response, cookies: CookiesMock.new) }
11-
subject { Split::Persistence::CookieAdapter.new(context) }
8+
shared_examples "sets cookies correctly" do
9+
describe "#[] and #[]=" do
10+
it "set and return the value for given key" do
11+
subject["my_key"] = "my_value"
12+
expect(subject["my_key"]).to eq("my_value")
13+
end
1214

13-
describe "#[] and #[]=" do
14-
it "should set and return the value for given key" do
15-
subject["my_key"] = "my_value"
16-
expect(subject["my_key"]).to eq("my_value")
15+
it "handles invalid JSON" do
16+
context.request.cookies[:split] = {
17+
:value => '{"foo":2,',
18+
:expires => Time.now
19+
}
20+
expect(subject["my_key"]).to be_nil
21+
subject["my_key"] = "my_value"
22+
expect(subject["my_key"]).to eq("my_value")
23+
end
1724
end
18-
end
1925

20-
describe "#delete" do
21-
it "should delete the given key" do
22-
subject["my_key"] = "my_value"
23-
subject.delete("my_key")
24-
expect(subject["my_key"]).to be_nil
26+
describe "#delete" do
27+
it "should delete the given key" do
28+
subject["my_key"] = "my_value"
29+
subject.delete("my_key")
30+
expect(subject["my_key"]).to be_nil
31+
end
2532
end
26-
end
2733

28-
describe "#keys" do
29-
it "should return an array of the session's stored keys" do
30-
subject["my_key"] = "my_value"
31-
subject["my_second_key"] = "my_second_value"
32-
expect(subject.keys).to match(["my_key", "my_second_key"])
34+
describe "#keys" do
35+
it "should return an array of the session's stored keys" do
36+
subject["my_key"] = "my_value"
37+
subject["my_second_key"] = "my_second_value"
38+
expect(subject.keys).to match(["my_key", "my_second_key"])
39+
end
3340
end
3441
end
3542

36-
it "handles invalid JSON" do
37-
context.request.cookies[:split] = { :value => '{"foo":2,', :expires => Time.now }
38-
expect(subject["my_key"]).to be_nil
39-
subject["my_key"] = "my_value"
40-
expect(subject["my_key"]).to eq("my_value")
41-
end
4243

43-
it "puts multiple experiments in a single cookie" do
44-
subject["foo"] = "FOO"
45-
subject["bar"] = "BAR"
46-
expect(context.response.headers["Set-Cookie"]).to match(/\Asplit=%7B%22foo%22%3A%22FOO%22%2C%22bar%22%3A%22BAR%22%7D; path=\/; expires=[a-zA-Z]{3}, \d{2} [a-zA-Z]{3} \d{4} \d{2}:\d{2}:\d{2} -0000\Z/)
47-
end
44+
context "when using Rack" do
45+
let(:env) { Rack::MockRequest.env_for("http://example.com:8080/") }
46+
let(:request) { Rack::Request.new(env) }
47+
let(:response) { Rack::MockResponse.new(200, {}, "") }
48+
let(:context) { double(request: request, response: response, cookies: CookiesMock.new) }
4849

49-
it "ensure other added cookies are not overriden" do
50-
context.response.set_cookie 'dummy', 'wow'
51-
subject["foo"] = "FOO"
52-
expect(context.response.headers["Set-Cookie"]).to include("dummy=wow")
53-
expect(context.response.headers["Set-Cookie"]).to include("split=")
54-
end
50+
include_examples "sets cookies correctly"
5551

56-
it "uses ActionDispatch::Cookie when available for cookie writing" do
57-
allow(subject).to receive(:action_dispatch?).and_return(true)
58-
subject["foo"] = "FOO"
59-
expect(subject['foo']).to eq('FOO')
52+
it "puts multiple experiments in a single cookie" do
53+
subject["foo"] = "FOO"
54+
subject["bar"] = "BAR"
55+
expect(context.response.headers["Set-Cookie"]).to match(/\Asplit=%7B%22foo%22%3A%22FOO%22%2C%22bar%22%3A%22BAR%22%7D; path=\/; expires=[a-zA-Z]{3}, \d{2} [a-zA-Z]{3} \d{4} \d{2}:\d{2}:\d{2} -0000\Z/)
56+
end
57+
58+
it "ensure other added cookies are not overriden" do
59+
context.response.set_cookie 'dummy', 'wow'
60+
subject["foo"] = "FOO"
61+
expect(context.response.headers["Set-Cookie"]).to include("dummy=wow")
62+
expect(context.response.headers["Set-Cookie"]).to include("split=")
63+
end
6064
end
6165

66+
context "when @context is an ActionController::Base" do
67+
before :context do
68+
require "rails"
69+
require "action_controller/railtie"
70+
end
71+
72+
let(:context) do
73+
controller = controller_class.new
74+
if controller.respond_to?(:set_request!)
75+
controller.set_request!(ActionDispatch::Request.new({}))
76+
else # Before rails 5.0
77+
controller.send(:"request=", ActionDispatch::Request.new({}))
78+
end
79+
80+
response = ActionDispatch::Response.new(200, {}, '').tap do |res|
81+
res.request = controller.request
82+
end
83+
84+
if controller.respond_to?(:set_response!)
85+
controller.set_response!(response)
86+
else # Before rails 5.0
87+
controller.send(:set_response!, response)
88+
end
89+
controller
90+
end
91+
92+
let(:controller_class) { Class.new(ActionController::Base) }
93+
94+
include_examples "sets cookies correctly"
95+
96+
it "puts multiple experiments in a single cookie" do
97+
subject["foo"] = "FOO"
98+
subject["bar"] = "BAR"
99+
expect(subject.keys).to eq(["foo", "bar"])
100+
expect(subject["foo"]).to eq("FOO")
101+
expect(subject["bar"]).to eq("BAR")
102+
cookie_jar = context.request.env["action_dispatch.cookies"]
103+
expect(cookie_jar['split']).to eq("{\"foo\":\"FOO\",\"bar\":\"BAR\"}")
104+
end
105+
end
62106
end

0 commit comments

Comments
 (0)