rack-cookie-monster 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|