actionpack 1.11.2 → 1.12.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- data/CHANGELOG +392 -5
- data/lib/action_controller.rb +8 -4
- data/lib/action_controller/assertions.rb +9 -10
- data/lib/action_controller/base.rb +177 -88
- data/lib/action_controller/benchmarking.rb +5 -5
- data/lib/action_controller/caching.rb +44 -36
- data/lib/action_controller/cgi_ext/cgi_methods.rb +71 -6
- data/lib/action_controller/cgi_ext/cookie_performance_fix.rb +1 -1
- data/lib/action_controller/cgi_process.rb +36 -24
- data/lib/action_controller/components.rb +152 -52
- data/lib/action_controller/dependencies.rb +1 -1
- data/lib/action_controller/deprecated_redirects.rb +2 -2
- data/lib/action_controller/deprecated_request_methods.rb +34 -0
- data/lib/action_controller/filters.rb +59 -19
- data/lib/action_controller/flash.rb +53 -47
- data/lib/action_controller/helpers.rb +2 -2
- data/lib/action_controller/integration.rb +524 -0
- data/lib/action_controller/layout.rb +58 -23
- data/lib/action_controller/mime_responds.rb +163 -0
- data/lib/action_controller/mime_type.rb +142 -0
- data/lib/action_controller/pagination.rb +13 -7
- data/lib/action_controller/request.rb +59 -56
- data/lib/action_controller/rescue.rb +1 -1
- data/lib/action_controller/routing.rb +29 -10
- data/lib/action_controller/scaffolding.rb +8 -0
- data/lib/action_controller/session/active_record_store.rb +21 -10
- data/lib/action_controller/session/mem_cache_store.rb +18 -12
- data/lib/action_controller/session_management.rb +30 -11
- data/lib/action_controller/templates/rescues/_trace.rhtml +1 -1
- data/lib/action_controller/templates/scaffolds/layout.rhtml +4 -4
- data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
- data/lib/action_controller/test_process.rb +189 -118
- data/lib/action_controller/vendor/html-scanner/html/node.rb +20 -1
- data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +3 -0
- data/lib/action_controller/vendor/html-scanner/html/version.rb +1 -1
- data/lib/action_controller/vendor/xml_node.rb +97 -0
- data/lib/action_controller/verification.rb +2 -0
- data/lib/action_pack/version.rb +3 -3
- data/lib/action_view.rb +0 -2
- data/lib/action_view/base.rb +109 -36
- data/lib/action_view/compiled_templates.rb +1 -1
- data/lib/action_view/helpers/active_record_helper.rb +4 -2
- data/lib/action_view/helpers/asset_tag_helper.rb +6 -7
- data/lib/action_view/helpers/capture_helper.rb +49 -12
- data/lib/action_view/helpers/date_helper.rb +14 -4
- data/lib/action_view/helpers/form_helper.rb +136 -20
- data/lib/action_view/helpers/form_options_helper.rb +29 -7
- data/lib/action_view/helpers/form_tag_helper.rb +22 -20
- data/lib/action_view/helpers/java_script_macros_helper.rb +29 -9
- data/lib/action_view/helpers/javascript_helper.rb +50 -446
- data/lib/action_view/helpers/javascripts/controls.js +95 -30
- data/lib/action_view/helpers/javascripts/dragdrop.js +161 -21
- data/lib/action_view/helpers/javascripts/effects.js +310 -211
- data/lib/action_view/helpers/javascripts/prototype.js +228 -28
- data/lib/action_view/helpers/number_helper.rb +9 -9
- data/lib/action_view/helpers/pagination_helper.rb +1 -1
- data/lib/action_view/helpers/prototype_helper.rb +900 -0
- data/lib/action_view/helpers/scriptaculous_helper.rb +135 -0
- data/lib/action_view/helpers/text_helper.rb +7 -6
- data/lib/action_view/helpers/url_helper.rb +23 -14
- data/lib/action_view/partials.rb +12 -4
- data/rakefile +13 -5
- data/test/abstract_unit.rb +4 -3
- data/test/active_record_unit.rb +88 -0
- data/test/{controller → activerecord}/active_record_assertions_test.rb +7 -50
- data/test/{controller → activerecord}/active_record_store_test.rb +27 -4
- data/test/activerecord/pagination_test.rb +161 -0
- data/test/controller/action_pack_assertions_test.rb +18 -15
- data/test/controller/base_test.rb +31 -42
- data/test/controller/benchmark_test.rb +8 -11
- data/test/controller/capture_test.rb +33 -1
- data/test/controller/cgi_test.rb +33 -0
- data/test/controller/custom_handler_test.rb +8 -0
- data/test/controller/fake_controllers.rb +9 -17
- data/test/controller/filters_test.rb +32 -3
- data/test/controller/flash_test.rb +26 -41
- data/test/controller/fragment_store_setting_test.rb +1 -1
- data/test/controller/layout_test.rb +73 -0
- data/test/controller/mime_responds_test.rb +257 -0
- data/test/controller/mime_type_test.rb +24 -0
- data/test/controller/new_render_test.rb +157 -1
- data/test/controller/redirect_test.rb +23 -0
- data/test/controller/render_test.rb +54 -56
- data/test/controller/request_test.rb +25 -0
- data/test/controller/routing_test.rb +74 -66
- data/test/controller/test_test.rb +66 -1
- data/test/controller/verification_test.rb +3 -1
- data/test/controller/webservice_test.rb +255 -0
- data/test/fixtures/companies.yml +24 -0
- data/test/fixtures/company.rb +9 -0
- data/test/fixtures/db_definitions/sqlite.sql +42 -0
- data/test/fixtures/developer.rb +7 -0
- data/test/fixtures/developers.yml +21 -0
- data/test/fixtures/developers_projects.yml +13 -0
- data/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml +1 -0
- data/test/fixtures/layout_tests/layouts/item.rhtml +1 -0
- data/test/fixtures/layout_tests/layouts/layout_test.rhtml +1 -0
- data/test/fixtures/layout_tests/layouts/third_party_template_library.mab +1 -0
- data/test/fixtures/layout_tests/views/hello.rhtml +1 -0
- data/test/fixtures/multipart/mona_lisa.jpg +0 -0
- data/test/fixtures/project.rb +3 -0
- data/test/fixtures/projects.yml +7 -0
- data/test/fixtures/replies.yml +13 -0
- data/test/fixtures/reply.rb +5 -0
- data/test/fixtures/respond_to/all_types_with_layout.rhtml +1 -0
- data/test/fixtures/respond_to/all_types_with_layout.rjs +1 -0
- data/test/fixtures/respond_to/layouts/standard.rhtml +1 -0
- data/test/fixtures/respond_to/using_defaults.rhtml +1 -0
- data/test/fixtures/respond_to/using_defaults.rjs +1 -0
- data/test/fixtures/respond_to/using_defaults.rxml +1 -0
- data/test/fixtures/respond_to/using_defaults_with_type_list.rhtml +1 -0
- data/test/fixtures/respond_to/using_defaults_with_type_list.rjs +1 -0
- data/test/fixtures/respond_to/using_defaults_with_type_list.rxml +1 -0
- data/test/fixtures/test/block_content_for.rhtml +2 -0
- data/test/fixtures/test/delete_with_js.rjs +2 -0
- data/test/fixtures/test/dot.directory/render_file_with_ivar.rhtml +1 -0
- data/test/fixtures/test/enum_rjs_test.rjs +6 -0
- data/test/fixtures/test/erb_content_for.rhtml +2 -0
- data/test/fixtures/test/hello_world.rxml +3 -0
- data/test/fixtures/test/hello_world_with_layout_false.rhtml +1 -0
- data/test/fixtures/test/non_erb_block_content_for.rxml +4 -0
- data/test/fixtures/topic.rb +3 -0
- data/test/fixtures/topics.yml +22 -0
- data/test/template/active_record_helper_test.rb +4 -0
- data/test/template/asset_tag_helper_test.rb +7 -2
- data/test/template/date_helper_test.rb +39 -2
- data/test/template/form_helper_test.rb +238 -5
- data/test/template/form_options_helper_test.rb +78 -0
- data/test/template/form_tag_helper_test.rb +11 -0
- data/test/template/java_script_macros_helper_test.rb +51 -6
- data/test/template/javascript_helper_test.rb +7 -153
- data/test/template/number_helper_test.rb +14 -13
- data/test/template/prototype_helper_test.rb +423 -0
- data/test/template/scriptaculous_helper_test.rb +90 -0
- data/test/template/text_helper_test.rb +12 -9
- data/test/template/url_helper_test.rb +31 -15
- metadata +291 -246
- data/lib/action_controller/cgi_ext/multipart_progress.rb +0 -169
- data/lib/action_controller/upload_progress.rb +0 -473
- data/lib/action_controller/vendor/html-scanner/html/node.rb.rej +0 -17
- data/lib/action_view/helpers/upload_progress_helper.rb +0 -433
- data/lib/action_view/vendor/builder.rb +0 -13
- data/lib/action_view/vendor/builder/blankslate.rb +0 -53
- data/lib/action_view/vendor/builder/xmlbase.rb +0 -143
- data/lib/action_view/vendor/builder/xmlevents.rb +0 -63
- data/lib/action_view/vendor/builder/xmlmarkup.rb +0 -308
- data/test/controller/multipart_progress_testx.rb +0 -365
- data/test/controller/upload_progress_testx.rb +0 -89
- data/test/template/upload_progress_helper_testx.rb +0 -136
@@ -24,14 +24,21 @@ module ActionController #:nodoc:
|
|
24
24
|
#
|
25
25
|
# See docs on the FlashHash class for more details about the flash.
|
26
26
|
module Flash
|
27
|
-
def self.
|
28
|
-
|
29
|
-
|
30
|
-
base.
|
31
|
-
|
27
|
+
def self.included(base)
|
28
|
+
base.send :include, InstanceMethods
|
29
|
+
|
30
|
+
base.class_eval do
|
31
|
+
alias_method :assign_shortcuts_without_flash, :assign_shortcuts
|
32
|
+
alias_method :assign_shortcuts, :assign_shortcuts_with_flash
|
32
33
|
|
34
|
+
alias_method :process_cleanup_without_flash, :process_cleanup
|
35
|
+
alias_method :process_cleanup, :process_cleanup_with_flash
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
33
40
|
class FlashNow #:nodoc:
|
34
|
-
def initialize
|
41
|
+
def initialize(flash)
|
35
42
|
@flash = flash
|
36
43
|
end
|
37
44
|
|
@@ -47,9 +54,6 @@ module ActionController #:nodoc:
|
|
47
54
|
end
|
48
55
|
|
49
56
|
class FlashHash < Hash
|
50
|
-
@@avoid_sweep = false
|
51
|
-
cattr_accessor :avoid_sweep
|
52
|
-
|
53
57
|
def initialize #:nodoc:
|
54
58
|
super
|
55
59
|
@used = {}
|
@@ -60,14 +64,14 @@ module ActionController #:nodoc:
|
|
60
64
|
super
|
61
65
|
end
|
62
66
|
|
63
|
-
def update
|
64
|
-
h.keys.each{|k| discard
|
67
|
+
def update(h) #:nodoc:
|
68
|
+
h.keys.each{ |k| discard(k) }
|
65
69
|
super
|
66
70
|
end
|
67
71
|
|
68
|
-
alias merge! update
|
72
|
+
alias :merge! :update
|
69
73
|
|
70
|
-
def replace
|
74
|
+
def replace(h) #:nodoc:
|
71
75
|
@used = {}
|
72
76
|
super
|
73
77
|
end
|
@@ -106,7 +110,6 @@ module ActionController #:nodoc:
|
|
106
110
|
#
|
107
111
|
# This method is called automatically by filters, so you generally don't need to care about it.
|
108
112
|
def sweep #:nodoc:
|
109
|
-
return if @@avoid_sweep
|
110
113
|
keys.each do |k|
|
111
114
|
unless @used[k]
|
112
115
|
use(k)
|
@@ -133,40 +136,43 @@ module ActionController #:nodoc:
|
|
133
136
|
end
|
134
137
|
end
|
135
138
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
# Note that if sessions are disabled only flash.now will work.
|
141
|
-
def flash #:doc:
|
142
|
-
# @session = Hash.new if sessions are disabled
|
143
|
-
if @session.is_a?(Hash)
|
144
|
-
@__flash ||= FlashHash.new
|
145
|
-
|
146
|
-
# otherwise, @session is a CGI::Session or a TestSession
|
147
|
-
else
|
148
|
-
@session['flash'] ||= FlashHash.new
|
149
|
-
end
|
139
|
+
module InstanceMethods #:nodoc:
|
140
|
+
def assign_shortcuts_with_flash(request, response) #:nodoc:
|
141
|
+
assign_shortcuts_without_flash(request, response)
|
142
|
+
flash(:refresh)
|
150
143
|
end
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
flash.keep
|
144
|
+
|
145
|
+
def process_cleanup_with_flash
|
146
|
+
flash.sweep if @session
|
147
|
+
process_cleanup_without_flash
|
156
148
|
end
|
149
|
+
|
150
|
+
protected
|
151
|
+
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to read a notice you put there or
|
152
|
+
# <tt>flash["notice"] = "hello"</tt> to put a new one.
|
153
|
+
# Note that if sessions are disabled only flash.now will work.
|
154
|
+
def flash(refresh = false) #:doc:
|
155
|
+
if @flash.nil? || refresh
|
156
|
+
@flash =
|
157
|
+
if @session.is_a?(Hash)
|
158
|
+
# @session is a Hash, if sessions are disabled
|
159
|
+
# we don't put the flash in the session in this case
|
160
|
+
FlashHash.new
|
161
|
+
else
|
162
|
+
# otherwise, @session is a CGI::Session or a TestSession
|
163
|
+
# so make sure it gets retrieved from/saved to session storage after request processing
|
164
|
+
@session["flash"] ||= FlashHash.new
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
@flash
|
169
|
+
end
|
157
170
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
@assigns["flash"] = flash
|
165
|
-
end
|
166
|
-
|
167
|
-
# deletes the flash entries that were not marked for keeping
|
168
|
-
def sweep_flash
|
169
|
-
flash.sweep
|
170
|
-
end
|
171
|
+
# deprecated. use <tt>flash.keep</tt> instead
|
172
|
+
def keep_flash #:doc:
|
173
|
+
warn 'keep_flash is deprecated; use flash.keep instead.'
|
174
|
+
flash.keep
|
175
|
+
end
|
176
|
+
end
|
171
177
|
end
|
172
|
-
end
|
178
|
+
end
|
@@ -109,7 +109,7 @@ module ActionController #:nodoc:
|
|
109
109
|
|
110
110
|
private
|
111
111
|
def default_helper_module!
|
112
|
-
module_name = name.sub(
|
112
|
+
module_name = name.sub(/Controller$|$/, 'Helper')
|
113
113
|
module_path = module_name.split('::').map { |m| m.underscore }.join('/')
|
114
114
|
require_dependency module_path
|
115
115
|
helper module_name.constantize
|
@@ -128,7 +128,7 @@ module ActionController #:nodoc:
|
|
128
128
|
rescue MissingSourceFile => e
|
129
129
|
raise unless e.is_missing?("helpers/#{child.controller_path}_helper")
|
130
130
|
end
|
131
|
-
end
|
131
|
+
end
|
132
132
|
end
|
133
133
|
end
|
134
134
|
end
|
@@ -0,0 +1,524 @@
|
|
1
|
+
require 'dispatcher'
|
2
|
+
require 'stringio'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module ActionController
|
6
|
+
module Integration #:nodoc:
|
7
|
+
# An integration Session instance represents a set of requests and responses
|
8
|
+
# performed sequentially by some virtual user. Becase you can instantiate
|
9
|
+
# multiple sessions and run them side-by-side, you can also mimic (to some
|
10
|
+
# limited extent) multiple simultaneous users interacting with your system.
|
11
|
+
#
|
12
|
+
# Typically, you will instantiate a new session using IntegrationTest#open_session,
|
13
|
+
# rather than instantiating Integration::Session directly.
|
14
|
+
class Session
|
15
|
+
include Test::Unit::Assertions
|
16
|
+
include ActionController::TestProcess
|
17
|
+
|
18
|
+
# The integer HTTP status code of the last request.
|
19
|
+
attr_reader :status
|
20
|
+
|
21
|
+
# The status message that accompanied the status code of the last request.
|
22
|
+
attr_reader :status_message
|
23
|
+
|
24
|
+
# The URI of the last request.
|
25
|
+
attr_reader :path
|
26
|
+
|
27
|
+
# The hostname used in the last request.
|
28
|
+
attr_accessor :host
|
29
|
+
|
30
|
+
# The remote_addr used in the last request.
|
31
|
+
attr_accessor :remote_addr
|
32
|
+
|
33
|
+
# The Accept header to send.
|
34
|
+
attr_accessor :accept
|
35
|
+
|
36
|
+
# A map of the cookies returned by the last response, and which will be
|
37
|
+
# sent with the next request.
|
38
|
+
attr_reader :cookies
|
39
|
+
|
40
|
+
# A map of the headers returned by the last response.
|
41
|
+
attr_reader :headers
|
42
|
+
|
43
|
+
# A reference to the controller instance used by the last request.
|
44
|
+
attr_reader :controller
|
45
|
+
|
46
|
+
# A reference to the request instance used by the last request.
|
47
|
+
attr_reader :request
|
48
|
+
|
49
|
+
# A reference to the response instance used by the last request.
|
50
|
+
attr_reader :response
|
51
|
+
|
52
|
+
# Create an initialize a new Session instance.
|
53
|
+
def initialize
|
54
|
+
reset!
|
55
|
+
end
|
56
|
+
|
57
|
+
# Resets the instance. This can be used to reset the state information
|
58
|
+
# in an existing session instance, so it can be used from a clean-slate
|
59
|
+
# condition.
|
60
|
+
#
|
61
|
+
# session.reset!
|
62
|
+
def reset!
|
63
|
+
@status = @path = @headers = nil
|
64
|
+
@result = @status_message = nil
|
65
|
+
@https = false
|
66
|
+
@cookies = {}
|
67
|
+
@controller = @request = @response = nil
|
68
|
+
|
69
|
+
self.host = "www.example.com"
|
70
|
+
self.remote_addr = "127.0.0.1"
|
71
|
+
self.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
|
72
|
+
|
73
|
+
unless @named_routes_configured
|
74
|
+
# install the named routes in this session instance.
|
75
|
+
klass = class<<self; self; end
|
76
|
+
Routing::NamedRoutes.install(klass)
|
77
|
+
|
78
|
+
# the helpers are made protected by default--we make them public for
|
79
|
+
# easier access during testing and troubleshooting.
|
80
|
+
klass.send(:public, *Routing::NamedRoutes::Helpers)
|
81
|
+
@named_routes_configured = true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Specify whether or not the session should mimic a secure HTTPS request.
|
86
|
+
#
|
87
|
+
# session.https!
|
88
|
+
# session.https!(false)
|
89
|
+
def https!(flag=true)
|
90
|
+
@https = flag
|
91
|
+
end
|
92
|
+
|
93
|
+
# Return +true+ if the session is mimicing a secure HTTPS request.
|
94
|
+
#
|
95
|
+
# if session.https?
|
96
|
+
# ...
|
97
|
+
# end
|
98
|
+
def https?
|
99
|
+
@https
|
100
|
+
end
|
101
|
+
|
102
|
+
# Set the host name to use in the next request.
|
103
|
+
#
|
104
|
+
# session.host! "www.example.com"
|
105
|
+
def host!(name)
|
106
|
+
@host = name
|
107
|
+
end
|
108
|
+
|
109
|
+
# Follow a single redirect response. If the last response was not a
|
110
|
+
# redirect, an exception will be raised. Otherwise, the redirect is
|
111
|
+
# performed on the location header.
|
112
|
+
def follow_redirect!
|
113
|
+
raise "not a redirect! #{@status} #{@status_message}" unless redirect?
|
114
|
+
get(interpret_uri(headers["location"].first))
|
115
|
+
status
|
116
|
+
end
|
117
|
+
|
118
|
+
# Performs a GET request, following any subsequent redirect. Note that
|
119
|
+
# the redirects are followed until the response is not a redirect--this
|
120
|
+
# means you may run into an infinite loop if your redirect loops back to
|
121
|
+
# itself.
|
122
|
+
def get_via_redirect(path, args={})
|
123
|
+
get path, args
|
124
|
+
follow_redirect! while redirect?
|
125
|
+
status
|
126
|
+
end
|
127
|
+
|
128
|
+
# Performs a POST request, following any subsequent redirect. This is
|
129
|
+
# vulnerable to infinite loops, the same as #get_via_redirect.
|
130
|
+
def post_via_redirect(path, args={})
|
131
|
+
post path, args
|
132
|
+
follow_redirect! while redirect?
|
133
|
+
status
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns +true+ if the last response was a redirect.
|
137
|
+
def redirect?
|
138
|
+
status/100 == 3
|
139
|
+
end
|
140
|
+
|
141
|
+
# Performs a GET request with the given parameters. The parameters may
|
142
|
+
# be +nil+, a Hash, or a string that is appropriately encoded
|
143
|
+
# (application/x-www-form-urlencoded or multipart/form-data).
|
144
|
+
def get(path, parameters=nil, headers=nil)
|
145
|
+
process :get, path, parameters, headers
|
146
|
+
end
|
147
|
+
|
148
|
+
# Performs a POST request with the given parameters. The parameters may
|
149
|
+
# be +nil+, a Hash, or a string that is appropriately encoded
|
150
|
+
# (application/x-www-form-urlencoded or multipart/form-data).
|
151
|
+
def post(path, parameters=nil, headers=nil)
|
152
|
+
process :post, path, parameters, headers
|
153
|
+
end
|
154
|
+
|
155
|
+
# Performs an XMLHttpRequest request with the given parameters, mimicing
|
156
|
+
# the request environment created by the Prototype library. The parameters
|
157
|
+
# may be +nil+, a Hash, or a string that is appropriately encoded
|
158
|
+
# (application/x-www-form-urlencoded or multipart/form-data).
|
159
|
+
def xml_http_request(path, parameters=nil, headers=nil)
|
160
|
+
headers = (headers || {}).merge("X-Requested-With" => "XMLHttpRequest")
|
161
|
+
post(path, parameters, headers)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Returns the URL for the given options, according to the rules specified
|
165
|
+
# in the application's routes.
|
166
|
+
def url_for(options)
|
167
|
+
controller ? controller.url_for(options) : generic_url_rewriter.rewrite(options)
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
class MockCGI < CGI #:nodoc:
|
173
|
+
attr_accessor :stdinput, :stdoutput, :env_table
|
174
|
+
|
175
|
+
def initialize(env, input=nil)
|
176
|
+
self.env_table = env
|
177
|
+
self.stdinput = StringIO.new(input || "")
|
178
|
+
self.stdoutput = StringIO.new
|
179
|
+
|
180
|
+
super()
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Tailors the session based on the given URI, setting the HTTPS value
|
185
|
+
# and the hostname.
|
186
|
+
def interpret_uri(path)
|
187
|
+
location = URI.parse(path)
|
188
|
+
https! URI::HTTPS === location if location.scheme
|
189
|
+
host! location.host if location.host
|
190
|
+
location.query ? "#{location.path}?#{location.query}" : location.path
|
191
|
+
end
|
192
|
+
|
193
|
+
# Performs the actual request.
|
194
|
+
def process(method, path, parameters=nil, headers=nil)
|
195
|
+
data = requestify(parameters)
|
196
|
+
path = interpret_uri(path) if path =~ %r{://}
|
197
|
+
path = "/#{path}" unless path[0] == ?/
|
198
|
+
@path = path
|
199
|
+
env = {}
|
200
|
+
|
201
|
+
if method == :get
|
202
|
+
env["QUERY_STRING"] = data
|
203
|
+
data = nil
|
204
|
+
end
|
205
|
+
|
206
|
+
env.update(
|
207
|
+
"REQUEST_METHOD" => method.to_s.upcase,
|
208
|
+
"REQUEST_URI" => path,
|
209
|
+
"HTTP_HOST" => host,
|
210
|
+
"REMOTE_ADDR" => remote_addr,
|
211
|
+
"SERVER_PORT" => (https? ? "443" : "80"),
|
212
|
+
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
213
|
+
"CONTENT_LENGTH" => data ? data.length.to_s : nil,
|
214
|
+
"HTTP_COOKIE" => encode_cookies,
|
215
|
+
"HTTPS" => https? ? "on" : "off",
|
216
|
+
"HTTP_ACCEPT" => accept
|
217
|
+
)
|
218
|
+
|
219
|
+
(headers || {}).each do |key, value|
|
220
|
+
key = key.to_s.upcase.gsub(/-/, "_")
|
221
|
+
key = "HTTP_#{key}" unless env.has_key?(key)
|
222
|
+
env[key] = value
|
223
|
+
end
|
224
|
+
|
225
|
+
unless ActionController::Base.respond_to?(:clear_last_instantiation!)
|
226
|
+
ActionController::Base.send(:include, ControllerCapture)
|
227
|
+
end
|
228
|
+
|
229
|
+
ActionController::Base.clear_last_instantiation!
|
230
|
+
|
231
|
+
cgi = MockCGI.new(env, data)
|
232
|
+
Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)
|
233
|
+
@result = cgi.stdoutput.string
|
234
|
+
|
235
|
+
@controller = ActionController::Base.last_instantiation
|
236
|
+
@request = @controller.request
|
237
|
+
@response = @controller.response
|
238
|
+
|
239
|
+
# Decorate the response with the standard behavior of the TestResponse
|
240
|
+
# so that things like assert_response can be used in integration
|
241
|
+
# tests.
|
242
|
+
@response.extend(TestResponseBehavior)
|
243
|
+
|
244
|
+
parse_result
|
245
|
+
return status
|
246
|
+
end
|
247
|
+
|
248
|
+
# Parses the result of the response and extracts the various values,
|
249
|
+
# like cookies, status, headers, etc.
|
250
|
+
def parse_result
|
251
|
+
headers, result_body = @result.split(/\r\n\r\n/, 2)
|
252
|
+
|
253
|
+
@headers = Hash.new { |h,k| h[k] = [] }
|
254
|
+
headers.each_line do |line|
|
255
|
+
key, value = line.strip.split(/:\s*/, 2)
|
256
|
+
@headers[key.downcase] << value
|
257
|
+
end
|
258
|
+
|
259
|
+
(@headers['set-cookie'] || [] ).each do |string|
|
260
|
+
name, value = string.match(/^(.*?)=(.*?);/)[1,2]
|
261
|
+
@cookies[name] = value
|
262
|
+
end
|
263
|
+
|
264
|
+
@status, @status_message = @headers["status"].first.split(/ /)
|
265
|
+
@status = @status.to_i
|
266
|
+
end
|
267
|
+
|
268
|
+
# Encode the cookies hash in a format suitable for passing to a
|
269
|
+
# request.
|
270
|
+
def encode_cookies
|
271
|
+
cookies.inject("") do |string, (name, value)|
|
272
|
+
string << "#{name}=#{value}; "
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Get a temporarly URL writer object
|
277
|
+
def generic_url_rewriter
|
278
|
+
cgi = MockCGI.new('REQUEST_METHOD' => "GET",
|
279
|
+
'QUERY_STRING' => "",
|
280
|
+
"REQUEST_URI" => "/",
|
281
|
+
"HTTP_HOST" => host,
|
282
|
+
"SERVER_PORT" => https? ? "443" : "80",
|
283
|
+
"HTTPS" => https? ? "on" : "off")
|
284
|
+
ActionController::UrlRewriter.new(ActionController::CgiRequest.new(cgi), {})
|
285
|
+
end
|
286
|
+
|
287
|
+
def name_with_prefix(prefix, name)
|
288
|
+
prefix ? "#{prefix}[#{name}]" : name.to_s
|
289
|
+
end
|
290
|
+
|
291
|
+
# Convert the given parameters to a request string. The parameters may
|
292
|
+
# be a string, +nil+, or a Hash.
|
293
|
+
def requestify(parameters, prefix=nil)
|
294
|
+
if Hash === parameters
|
295
|
+
return nil if parameters.empty?
|
296
|
+
parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
|
297
|
+
elsif Array === parameters
|
298
|
+
parameters.map { |v| requestify(v, name_with_prefix(prefix, "")) }.join("&")
|
299
|
+
elsif prefix.nil?
|
300
|
+
parameters
|
301
|
+
else
|
302
|
+
"#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}"
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
end
|
307
|
+
|
308
|
+
# A module used to extend ActionController::Base, so that integration tests
|
309
|
+
# can capture the controller used to satisfy a request.
|
310
|
+
module ControllerCapture #:nodoc:
|
311
|
+
def self.included(base)
|
312
|
+
base.extend(ClassMethods)
|
313
|
+
base.class_eval do
|
314
|
+
class <<self
|
315
|
+
alias_method :new_without_capture, :new
|
316
|
+
alias_method :new, :new_with_capture
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
module ClassMethods #:nodoc:
|
322
|
+
mattr_accessor :last_instantiation
|
323
|
+
|
324
|
+
def clear_last_instantiation!
|
325
|
+
self.last_instantiation = nil
|
326
|
+
end
|
327
|
+
|
328
|
+
def new_with_capture(*args)
|
329
|
+
self.last_instantiation ||= new_without_capture(*args)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# An IntegrationTest is one that spans multiple controllers and actions,
|
336
|
+
# tying them all together to ensure they work together as expected. It tests
|
337
|
+
# more completely than either unit or functional tests do, exercising the
|
338
|
+
# entire stack, from the dispatcher to the database.
|
339
|
+
#
|
340
|
+
# At its simplest, you simply extend IntegrationTest and write your tests
|
341
|
+
# using the get/post methods:
|
342
|
+
#
|
343
|
+
# require "#{File.dirname(__FILE__)}/test_helper"
|
344
|
+
# require "integration_test"
|
345
|
+
#
|
346
|
+
# class ExampleTest < ActionController::IntegrationTest
|
347
|
+
# fixtures :people
|
348
|
+
#
|
349
|
+
# def test_login
|
350
|
+
# # get the login page
|
351
|
+
# get "/login"
|
352
|
+
# assert_equal 200, status
|
353
|
+
#
|
354
|
+
# # post the login and follow through to the home page
|
355
|
+
# post "/login", :username => people(:jamis).username,
|
356
|
+
# :password => people(:jamis).password
|
357
|
+
# follow_redirect!
|
358
|
+
# assert_equal 200, status
|
359
|
+
# assert_equal "/home", path
|
360
|
+
# end
|
361
|
+
# end
|
362
|
+
#
|
363
|
+
# However, you can also have multiple session instances open per test, and
|
364
|
+
# even extend those instances with assertions and methods to create a very
|
365
|
+
# powerful testing DSL that is specific for your application. You can even
|
366
|
+
# reference any named routes you happen to have defined!
|
367
|
+
#
|
368
|
+
# require "#{File.dirname(__FILE__)}/test_helper"
|
369
|
+
# require "integration_test"
|
370
|
+
#
|
371
|
+
# class AdvancedTest < ActionController::IntegrationTest
|
372
|
+
# fixtures :people, :rooms
|
373
|
+
#
|
374
|
+
# def test_login_and_speak
|
375
|
+
# jamis, david = login(:jamis), login(:david)
|
376
|
+
# room = rooms(:office)
|
377
|
+
#
|
378
|
+
# jamis.enter(room)
|
379
|
+
# jamis.speak(room, "anybody home?")
|
380
|
+
#
|
381
|
+
# david.enter(room)
|
382
|
+
# david.speak(room, "hello!")
|
383
|
+
# end
|
384
|
+
#
|
385
|
+
# private
|
386
|
+
#
|
387
|
+
# module CustomAssertions
|
388
|
+
# def enter(room)
|
389
|
+
# # reference a named route, for maximum internal consistency!
|
390
|
+
# get(room_url(:id => room.id))
|
391
|
+
# assert(...)
|
392
|
+
# ...
|
393
|
+
# end
|
394
|
+
#
|
395
|
+
# def speak(room, message)
|
396
|
+
# xml_http_request "/say/#{room.id}", :message => message
|
397
|
+
# assert(...)
|
398
|
+
# ...
|
399
|
+
# end
|
400
|
+
# end
|
401
|
+
#
|
402
|
+
# def login(who)
|
403
|
+
# open_session do |sess|
|
404
|
+
# sess.extend(CustomAssertions)
|
405
|
+
# who = people(who)
|
406
|
+
# sess.post "/login", :username => who.username,
|
407
|
+
# :password => who.password
|
408
|
+
# assert(...)
|
409
|
+
# end
|
410
|
+
# end
|
411
|
+
# end
|
412
|
+
class IntegrationTest < Test::Unit::TestCase
|
413
|
+
# Work around a bug in test/unit caused by the default test being named
|
414
|
+
# as a symbol (:default_test), which causes regex test filters
|
415
|
+
# (like "ruby test.rb -n /foo/") to fail because =~ doesn't work on
|
416
|
+
# symbols.
|
417
|
+
def initialize(name) #:nodoc:
|
418
|
+
super(name.to_s)
|
419
|
+
end
|
420
|
+
|
421
|
+
# Work around test/unit's requirement that every subclass of TestCase have
|
422
|
+
# at least one test method. Note that this implementation extends to all
|
423
|
+
# subclasses, as well, so subclasses of IntegrationTest may also exist
|
424
|
+
# without any test methods.
|
425
|
+
def run(*args) #:nodoc:
|
426
|
+
return if @method_name == "default_test"
|
427
|
+
super
|
428
|
+
end
|
429
|
+
|
430
|
+
# Because of how use_instantiated_fixtures and use_transactional_fixtures
|
431
|
+
# are defined, we need to treat them as special cases. Otherwise, users
|
432
|
+
# would potentially have to set their values for both Test::Unit::TestCase
|
433
|
+
# ActionController::IntegrationTest, since by the time the value is set on
|
434
|
+
# TestCase, IntegrationTest has already been defined and cannot inherit
|
435
|
+
# changes to those variables. So, we make those two attributes copy-on-write.
|
436
|
+
|
437
|
+
class << self
|
438
|
+
def use_transactional_fixtures=(flag) #:nodoc:
|
439
|
+
@_use_transactional_fixtures = true
|
440
|
+
@use_transactional_fixtures = flag
|
441
|
+
end
|
442
|
+
|
443
|
+
def use_instantiated_fixtures=(flag) #:nodoc:
|
444
|
+
@_use_instantiated_fixtures = true
|
445
|
+
@use_instantiated_fixtures = flag
|
446
|
+
end
|
447
|
+
|
448
|
+
def use_transactional_fixtures #:nodoc:
|
449
|
+
@_use_transactional_fixtures ?
|
450
|
+
@use_transactional_fixtures :
|
451
|
+
superclass.use_transactional_fixtures
|
452
|
+
end
|
453
|
+
|
454
|
+
def use_instantiated_fixtures #:nodoc:
|
455
|
+
@_use_instantiated_fixtures ?
|
456
|
+
@use_instantiated_fixtures :
|
457
|
+
superclass.use_instantiated_fixtures
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
# Reset the current session. This is useful for testing multiple sessions
|
462
|
+
# in a single test case.
|
463
|
+
def reset!
|
464
|
+
@integration_session = open_session
|
465
|
+
end
|
466
|
+
|
467
|
+
%w(get post cookies assigns).each do |method|
|
468
|
+
define_method(method) do |*args|
|
469
|
+
reset! unless @integration_session
|
470
|
+
returning @integration_session.send(method, *args) do
|
471
|
+
copy_session_variables!
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
# Open a new session instance. If a block is given, the new session is
|
477
|
+
# yielded to the block before being returned.
|
478
|
+
#
|
479
|
+
# session = open_session do |sess|
|
480
|
+
# sess.extend(CustomAssertions)
|
481
|
+
# end
|
482
|
+
#
|
483
|
+
# By default, a single session is automatically created for you, but you
|
484
|
+
# can use this method to open multiple sessions that ought to be tested
|
485
|
+
# simultaneously.
|
486
|
+
def open_session
|
487
|
+
session = Integration::Session.new
|
488
|
+
|
489
|
+
# delegate the fixture accessors back to the test instance
|
490
|
+
extras = Module.new { attr_accessor :delegate, :test_result }
|
491
|
+
self.class.fixture_table_names.each do |table_name|
|
492
|
+
name = table_name.tr(".", "_")
|
493
|
+
next unless respond_to?(name)
|
494
|
+
extras.send(:define_method, name) { |*args| delegate.send(name, *args) }
|
495
|
+
end
|
496
|
+
|
497
|
+
# delegate add_assertion to the test case
|
498
|
+
extras.send(:define_method, :add_assertion) { test_result.add_assertion }
|
499
|
+
session.extend(extras)
|
500
|
+
session.delegate = self
|
501
|
+
session.test_result = @_result
|
502
|
+
|
503
|
+
yield session if block_given?
|
504
|
+
session
|
505
|
+
end
|
506
|
+
|
507
|
+
# Copy the instance variables from the current session instance into the
|
508
|
+
# test instance.
|
509
|
+
def copy_session_variables! #:nodoc:
|
510
|
+
return unless @integration_session
|
511
|
+
%w(controller response request).each do |var|
|
512
|
+
instance_variable_set("@#{var}", @integration_session.send(var))
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
# Delegate unhandled messages to the current session instance.
|
517
|
+
def method_missing(sym, *args, &block)
|
518
|
+
reset! unless @integration_session
|
519
|
+
returning @integration_session.send(sym, *args, &block) do
|
520
|
+
copy_session_variables!
|
521
|
+
end
|
522
|
+
end
|
523
|
+
end
|
524
|
+
end
|