rack-cookie-monster 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +61 -0
- data/features/cookie_monster.feature +50 -0
- data/features/monster.ru +49 -0
- data/features/step_definitions/all_steps.rb +23 -0
- data/features/step_definitions/common_celerity.rb +163 -0
- data/features/support/celerity_startup.rb +57 -0
- data/lib/rack/cookie_monster.rb +104 -0
- data/spec/rack/cookie_monster_spec.rb +131 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/stub_helpers.rb +164 -0
- metadata +72 -0
data/README.rdoc
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
= Rack Cookie Monster
|
2
|
+
|
3
|
+
A rack middleware library that allows for cookies to be passed through form parameters. Specifically, it merges the specified
|
4
|
+
form parameters into the Cookie header of an http request. It gets around the problem of having a flash application which interacts with a web application that uses cookie based sessions.
|
5
|
+
|
6
|
+
= Contributing
|
7
|
+
The environment can be configured by using bundler.
|
8
|
+
|
9
|
+
gem bundle or rake setup:contrib
|
10
|
+
|
11
|
+
= Usage
|
12
|
+
|
13
|
+
<b>Rails Example:</b>
|
14
|
+
|
15
|
+
# In rails initializer
|
16
|
+
Rack::CookieMonster.configure do |c|
|
17
|
+
c.eat :_session_id
|
18
|
+
c.eat :user_credentials
|
19
|
+
c.share_with /^(Adobe|Shockwave) Flash/
|
20
|
+
c.share_with "Burt"
|
21
|
+
end
|
22
|
+
|
23
|
+
Rack::CookieMonster.configure_for_rails
|
24
|
+
|
25
|
+
<b>Rack Example:</b>
|
26
|
+
|
27
|
+
class CookieMonsterApplication
|
28
|
+
def call(env)
|
29
|
+
cookies = ::Rack::Request.new(env).cookies
|
30
|
+
res = ::Rack::Response.new
|
31
|
+
res.write %{
|
32
|
+
<html>
|
33
|
+
<head>
|
34
|
+
<title>Form</title>
|
35
|
+
</head>
|
36
|
+
<body>
|
37
|
+
<form action="/" method="post">
|
38
|
+
Cookie 1: <input type="text" name="cookie_1" />
|
39
|
+
Cookie 2: <input type="text" name="cookie_2" />
|
40
|
+
Non Cookie: <input type="text" name="non_cookie" />
|
41
|
+
<input type="submit" />
|
42
|
+
</form>
|
43
|
+
</body>
|
44
|
+
#{
|
45
|
+
cookies.map do |k,v|
|
46
|
+
"<p>#{k} - #{v}</p>"
|
47
|
+
end.join("\n")
|
48
|
+
}
|
49
|
+
</html>
|
50
|
+
}
|
51
|
+
res.finish
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
Rack::CookieMonster.configure do |c|
|
56
|
+
c.eat :cookie_1
|
57
|
+
c.eat :cookie_2
|
58
|
+
end
|
59
|
+
|
60
|
+
use Rack::CookieMonster
|
61
|
+
run CookieMonsterApplication.new
|
@@ -0,0 +1,50 @@
|
|
1
|
+
Feature: Eats Cookies
|
2
|
+
|
3
|
+
Scenario: Browser with user agent that cookie monster shares with
|
4
|
+
Given I go to "/"
|
5
|
+
And I fill in "Cookie 1" for the text field named "cookie_1"
|
6
|
+
And I fill in "Cookie 2" for the text field named "cookie_2"
|
7
|
+
And I fill in "Noncookie" for the text field named "non_cookie"
|
8
|
+
When I press "Submit"
|
9
|
+
Then I should see "cookies" for
|
10
|
+
| name | value |
|
11
|
+
| cookie_1 | Cookie 1 |
|
12
|
+
| cookie_2 | Cookie 2 |
|
13
|
+
But I should not see "cookies" for
|
14
|
+
| name | value |
|
15
|
+
| non_cookie | Noncookie |
|
16
|
+
But I should see "params" for
|
17
|
+
| name | value |
|
18
|
+
| non_cookie | Noncookie |
|
19
|
+
|
20
|
+
Scenario: User agent does not match cookie monster's share list
|
21
|
+
Given I change my browser to "ie"
|
22
|
+
When I go to "/"
|
23
|
+
And I fill in "Cookie 1" for the text field named "cookie_1"
|
24
|
+
And I fill in "Cookie 2" for the text field named "cookie_2"
|
25
|
+
And I fill in "Noncookie" for the text field named "non_cookie"
|
26
|
+
When I press "Submit"
|
27
|
+
Then I should not see "cookies" for
|
28
|
+
| name | value |
|
29
|
+
| cookie_1 | Cookie 1 |
|
30
|
+
| cookie_2 | Cookie 2 |
|
31
|
+
But I should see "params" for
|
32
|
+
| name | value |
|
33
|
+
| non_cookie | Noncookie |
|
34
|
+
| cookie_1 | Cookie 1 |
|
35
|
+
| cookie_2 | Cookie 2 |
|
36
|
+
|
37
|
+
Scenario: Browser that has existing cookies for domain
|
38
|
+
Given My browser has the following cookies
|
39
|
+
| name | value |
|
40
|
+
| session | secrets |
|
41
|
+
| creds | street |
|
42
|
+
When I go to "/"
|
43
|
+
And I fill in "Cookie 1" for the text field named "cookie_1"
|
44
|
+
And I press "Submit"
|
45
|
+
Then I should see "cookies" for
|
46
|
+
| name | value |
|
47
|
+
| cookie_1 | Cookie 1 |
|
48
|
+
| session | secrets |
|
49
|
+
| creds | street |
|
50
|
+
|
data/features/monster.ru
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
class CookieMonsterApplication
|
2
|
+
def call(env)
|
3
|
+
request = ::Rack::Request.new(env)
|
4
|
+
cookies = request.cookies
|
5
|
+
params = request.params
|
6
|
+
|
7
|
+
res = ::Rack::Response.new
|
8
|
+
res.write %{
|
9
|
+
<html>
|
10
|
+
<head>
|
11
|
+
<title>Form</title>
|
12
|
+
</head>
|
13
|
+
<body>
|
14
|
+
<p>#{env.inspect}</p>
|
15
|
+
<form action="/" method="post">
|
16
|
+
Cookie 1: <input type="text" name="cookie_1" />
|
17
|
+
Cookie 2: <input type="text" name="cookie_2" />
|
18
|
+
Non Cookie: <input type="text" name="non_cookie" />
|
19
|
+
<input type="submit" value="Submit" />
|
20
|
+
</form>
|
21
|
+
<div id="cookies">
|
22
|
+
#{
|
23
|
+
cookies.map do |k,v|
|
24
|
+
"<p>#{k} - #{v}</p>"
|
25
|
+
end.join("\n")
|
26
|
+
}
|
27
|
+
</div>
|
28
|
+
<div id="params">
|
29
|
+
#{
|
30
|
+
params.map do |k,v|
|
31
|
+
"<p>#{k} - #{v}</p>"
|
32
|
+
end.join("\n")
|
33
|
+
}
|
34
|
+
</div>
|
35
|
+
</body>
|
36
|
+
</html>
|
37
|
+
}
|
38
|
+
res.finish
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
Rack::CookieMonster.configure do |c|
|
43
|
+
c.eat :cookie_1
|
44
|
+
c.eat :cookie_2
|
45
|
+
c.share_with /firefox/i
|
46
|
+
end
|
47
|
+
|
48
|
+
use Rack::CookieMonster
|
49
|
+
run CookieMonsterApplication.new
|
@@ -0,0 +1,23 @@
|
|
1
|
+
And %r{I change my browser to "(\w+)"} do |browser|
|
2
|
+
$browser = Culerity::RemoteBrowserProxy.new $server, {:browser => browser}
|
3
|
+
$browser.extend BrowserExtensions
|
4
|
+
end
|
5
|
+
|
6
|
+
And %r{^I should( not)? see "([^"]+)" for$} do |not_see, container_id, table|
|
7
|
+
table.hashes.each do |h|
|
8
|
+
container = $browser.div(:id, container_id)
|
9
|
+
if not_see
|
10
|
+
lambda {
|
11
|
+
container.p(:text, "#{h['name']} - #{h['value']}").text
|
12
|
+
}.should raise_error(/unable to locate/i)
|
13
|
+
else
|
14
|
+
container.p(:text, "#{h['name']} - #{h['value']}").text.should_not be_nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Given "My browser has the following cookies" do |table|
|
20
|
+
table.hashes.each do |h|
|
21
|
+
$browser.add_cookie("localhost", h["name"], h["value"])
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module BrowserExtensions
|
2
|
+
def current_html
|
3
|
+
div(:xpath => "/.").html
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
Then %r{^I eval:?(.*)} do |code|
|
8
|
+
eval code
|
9
|
+
end
|
10
|
+
|
11
|
+
When %r{^I (am curious|debug)} do |what|
|
12
|
+
require 'ruby-debug'
|
13
|
+
debugger
|
14
|
+
end
|
15
|
+
|
16
|
+
And "I pass out" do
|
17
|
+
sleep
|
18
|
+
end
|
19
|
+
|
20
|
+
Then /I should be redirected to "(.*)"/ do |url|
|
21
|
+
$browser.url.should == url
|
22
|
+
end
|
23
|
+
|
24
|
+
When /I press "(.*)"/ do |button|
|
25
|
+
$browser.button(:text, button).click
|
26
|
+
assert_successful_response
|
27
|
+
end
|
28
|
+
|
29
|
+
When /I follow "(.*)"/ do |link|
|
30
|
+
$browser.link(:text, /#{link}/).click
|
31
|
+
assert_successful_response
|
32
|
+
end
|
33
|
+
|
34
|
+
When %r{^I browse to "([^"]+)"$} do |url|
|
35
|
+
$browser.goto url
|
36
|
+
end
|
37
|
+
|
38
|
+
When %r{^I refresh$} do
|
39
|
+
$browser.refresh
|
40
|
+
end
|
41
|
+
|
42
|
+
When /I fill in "(.*)" for "(.*)"/ do |value, field|
|
43
|
+
$browser.text_field(:id, find_label(field).for).set(value)
|
44
|
+
end
|
45
|
+
|
46
|
+
And %r{^I select file "([^"]*)" for "([^"]*)"$} do |file, field|
|
47
|
+
$browser.file_field(:id, find_label(field).for).set(test_file(file))
|
48
|
+
end
|
49
|
+
|
50
|
+
When %r{^I fill in "([^"]*)" for the text field named "([^"]*)"$} do |value, field|
|
51
|
+
$browser.text_field(:name, field).set(value)
|
52
|
+
end
|
53
|
+
|
54
|
+
When "I go back" do
|
55
|
+
$browser.back
|
56
|
+
end
|
57
|
+
|
58
|
+
When /I check "(.*)"/ do |field|
|
59
|
+
$browser.check_box(:id, find_label(field).for).set(true)
|
60
|
+
end
|
61
|
+
|
62
|
+
When /^I uncheck "(.*)"$/ do |field|
|
63
|
+
$browser.check_box(:id, find_label(field).for).set(false)
|
64
|
+
end
|
65
|
+
|
66
|
+
When /I select "(.*)" from "(.*)"/ do |value, field|
|
67
|
+
$browser.select_list(:id, find_label(field).for).select value
|
68
|
+
end
|
69
|
+
|
70
|
+
When /I select "(.*)" from select field "(.*)"/ do |value, field|
|
71
|
+
$browser.select_list(:name => field).select value
|
72
|
+
end
|
73
|
+
|
74
|
+
When /I choose "(.*)"/ do |field|
|
75
|
+
$browser.radio(:id, find_label(field).for).set(true)
|
76
|
+
end
|
77
|
+
|
78
|
+
When /I go to "(.+)"/ do |path|
|
79
|
+
$browser.goto @host + path
|
80
|
+
assert_successful_response
|
81
|
+
end
|
82
|
+
|
83
|
+
When /I wait for the (AJAX|restful).*/ do |what|
|
84
|
+
$browser.wait
|
85
|
+
end
|
86
|
+
|
87
|
+
When %r{I wait (\d+) seconds} do |seconds|
|
88
|
+
sleep seconds.to_i
|
89
|
+
end
|
90
|
+
|
91
|
+
Then %r{^I should see "([^"]+)"$} do |text|
|
92
|
+
$browser.current_html.should match(text)
|
93
|
+
end
|
94
|
+
|
95
|
+
When /I puts/ do ||
|
96
|
+
puts $browser.current_html
|
97
|
+
end
|
98
|
+
|
99
|
+
Then %r{^I should not see "([^"]+)"$} do |text|
|
100
|
+
$browser.current_html.should_not match(text)
|
101
|
+
end
|
102
|
+
|
103
|
+
Then %r{^I change "([^"]+)" to "([^"]+)"$} do |field, value|
|
104
|
+
$browser.text_field(:id, find_label(field).for).set(value)
|
105
|
+
end
|
106
|
+
|
107
|
+
Then %r{^I should see "([^\"]*)" with value "([^\"]*)"$} do |field_selector, value|
|
108
|
+
doc = Nokogiri.HTML($browser.current_html)
|
109
|
+
node = doc.at("//*[@id = //label[contains(.,'#{field_selector}')]/@for]")
|
110
|
+
node ||= doc.at("##{field_selector}")
|
111
|
+
node ||= doc.at("*[@name = '#{field_selector}']")
|
112
|
+
|
113
|
+
case node.name
|
114
|
+
when "input"
|
115
|
+
node["value"].should include(value)
|
116
|
+
when 'textarea'
|
117
|
+
node.text.should include(value)
|
118
|
+
else
|
119
|
+
raise "Bad field type: don't know how to check value of a node of type #{node.name}. You should add support!"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
Then %r{^I should see the radio buttons with these settings$} do |table|
|
124
|
+
table.raw.each do |label, state|
|
125
|
+
radio = $browser.radio(:id, find_label(label).for)
|
126
|
+
case state
|
127
|
+
when "checked"
|
128
|
+
radio.should be_checked
|
129
|
+
when "unchecked"
|
130
|
+
radio.should_not be_checked
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
Then %r{^I should see the "([^"]+)" form$} do |form|
|
136
|
+
$browser.current_html.should have_selector("form##{form}_form")
|
137
|
+
end
|
138
|
+
|
139
|
+
When %r{^I browse$} do ||
|
140
|
+
page = $browser.current_html
|
141
|
+
puts page
|
142
|
+
end
|
143
|
+
|
144
|
+
def find_label(text)
|
145
|
+
$browser.label :text, text
|
146
|
+
end
|
147
|
+
|
148
|
+
def assert_successful_response(allow_bad_response=false)
|
149
|
+
status = $browser.page.web_response.status_code
|
150
|
+
if(status == 302 || status == 301)
|
151
|
+
location = $browser.page.web_response.get_response_header_value('Location')
|
152
|
+
puts "Being redirected to #{location}"
|
153
|
+
$browser.goto location
|
154
|
+
assert_successful_response
|
155
|
+
elsif status != 200 and !allow_bad_response
|
156
|
+
raise "Browser returned Response Code #{$browser.page.web_response.status_code}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def fill_in_textfield(field, value)
|
161
|
+
$browser.text_field(:id, find_label(field).for).set(value)
|
162
|
+
end
|
163
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'culerity'
|
2
|
+
|
3
|
+
SERVER_PID = "server.pid"
|
4
|
+
|
5
|
+
$port = 9292
|
6
|
+
$host = "http://localhost:#{$port}"
|
7
|
+
|
8
|
+
Before do
|
9
|
+
$browser = Culerity::RemoteBrowserProxy.new $server, {:browser => :firefox}
|
10
|
+
module BrowserExtensions; end
|
11
|
+
$browser.extend BrowserExtensions
|
12
|
+
@host = $host
|
13
|
+
@port = $port
|
14
|
+
end
|
15
|
+
|
16
|
+
def kill_test_server
|
17
|
+
if File.exist?(SERVER_PID)
|
18
|
+
test_server_pid = File.read(SERVER_PID).to_i
|
19
|
+
puts "Killing #{test_server_pid}"
|
20
|
+
Process.kill "KILL", test_server_pid
|
21
|
+
File.delete SERVER_PID
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def host_listening_on_port?(host, port, timeout=0)
|
26
|
+
start_time = Time.now
|
27
|
+
begin
|
28
|
+
socket = TCPSocket.open(host, port)
|
29
|
+
socket.close
|
30
|
+
return true
|
31
|
+
rescue Errno::ECONNREFUSED
|
32
|
+
if Time.now - start_time < timeout
|
33
|
+
# try to connect again
|
34
|
+
sleep 0.25
|
35
|
+
retry
|
36
|
+
else
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
if host_listening_on_port? "localhost", $port
|
44
|
+
puts "Server already running on test port. Killing it..."
|
45
|
+
kill_test_server
|
46
|
+
end
|
47
|
+
|
48
|
+
%x(bin/rackup --require lib/rack/cookie_monster --require vendor/gems/environment --daemon --pid #{SERVER_PID} --server webrick features/monster.ru)
|
49
|
+
raise "Couldn't start server" unless host_listening_on_port? "localhost", $port, 10
|
50
|
+
|
51
|
+
$server ||= Culerity::run_server
|
52
|
+
|
53
|
+
at_exit do
|
54
|
+
kill_test_server
|
55
|
+
$browser.exit
|
56
|
+
$server.close
|
57
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Rack
|
2
|
+
class CookieMonster
|
3
|
+
|
4
|
+
class Hungry < StandardError; end
|
5
|
+
|
6
|
+
class<<self
|
7
|
+
def configure
|
8
|
+
@config ||= CookieMonsterConfig.new
|
9
|
+
yield(@config)
|
10
|
+
@configured = true
|
11
|
+
end
|
12
|
+
|
13
|
+
def snackers
|
14
|
+
ensure_monster_configured!
|
15
|
+
@config.snackers
|
16
|
+
end
|
17
|
+
|
18
|
+
def cookies
|
19
|
+
ensure_monster_configured!
|
20
|
+
@config.cookies
|
21
|
+
end
|
22
|
+
|
23
|
+
def configure_for_rails
|
24
|
+
ensure_monster_configured!
|
25
|
+
ActionController::Dispatcher.middleware.insert_before(
|
26
|
+
ActionController::Base.session_store,
|
27
|
+
self
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def ensure_monster_configured!
|
34
|
+
raise Hungry.new("Cookie Monster has not been configured") unless @configured
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(app)
|
40
|
+
@app = app
|
41
|
+
end
|
42
|
+
|
43
|
+
def call(env)
|
44
|
+
shares_with(env["HTTP_USER_AGENT"]) do
|
45
|
+
request = ::Rack::Request.new(env)
|
46
|
+
eat_cookies!(env, request)
|
47
|
+
end
|
48
|
+
@app.call(env)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def shares_with(agent)
|
54
|
+
yield if self.class.snackers.empty?
|
55
|
+
|
56
|
+
any_matches = self.class.snackers.any? do |snacker|
|
57
|
+
case snacker
|
58
|
+
when String
|
59
|
+
snacker == agent
|
60
|
+
when Regexp
|
61
|
+
snacker.match(agent) != nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
yield if any_matches
|
66
|
+
end
|
67
|
+
|
68
|
+
def eat_cookies!(env, request)
|
69
|
+
cookies = request.cookies
|
70
|
+
new_cookies = {}
|
71
|
+
|
72
|
+
self.class.cookies.each do |cookie_name|
|
73
|
+
value = request.params[cookie_name.to_s]
|
74
|
+
if value
|
75
|
+
cookies.delete(cookie_name.to_s)
|
76
|
+
new_cookies[cookie_name.to_s] = value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
new_cookies.merge!(cookies)
|
81
|
+
env["HTTP_COOKIE"] = new_cookies.map do |k,v|
|
82
|
+
"#{k}=#{v}"
|
83
|
+
end.compact.join("; ").freeze
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
class CookieMonsterConfig
|
89
|
+
attr_reader :cookies, :snackers
|
90
|
+
|
91
|
+
def initialize
|
92
|
+
@cookies = []
|
93
|
+
@snackers = []
|
94
|
+
end
|
95
|
+
|
96
|
+
def eat(cookie)
|
97
|
+
@cookies << cookie.to_sym
|
98
|
+
end
|
99
|
+
|
100
|
+
def share_with(snacker)
|
101
|
+
@snackers << snacker
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rack/cookie_monster'
|
3
|
+
require 'action_controller'
|
4
|
+
|
5
|
+
describe Rack::CookieMonster do
|
6
|
+
subject { Class.new(described_class) }
|
7
|
+
|
8
|
+
describe ".configure" do
|
9
|
+
|
10
|
+
it "specifies what cookies are to be eaten" do
|
11
|
+
subject.configure do |c|
|
12
|
+
c.eat :_session_id
|
13
|
+
c.eat :user_credentials
|
14
|
+
end
|
15
|
+
|
16
|
+
subject.cookies.should == [:_session_id, :user_credentials]
|
17
|
+
end
|
18
|
+
|
19
|
+
it "turns the cookie keys into symbols" do
|
20
|
+
subject.configure do |c|
|
21
|
+
c.eat "burt"
|
22
|
+
end
|
23
|
+
|
24
|
+
subject.cookies.should == [:burt]
|
25
|
+
end
|
26
|
+
|
27
|
+
it "specifies what user agents can snack" do
|
28
|
+
subject.configure do |c|
|
29
|
+
c.share_with "MSIE 6.0"
|
30
|
+
c.share_with /safari \d+/
|
31
|
+
end
|
32
|
+
|
33
|
+
subject.snackers.should == ["MSIE 6.0", /safari \d+/]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "raises an error if it is not configured" do
|
37
|
+
lambda { subject.cookies }.should raise_error(Rack::CookieMonster::Hungry, /not been configured/i)
|
38
|
+
lambda { subject.snackers }.should raise_error(Rack::CookieMonster::Hungry, /not been configured/i)
|
39
|
+
lambda { subject.configure_for_rails }.should raise_error(Rack::CookieMonster::Hungry, /not been configured/i)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe ".configure_for_rails" do
|
44
|
+
it "injects itself into the rails request stack" do
|
45
|
+
subject.configure {}
|
46
|
+
subject.configure_for_rails
|
47
|
+
ActionController::Dispatcher.middleware[2].should == subject
|
48
|
+
ActionController::Dispatcher.middleware[3].should == ActionController::Session::CookieStore
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#call" do
|
53
|
+
before do
|
54
|
+
@target = subject.new(stub_instance(:app))
|
55
|
+
@environment = {
|
56
|
+
"HTTP_COOKIE" => "",
|
57
|
+
"QUERY_STRING" => "oatmeal_cookie=delicious&chocolate_cookie=yummy&burt=ernie&oats=honey",
|
58
|
+
"REQUEST_METHOD" => "PUT"
|
59
|
+
}
|
60
|
+
|
61
|
+
subject.configure do |c|
|
62
|
+
c.eat :oatmeal_cookie
|
63
|
+
c.eat :chocolate_cookie
|
64
|
+
end
|
65
|
+
|
66
|
+
@app.stubs(:call)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "builds cookie string from environment params" do
|
70
|
+
@app.expects(:call).with do |env|
|
71
|
+
env["HTTP_COOKIE"].should == "chocolate_cookie=yummy; oatmeal_cookie=delicious"
|
72
|
+
env["HTTP_COOKIE"].should be_frozen
|
73
|
+
end
|
74
|
+
|
75
|
+
@target.call(@environment)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "will play nice with existing cookies" do
|
79
|
+
@environment["HTTP_COOKIE"] = "oatmeal_cookie=gross; peanutbutter_cookie=good"
|
80
|
+
|
81
|
+
@app.expects(:call).with do |env|
|
82
|
+
env["HTTP_COOKIE"].should == "peanutbutter_cookie=good; chocolate_cookie=yummy; oatmeal_cookie=delicious"
|
83
|
+
end
|
84
|
+
|
85
|
+
@target.call(@environment)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "will not append an empty cookie" do
|
89
|
+
@environment["QUERY_STRING"] = ""
|
90
|
+
@app.expects(:call).with do |env|
|
91
|
+
env["HTTP_COOKIE"].should == ""
|
92
|
+
env["HTTP_COOKIE"].should be_frozen
|
93
|
+
end
|
94
|
+
|
95
|
+
@target.call(@environment)
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "optional user agents" do
|
99
|
+
before do
|
100
|
+
subject.configure do |c|
|
101
|
+
c.share_with /^(Adobe|Shockwave) Flash/
|
102
|
+
c.share_with "MSIE 6.0"
|
103
|
+
end
|
104
|
+
@cookies = "chocolate_cookie=yummy; oatmeal_cookie=delicious"
|
105
|
+
end
|
106
|
+
|
107
|
+
it "will match against regular expressions" do
|
108
|
+
|
109
|
+
@app.expects(:call).with(has_entry("HTTP_COOKIE", @cookies)).twice
|
110
|
+
@app.expects(:call).with(has_entry("HTTP_COOKIE", "")).once
|
111
|
+
|
112
|
+
@environment["HTTP_USER_AGENT"] = "Shockwave Flash"
|
113
|
+
@target.call(@environment.dup)
|
114
|
+
@environment["HTTP_USER_AGENT"] = "Adobe Flash"
|
115
|
+
@target.call(@environment.dup)
|
116
|
+
@environment["HTTP_USER_AGENT"] = "Flash"
|
117
|
+
@target.call(@environment.dup)
|
118
|
+
end
|
119
|
+
|
120
|
+
it "will exact match against strings" do
|
121
|
+
@app.expects(:call).with(has_entry("HTTP_COOKIE", @cookies)).once
|
122
|
+
@app.expects(:call).with(has_entry("HTTP_COOKIE", "")).once
|
123
|
+
|
124
|
+
@environment["HTTP_USER_AGENT"] = "MSIE 6.0"
|
125
|
+
@target.call(@environment.dup)
|
126
|
+
@environment["HTTP_USER_AGENT"] = "MSIE"
|
127
|
+
@target.call(@environment.dup)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
here = File.expand_path(File.dirname(__FILE__))
|
2
|
+
require File.join(here, "..", "vendor/gems/environment")
|
3
|
+
require File.join(here, "..", "init")
|
4
|
+
|
5
|
+
require 'spec/autorun'
|
6
|
+
require 'mocha'
|
7
|
+
require 'active_support'
|
8
|
+
require 'rack'
|
9
|
+
|
10
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
11
|
+
|
12
|
+
Spec::Runner.configure do |config|
|
13
|
+
config.include(SpecInstanceHelpers)
|
14
|
+
config.mock_with :mocha
|
15
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module SpecInstanceHelpers
|
2
|
+
def stub_instance_hash(*instance_names)
|
3
|
+
hash = {}
|
4
|
+
instance_names.each do |instance_name|
|
5
|
+
hash[instance_name] = stub_instance(instance_name)
|
6
|
+
end
|
7
|
+
hash
|
8
|
+
end
|
9
|
+
|
10
|
+
def stub_string_hash(*instance_names)
|
11
|
+
hash = {}
|
12
|
+
instance_names.each do |instance_name|
|
13
|
+
hash[instance_name] = stub_string(instance_name)
|
14
|
+
end
|
15
|
+
hash
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_array_of_stubs(instance, count, stubbings)
|
19
|
+
stub_array = stub_array_instance(instance, count, instance.to_s.singularize)
|
20
|
+
stub_array.each do |stub|
|
21
|
+
stubbings.each do |key, value_lambda|
|
22
|
+
stub.stub!(key).and_return(value_lambda.call)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def stub_instance(var_name, options={})
|
28
|
+
var_name = var_name.to_s.gsub(/[\?!]/, "")
|
29
|
+
the_stub = stub(var_name.to_s.humanize, options)
|
30
|
+
instance_variable_set("@#{var_name}", the_stub)
|
31
|
+
the_stub
|
32
|
+
end
|
33
|
+
|
34
|
+
def stub_array_instance(ivar_sym, count, label=nil)
|
35
|
+
label ||= ivar_sym.to_s.singularize
|
36
|
+
array = stub_array(count, label)
|
37
|
+
instance_variable_set("@#{ivar_sym}", array)
|
38
|
+
array
|
39
|
+
end
|
40
|
+
|
41
|
+
def stub_array(count, label)
|
42
|
+
array = []
|
43
|
+
1.upto(count) do |i|
|
44
|
+
array << stub("#{label} #{i}")
|
45
|
+
end
|
46
|
+
array
|
47
|
+
end
|
48
|
+
|
49
|
+
def stub_instances(*var_names)
|
50
|
+
var_names.map do |var_name|
|
51
|
+
stub_instance var_name
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def stub_params(*param_names)
|
56
|
+
@params = {}
|
57
|
+
param_names.each do |param_name|
|
58
|
+
instance_variable_set("@#{param_name}", "param #{param_name}")
|
59
|
+
@params[param_name.to_s] = "param #{param_name}"
|
60
|
+
end
|
61
|
+
@params
|
62
|
+
end
|
63
|
+
|
64
|
+
def stub_partial(partial, options={})
|
65
|
+
options = _prepare_stub_partial_options(partial, options)
|
66
|
+
template.stub!(:render).with(options).and_return(%|<p id="#{partial.gsub(/\//,'_')}_partial"></p>|)
|
67
|
+
end
|
68
|
+
|
69
|
+
def stub_partials(*partials)
|
70
|
+
partials.each &method(:stub_partial)
|
71
|
+
end
|
72
|
+
|
73
|
+
def stub_string(sym)
|
74
|
+
the_stub = sym.to_s.humanize
|
75
|
+
instance_variable_set("@#{sym}", the_stub)
|
76
|
+
the_stub
|
77
|
+
end
|
78
|
+
|
79
|
+
def stub_strings(*syms)
|
80
|
+
syms.each &method(:stub_string)
|
81
|
+
end
|
82
|
+
|
83
|
+
def stub_for_view(name, field_names)
|
84
|
+
the_stub = stub_instance(name)
|
85
|
+
_stub_methods_for_view(the_stub, field_names)
|
86
|
+
the_stub
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
def expect_and_render_partial(partial,options={})
|
92
|
+
options = _prepare_stub_partial_options(partial, options)
|
93
|
+
template.should_receive(:render).with(options).and_return(%|<p id="#{partial.gsub(/\//,'_')}_partial"></p>|)
|
94
|
+
end
|
95
|
+
|
96
|
+
def _stub_methods_for_view(the_stub, field_names)
|
97
|
+
field_names.each do |fname|
|
98
|
+
case fname
|
99
|
+
when String, Symbol
|
100
|
+
field_name = fname.to_sym
|
101
|
+
field_value = block_given? ? yield(fname) : "The #{fname.to_s.humanize}"
|
102
|
+
the_stub.stub!(field_name).and_return(field_value)
|
103
|
+
when Hash
|
104
|
+
fname.each do |k,v|
|
105
|
+
the_stub.stub!(k.to_sym).and_return(v)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def _prepare_stub_partial_options(partial, options)
|
112
|
+
options.reverse_merge!(:partial => partial)
|
113
|
+
if options[:locals] == false
|
114
|
+
options.delete(:locals)
|
115
|
+
else
|
116
|
+
options.reverse_merge!(:locals => anything)
|
117
|
+
end
|
118
|
+
options
|
119
|
+
end
|
120
|
+
|
121
|
+
def stub_array_for_view(count, name_base, field_names)
|
122
|
+
the_array = []
|
123
|
+
count.times do |i|
|
124
|
+
i = i + 1 # one-based numbering
|
125
|
+
name = "#{name_base.to_s.singularize}#{i}"
|
126
|
+
the_stub = stub_instance(name)
|
127
|
+
_stub_methods_for_view(the_stub, field_names) do |fname|
|
128
|
+
"The #{fname.to_s.humanize} #{i}"
|
129
|
+
end
|
130
|
+
the_array << the_stub
|
131
|
+
end
|
132
|
+
instance_variable_set("@#{name_base}", the_array)
|
133
|
+
the_array
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
class NilClass
|
140
|
+
# Mocha:
|
141
|
+
|
142
|
+
def expects(*args)
|
143
|
+
raise "Don't expects(#{args.inspect}) on nil"
|
144
|
+
end
|
145
|
+
|
146
|
+
def stubs(*args)
|
147
|
+
raise "Don't stubs(#{args.inspect}) on nil"
|
148
|
+
end
|
149
|
+
|
150
|
+
# RSpec mocks:
|
151
|
+
#
|
152
|
+
def stub!(*args)
|
153
|
+
raise "Don't stub! on nil."
|
154
|
+
end
|
155
|
+
|
156
|
+
def should_receive(*args)
|
157
|
+
raise "Don't should_receive on nil."
|
158
|
+
end
|
159
|
+
|
160
|
+
def should_not_receive(*args)
|
161
|
+
raise "Don't should_not_receive on nil."
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-cookie-monster
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Justin DeWind
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-15 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rack
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.0.0
|
24
|
+
version:
|
25
|
+
description: A rack middleware library that allows cookies to be passed through forms
|
26
|
+
email: dewind@atomicobject.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.rdoc
|
33
|
+
files:
|
34
|
+
- lib/rack/cookie_monster.rb
|
35
|
+
- README.rdoc
|
36
|
+
has_rdoc: true
|
37
|
+
homepage: http://github.com/dewind/rack-cookie-monster
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options:
|
42
|
+
- --charset=UTF-8
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.3.5
|
61
|
+
signing_key:
|
62
|
+
specification_version: 3
|
63
|
+
summary: A rack middleware library that allows cookies to be passed through forms
|
64
|
+
test_files:
|
65
|
+
- spec/rack/cookie_monster_spec.rb
|
66
|
+
- spec/spec_helper.rb
|
67
|
+
- spec/support/stub_helpers.rb
|
68
|
+
- features/cookie_monster.feature
|
69
|
+
- features/monster.ru
|
70
|
+
- features/step_definitions/all_steps.rb
|
71
|
+
- features/step_definitions/common_celerity.rb
|
72
|
+
- features/support/celerity_startup.rb
|