halorgium-actionpack 3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. data/CHANGELOG +5179 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +409 -0
  4. data/lib/abstract_controller.rb +16 -0
  5. data/lib/abstract_controller/base.rb +158 -0
  6. data/lib/abstract_controller/callbacks.rb +113 -0
  7. data/lib/abstract_controller/exceptions.rb +12 -0
  8. data/lib/abstract_controller/helpers.rb +151 -0
  9. data/lib/abstract_controller/layouts.rb +250 -0
  10. data/lib/abstract_controller/localized_cache.rb +49 -0
  11. data/lib/abstract_controller/logger.rb +61 -0
  12. data/lib/abstract_controller/rendering_controller.rb +188 -0
  13. data/lib/action_controller.rb +72 -0
  14. data/lib/action_controller/base.rb +168 -0
  15. data/lib/action_controller/caching.rb +80 -0
  16. data/lib/action_controller/caching/actions.rb +163 -0
  17. data/lib/action_controller/caching/fragments.rb +116 -0
  18. data/lib/action_controller/caching/pages.rb +154 -0
  19. data/lib/action_controller/caching/sweeping.rb +97 -0
  20. data/lib/action_controller/deprecated.rb +4 -0
  21. data/lib/action_controller/deprecated/integration_test.rb +2 -0
  22. data/lib/action_controller/deprecated/performance_test.rb +1 -0
  23. data/lib/action_controller/dispatch/dispatcher.rb +57 -0
  24. data/lib/action_controller/metal.rb +129 -0
  25. data/lib/action_controller/metal/benchmarking.rb +73 -0
  26. data/lib/action_controller/metal/compatibility.rb +145 -0
  27. data/lib/action_controller/metal/conditional_get.rb +86 -0
  28. data/lib/action_controller/metal/configuration.rb +28 -0
  29. data/lib/action_controller/metal/cookies.rb +105 -0
  30. data/lib/action_controller/metal/exceptions.rb +55 -0
  31. data/lib/action_controller/metal/filter_parameter_logging.rb +77 -0
  32. data/lib/action_controller/metal/flash.rb +162 -0
  33. data/lib/action_controller/metal/head.rb +27 -0
  34. data/lib/action_controller/metal/helpers.rb +115 -0
  35. data/lib/action_controller/metal/hide_actions.rb +47 -0
  36. data/lib/action_controller/metal/http_authentication.rb +312 -0
  37. data/lib/action_controller/metal/layouts.rb +171 -0
  38. data/lib/action_controller/metal/mime_responds.rb +317 -0
  39. data/lib/action_controller/metal/rack_convenience.rb +27 -0
  40. data/lib/action_controller/metal/redirector.rb +22 -0
  41. data/lib/action_controller/metal/render_options.rb +103 -0
  42. data/lib/action_controller/metal/rendering_controller.rb +57 -0
  43. data/lib/action_controller/metal/request_forgery_protection.rb +108 -0
  44. data/lib/action_controller/metal/rescuable.rb +13 -0
  45. data/lib/action_controller/metal/responder.rb +200 -0
  46. data/lib/action_controller/metal/session.rb +15 -0
  47. data/lib/action_controller/metal/session_management.rb +45 -0
  48. data/lib/action_controller/metal/streaming.rb +188 -0
  49. data/lib/action_controller/metal/testing.rb +39 -0
  50. data/lib/action_controller/metal/url_for.rb +41 -0
  51. data/lib/action_controller/metal/verification.rb +130 -0
  52. data/lib/action_controller/middleware.rb +38 -0
  53. data/lib/action_controller/notifications.rb +10 -0
  54. data/lib/action_controller/polymorphic_routes.rb +183 -0
  55. data/lib/action_controller/record_identifier.rb +91 -0
  56. data/lib/action_controller/testing/process.rb +111 -0
  57. data/lib/action_controller/testing/test_case.rb +345 -0
  58. data/lib/action_controller/translation.rb +13 -0
  59. data/lib/action_controller/url_rewriter.rb +204 -0
  60. data/lib/action_controller/vendor/html-scanner.rb +16 -0
  61. data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
  62. data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
  63. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +176 -0
  64. data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
  65. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
  66. data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
  67. data/lib/action_dispatch.rb +70 -0
  68. data/lib/action_dispatch/http/headers.rb +33 -0
  69. data/lib/action_dispatch/http/mime_type.rb +231 -0
  70. data/lib/action_dispatch/http/mime_types.rb +23 -0
  71. data/lib/action_dispatch/http/request.rb +539 -0
  72. data/lib/action_dispatch/http/response.rb +290 -0
  73. data/lib/action_dispatch/http/status_codes.rb +42 -0
  74. data/lib/action_dispatch/http/utils.rb +20 -0
  75. data/lib/action_dispatch/middleware/callbacks.rb +50 -0
  76. data/lib/action_dispatch/middleware/params_parser.rb +79 -0
  77. data/lib/action_dispatch/middleware/rescue.rb +26 -0
  78. data/lib/action_dispatch/middleware/session/abstract_store.rb +208 -0
  79. data/lib/action_dispatch/middleware/session/cookie_store.rb +235 -0
  80. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +47 -0
  81. data/lib/action_dispatch/middleware/show_exceptions.rb +143 -0
  82. data/lib/action_dispatch/middleware/stack.rb +116 -0
  83. data/lib/action_dispatch/middleware/static.rb +44 -0
  84. data/lib/action_dispatch/middleware/string_coercion.rb +29 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +24 -0
  86. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +26 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +10 -0
  88. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +29 -0
  89. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +2 -0
  90. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +10 -0
  91. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +21 -0
  92. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +2 -0
  93. data/lib/action_dispatch/routing.rb +381 -0
  94. data/lib/action_dispatch/routing/deprecated_mapper.rb +878 -0
  95. data/lib/action_dispatch/routing/mapper.rb +327 -0
  96. data/lib/action_dispatch/routing/route.rb +49 -0
  97. data/lib/action_dispatch/routing/route_set.rb +497 -0
  98. data/lib/action_dispatch/testing/assertions.rb +8 -0
  99. data/lib/action_dispatch/testing/assertions/dom.rb +35 -0
  100. data/lib/action_dispatch/testing/assertions/model.rb +19 -0
  101. data/lib/action_dispatch/testing/assertions/response.rb +145 -0
  102. data/lib/action_dispatch/testing/assertions/routing.rb +144 -0
  103. data/lib/action_dispatch/testing/assertions/selector.rb +639 -0
  104. data/lib/action_dispatch/testing/assertions/tag.rb +123 -0
  105. data/lib/action_dispatch/testing/integration.rb +504 -0
  106. data/lib/action_dispatch/testing/performance_test.rb +15 -0
  107. data/lib/action_dispatch/testing/test_request.rb +83 -0
  108. data/lib/action_dispatch/testing/test_response.rb +131 -0
  109. data/lib/action_pack.rb +24 -0
  110. data/lib/action_pack/version.rb +9 -0
  111. data/lib/action_view.rb +58 -0
  112. data/lib/action_view/base.rb +308 -0
  113. data/lib/action_view/context.rb +44 -0
  114. data/lib/action_view/erb/util.rb +48 -0
  115. data/lib/action_view/helpers.rb +62 -0
  116. data/lib/action_view/helpers/active_model_helper.rb +306 -0
  117. data/lib/action_view/helpers/ajax_helper.rb +68 -0
  118. data/lib/action_view/helpers/asset_tag_helper.rb +830 -0
  119. data/lib/action_view/helpers/atom_feed_helper.rb +198 -0
  120. data/lib/action_view/helpers/cache_helper.rb +39 -0
  121. data/lib/action_view/helpers/capture_helper.rb +168 -0
  122. data/lib/action_view/helpers/date_helper.rb +988 -0
  123. data/lib/action_view/helpers/debug_helper.rb +38 -0
  124. data/lib/action_view/helpers/form_helper.rb +1102 -0
  125. data/lib/action_view/helpers/form_options_helper.rb +600 -0
  126. data/lib/action_view/helpers/form_tag_helper.rb +495 -0
  127. data/lib/action_view/helpers/javascript_helper.rb +208 -0
  128. data/lib/action_view/helpers/number_helper.rb +311 -0
  129. data/lib/action_view/helpers/prototype_helper.rb +1309 -0
  130. data/lib/action_view/helpers/raw_output_helper.rb +9 -0
  131. data/lib/action_view/helpers/record_identification_helper.rb +20 -0
  132. data/lib/action_view/helpers/record_tag_helper.rb +58 -0
  133. data/lib/action_view/helpers/sanitize_helper.rb +259 -0
  134. data/lib/action_view/helpers/scriptaculous_helper.rb +226 -0
  135. data/lib/action_view/helpers/tag_helper.rb +151 -0
  136. data/lib/action_view/helpers/text_helper.rb +594 -0
  137. data/lib/action_view/helpers/translation_helper.rb +39 -0
  138. data/lib/action_view/helpers/url_helper.rb +639 -0
  139. data/lib/action_view/locale/en.yml +117 -0
  140. data/lib/action_view/paths.rb +80 -0
  141. data/lib/action_view/render/partials.rb +342 -0
  142. data/lib/action_view/render/rendering.rb +134 -0
  143. data/lib/action_view/safe_buffer.rb +28 -0
  144. data/lib/action_view/template/error.rb +101 -0
  145. data/lib/action_view/template/handler.rb +36 -0
  146. data/lib/action_view/template/handlers.rb +52 -0
  147. data/lib/action_view/template/handlers/builder.rb +17 -0
  148. data/lib/action_view/template/handlers/erb.rb +53 -0
  149. data/lib/action_view/template/handlers/rjs.rb +18 -0
  150. data/lib/action_view/template/resolver.rb +165 -0
  151. data/lib/action_view/template/template.rb +131 -0
  152. data/lib/action_view/template/text.rb +38 -0
  153. data/lib/action_view/test_case.rb +163 -0
  154. metadata +236 -0
@@ -0,0 +1,123 @@
1
+ module ActionDispatch
2
+ module Assertions
3
+ # Pair of assertions to testing elements in the HTML output of the response.
4
+ module TagAssertions
5
+ # Asserts that there is a tag/node/element in the body of the response
6
+ # that meets all of the given conditions. The +conditions+ parameter must
7
+ # be a hash of any of the following keys (all are optional):
8
+ #
9
+ # * <tt>:tag</tt>: the node type must match the corresponding value
10
+ # * <tt>:attributes</tt>: a hash. The node's attributes must match the
11
+ # corresponding values in the hash.
12
+ # * <tt>:parent</tt>: a hash. The node's parent must match the
13
+ # corresponding hash.
14
+ # * <tt>:child</tt>: a hash. At least one of the node's immediate children
15
+ # must meet the criteria described by the hash.
16
+ # * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
17
+ # meet the criteria described by the hash.
18
+ # * <tt>:descendant</tt>: a hash. At least one of the node's descendants
19
+ # must meet the criteria described by the hash.
20
+ # * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
21
+ # meet the criteria described by the hash.
22
+ # * <tt>:after</tt>: a hash. The node must be after any sibling meeting
23
+ # the criteria described by the hash, and at least one sibling must match.
24
+ # * <tt>:before</tt>: a hash. The node must be before any sibling meeting
25
+ # the criteria described by the hash, and at least one sibling must match.
26
+ # * <tt>:children</tt>: a hash, for counting children of a node. Accepts
27
+ # the keys:
28
+ # * <tt>:count</tt>: either a number or a range which must equal (or
29
+ # include) the number of children that match.
30
+ # * <tt>:less_than</tt>: the number of matching children must be less
31
+ # than this number.
32
+ # * <tt>:greater_than</tt>: the number of matching children must be
33
+ # greater than this number.
34
+ # * <tt>:only</tt>: another hash consisting of the keys to use
35
+ # to match on the children, and only matching children will be
36
+ # counted.
37
+ # * <tt>:content</tt>: the textual content of the node must match the
38
+ # given value. This will not match HTML tags in the body of a
39
+ # tag--only text.
40
+ #
41
+ # Conditions are matched using the following algorithm:
42
+ #
43
+ # * if the condition is a string, it must be a substring of the value.
44
+ # * if the condition is a regexp, it must match the value.
45
+ # * if the condition is a number, the value must match number.to_s.
46
+ # * if the condition is +true+, the value must not be +nil+.
47
+ # * if the condition is +false+ or +nil+, the value must be +nil+.
48
+ #
49
+ # === Examples
50
+ #
51
+ # # Assert that there is a "span" tag
52
+ # assert_tag :tag => "span"
53
+ #
54
+ # # Assert that there is a "span" tag with id="x"
55
+ # assert_tag :tag => "span", :attributes => { :id => "x" }
56
+ #
57
+ # # Assert that there is a "span" tag using the short-hand
58
+ # assert_tag :span
59
+ #
60
+ # # Assert that there is a "span" tag with id="x" using the short-hand
61
+ # assert_tag :span, :attributes => { :id => "x" }
62
+ #
63
+ # # Assert that there is a "span" inside of a "div"
64
+ # assert_tag :tag => "span", :parent => { :tag => "div" }
65
+ #
66
+ # # Assert that there is a "span" somewhere inside a table
67
+ # assert_tag :tag => "span", :ancestor => { :tag => "table" }
68
+ #
69
+ # # Assert that there is a "span" with at least one "em" child
70
+ # assert_tag :tag => "span", :child => { :tag => "em" }
71
+ #
72
+ # # Assert that there is a "span" containing a (possibly nested)
73
+ # # "strong" tag.
74
+ # assert_tag :tag => "span", :descendant => { :tag => "strong" }
75
+ #
76
+ # # Assert that there is a "span" containing between 2 and 4 "em" tags
77
+ # # as immediate children
78
+ # assert_tag :tag => "span",
79
+ # :children => { :count => 2..4, :only => { :tag => "em" } }
80
+ #
81
+ # # Get funky: assert that there is a "div", with an "ul" ancestor
82
+ # # and an "li" parent (with "class" = "enum"), and containing a
83
+ # # "span" descendant that contains text matching /hello world/
84
+ # assert_tag :tag => "div",
85
+ # :ancestor => { :tag => "ul" },
86
+ # :parent => { :tag => "li",
87
+ # :attributes => { :class => "enum" } },
88
+ # :descendant => { :tag => "span",
89
+ # :child => /hello world/ }
90
+ #
91
+ # <b>Please note</b>: +assert_tag+ and +assert_no_tag+ only work
92
+ # with well-formed XHTML. They recognize a few tags as implicitly self-closing
93
+ # (like br and hr and such) but will not work correctly with tags
94
+ # that allow optional closing tags (p, li, td). <em>You must explicitly
95
+ # close all of your tags to use these assertions.</em>
96
+ def assert_tag(*opts)
97
+ opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
98
+ tag = find_tag(opts)
99
+ assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}"
100
+ end
101
+
102
+ # Identical to +assert_tag+, but asserts that a matching tag does _not_
103
+ # exist. (See +assert_tag+ for a full discussion of the syntax.)
104
+ #
105
+ # === Examples
106
+ # # Assert that there is not a "div" containing a "p"
107
+ # assert_no_tag :tag => "div", :descendant => { :tag => "p" }
108
+ #
109
+ # # Assert that an unordered list is empty
110
+ # assert_no_tag :tag => "ul", :descendant => { :tag => "li" }
111
+ #
112
+ # # Assert that there is not a "p" tag with between 1 to 3 "img" tags
113
+ # # as immediate children
114
+ # assert_no_tag :tag => "p",
115
+ # :children => { :count => 1..3, :only => { :tag => "img" } }
116
+ def assert_no_tag(*opts)
117
+ opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
118
+ tag = find_tag(opts)
119
+ assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}"
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,504 @@
1
+ require 'stringio'
2
+ require 'uri'
3
+ require 'active_support/test_case'
4
+ require 'active_support/core_ext/object/metaclass'
5
+
6
+ # TODO: Remove circular dependency on ActionController
7
+ require 'action_controller/testing/process'
8
+
9
+ module ActionDispatch
10
+ module Integration #:nodoc:
11
+ module RequestHelpers
12
+ # Performs a GET request with the given parameters.
13
+ #
14
+ # - +path+: The URI (as a String) on which you want to perform a GET
15
+ # request.
16
+ # - +parameters+: The HTTP parameters that you want to pass. This may
17
+ # be +nil+,
18
+ # a Hash, or a String that is appropriately encoded
19
+ # (<tt>application/x-www-form-urlencoded</tt> or
20
+ # <tt>multipart/form-data</tt>).
21
+ # - +headers+: Additional HTTP headers to pass, as a Hash. The keys will
22
+ # automatically be upcased, with the prefix 'HTTP_' added if needed.
23
+ #
24
+ # This method returns an Response object, which one can use to
25
+ # inspect the details of the response. Furthermore, if this method was
26
+ # called from an ActionDispatch::IntegrationTest object, then that
27
+ # object's <tt>@response</tt> instance variable will point to the same
28
+ # response object.
29
+ #
30
+ # You can also perform POST, PUT, DELETE, and HEAD requests with +post+,
31
+ # +put+, +delete+, and +head+.
32
+ def get(path, parameters = nil, headers = nil)
33
+ process :get, path, parameters, headers
34
+ end
35
+
36
+ # Performs a POST request with the given parameters. See get() for more
37
+ # details.
38
+ def post(path, parameters = nil, headers = nil)
39
+ process :post, path, parameters, headers
40
+ end
41
+
42
+ # Performs a PUT request with the given parameters. See get() for more
43
+ # details.
44
+ def put(path, parameters = nil, headers = nil)
45
+ process :put, path, parameters, headers
46
+ end
47
+
48
+ # Performs a DELETE request with the given parameters. See get() for
49
+ # more details.
50
+ def delete(path, parameters = nil, headers = nil)
51
+ process :delete, path, parameters, headers
52
+ end
53
+
54
+ # Performs a HEAD request with the given parameters. See get() for more
55
+ # details.
56
+ def head(path, parameters = nil, headers = nil)
57
+ process :head, path, parameters, headers
58
+ end
59
+
60
+ # Performs an XMLHttpRequest request with the given parameters, mirroring
61
+ # a request from the Prototype library.
62
+ #
63
+ # The request_method is :get, :post, :put, :delete or :head; the
64
+ # parameters are +nil+, a hash, or a url-encoded or multipart string;
65
+ # the headers are a hash. Keys are automatically upcased and prefixed
66
+ # with 'HTTP_' if not already.
67
+ def xml_http_request(request_method, path, parameters = nil, headers = nil)
68
+ headers ||= {}
69
+ headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
70
+ headers['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
71
+ process(request_method, path, parameters, headers)
72
+ end
73
+ alias xhr :xml_http_request
74
+
75
+ # Follow a single redirect response. If the last response was not a
76
+ # redirect, an exception will be raised. Otherwise, the redirect is
77
+ # performed on the location header.
78
+ def follow_redirect!
79
+ raise "not a redirect! #{status} #{status_message}" unless redirect?
80
+ get(response.location)
81
+ status
82
+ end
83
+
84
+ # Performs a request using the specified method, following any subsequent
85
+ # redirect. Note that the redirects are followed until the response is
86
+ # not a redirect--this means you may run into an infinite loop if your
87
+ # redirect loops back to itself.
88
+ def request_via_redirect(http_method, path, parameters = nil, headers = nil)
89
+ process(http_method, path, parameters, headers)
90
+ follow_redirect! while redirect?
91
+ status
92
+ end
93
+
94
+ # Performs a GET request, following any subsequent redirect.
95
+ # See +request_via_redirect+ for more information.
96
+ def get_via_redirect(path, parameters = nil, headers = nil)
97
+ request_via_redirect(:get, path, parameters, headers)
98
+ end
99
+
100
+ # Performs a POST request, following any subsequent redirect.
101
+ # See +request_via_redirect+ for more information.
102
+ def post_via_redirect(path, parameters = nil, headers = nil)
103
+ request_via_redirect(:post, path, parameters, headers)
104
+ end
105
+
106
+ # Performs a PUT request, following any subsequent redirect.
107
+ # See +request_via_redirect+ for more information.
108
+ def put_via_redirect(path, parameters = nil, headers = nil)
109
+ request_via_redirect(:put, path, parameters, headers)
110
+ end
111
+
112
+ # Performs a DELETE request, following any subsequent redirect.
113
+ # See +request_via_redirect+ for more information.
114
+ def delete_via_redirect(path, parameters = nil, headers = nil)
115
+ request_via_redirect(:delete, path, parameters, headers)
116
+ end
117
+ end
118
+
119
+ # An integration Session instance represents a set of requests and responses
120
+ # performed sequentially by some virtual user. Because you can instantiate
121
+ # multiple sessions and run them side-by-side, you can also mimic (to some
122
+ # limited extent) multiple simultaneous users interacting with your system.
123
+ #
124
+ # Typically, you will instantiate a new session using
125
+ # IntegrationTest#open_session, rather than instantiating
126
+ # Integration::Session directly.
127
+ class Session
128
+ DEFAULT_HOST = "www.example.com"
129
+
130
+ include Test::Unit::Assertions
131
+ include ActionDispatch::Assertions
132
+ include ActionController::TestProcess
133
+ include RequestHelpers
134
+
135
+ %w( status status_message headers body redirect? ).each do |method|
136
+ delegate method, :to => :response, :allow_nil => true
137
+ end
138
+
139
+ %w( path ).each do |method|
140
+ delegate method, :to => :request, :allow_nil => true
141
+ end
142
+
143
+ # The hostname used in the last request.
144
+ attr_accessor :host
145
+
146
+ # The remote_addr used in the last request.
147
+ attr_accessor :remote_addr
148
+
149
+ # The Accept header to send.
150
+ attr_accessor :accept
151
+
152
+ # A map of the cookies returned by the last response, and which will be
153
+ # sent with the next request.
154
+ def cookies
155
+ @mock_session.cookie_jar
156
+ end
157
+
158
+ # A reference to the controller instance used by the last request.
159
+ attr_reader :controller
160
+
161
+ # A reference to the request instance used by the last request.
162
+ attr_reader :request
163
+
164
+ # A reference to the response instance used by the last request.
165
+ attr_reader :response
166
+
167
+ # A running counter of the number of requests processed.
168
+ attr_accessor :request_count
169
+
170
+ # Create and initialize a new Session instance.
171
+ def initialize(app)
172
+ @app = app
173
+ reset!
174
+ end
175
+
176
+ # Resets the instance. This can be used to reset the state information
177
+ # in an existing session instance, so it can be used from a clean-slate
178
+ # condition.
179
+ #
180
+ # session.reset!
181
+ def reset!
182
+ @https = false
183
+ @mock_session = Rack::MockSession.new(@app, DEFAULT_HOST)
184
+ @controller = @request = @response = nil
185
+ @request_count = 0
186
+
187
+ self.host = DEFAULT_HOST
188
+ self.remote_addr = "127.0.0.1"
189
+ self.accept = "text/xml,application/xml,application/xhtml+xml," +
190
+ "text/html;q=0.9,text/plain;q=0.8,image/png," +
191
+ "*/*;q=0.5"
192
+
193
+ unless defined? @named_routes_configured
194
+ # install the named routes in this session instance.
195
+ klass = metaclass
196
+ ActionController::Routing::Routes.install_helpers(klass)
197
+
198
+ # the helpers are made protected by default--we make them public for
199
+ # easier access during testing and troubleshooting.
200
+ klass.module_eval { public *ActionController::Routing::Routes.named_routes.helpers }
201
+ @named_routes_configured = true
202
+ end
203
+ end
204
+
205
+ # Specify whether or not the session should mimic a secure HTTPS request.
206
+ #
207
+ # session.https!
208
+ # session.https!(false)
209
+ def https!(flag = true)
210
+ @https = flag
211
+ end
212
+
213
+ # Return +true+ if the session is mimicking a secure HTTPS request.
214
+ #
215
+ # if session.https?
216
+ # ...
217
+ # end
218
+ def https?
219
+ @https
220
+ end
221
+
222
+ # Set the host name to use in the next request.
223
+ #
224
+ # session.host! "www.example.com"
225
+ def host!(name)
226
+ @host = name
227
+ end
228
+
229
+ # Returns the URL for the given options, according to the rules specified
230
+ # in the application's routes.
231
+ def url_for(options)
232
+ controller ?
233
+ controller.url_for(options) :
234
+ generic_url_rewriter.rewrite(options)
235
+ end
236
+
237
+ private
238
+
239
+ # Performs the actual request.
240
+ def process(method, path, parameters = nil, rack_environment = nil)
241
+ if path =~ %r{://}
242
+ location = URI.parse(path)
243
+ https! URI::HTTPS === location if location.scheme
244
+ host! location.host if location.host
245
+ path = location.query ? "#{location.path}?#{location.query}" : location.path
246
+ end
247
+
248
+ [ControllerCapture, ActionController::Testing].each do |mod|
249
+ unless ActionController::Base < mod
250
+ ActionController::Base.class_eval { include mod }
251
+ end
252
+ end
253
+
254
+ env = {
255
+ :method => method,
256
+ :params => parameters,
257
+
258
+ "SERVER_NAME" => host,
259
+ "SERVER_PORT" => (https? ? "443" : "80"),
260
+ "HTTPS" => https? ? "on" : "off",
261
+ "rack.url_scheme" => https? ? "https" : "http",
262
+
263
+ "REQUEST_URI" => path,
264
+ "HTTP_HOST" => host,
265
+ "REMOTE_ADDR" => remote_addr,
266
+ "CONTENT_TYPE" => "application/x-www-form-urlencoded",
267
+ "HTTP_ACCEPT" => accept
268
+ }
269
+
270
+ (rack_environment || {}).each do |key, value|
271
+ env[key] = value
272
+ end
273
+
274
+ session = Rack::Test::Session.new(@mock_session)
275
+
276
+ @controller = ActionController::Base.capture_instantiation do
277
+ session.request(path, env)
278
+ end
279
+
280
+ @request_count += 1
281
+ @request = ActionDispatch::Request.new(session.last_request.env)
282
+ @response = ActionDispatch::TestResponse.from_response(@mock_session.last_response)
283
+ @html_document = nil
284
+
285
+ return response.status
286
+ end
287
+
288
+ # Get a temporary URL writer object
289
+ def generic_url_rewriter
290
+ env = {
291
+ 'REQUEST_METHOD' => "GET",
292
+ 'QUERY_STRING' => "",
293
+ "REQUEST_URI" => "/",
294
+ "HTTP_HOST" => host,
295
+ "SERVER_PORT" => https? ? "443" : "80",
296
+ "HTTPS" => https? ? "on" : "off"
297
+ }
298
+ ActionController::UrlRewriter.new(ActionDispatch::Request.new(env), {})
299
+ end
300
+ end
301
+
302
+ # A module used to extend ActionController::Base, so that integration tests
303
+ # can capture the controller used to satisfy a request.
304
+ module ControllerCapture #:nodoc:
305
+ extend ActiveSupport::Concern
306
+
307
+ included do
308
+ alias_method_chain :initialize, :capture
309
+ end
310
+
311
+ def initialize_with_capture(*args)
312
+ initialize_without_capture
313
+ self.class.last_instantiation ||= self
314
+ end
315
+
316
+ module ClassMethods #:nodoc:
317
+ mattr_accessor :last_instantiation
318
+
319
+ def capture_instantiation
320
+ self.last_instantiation = nil
321
+ yield
322
+ return last_instantiation
323
+ end
324
+ end
325
+ end
326
+
327
+ module Runner
328
+ def app
329
+ @app
330
+ end
331
+
332
+ # Reset the current session. This is useful for testing multiple sessions
333
+ # in a single test case.
334
+ def reset!
335
+ @integration_session = open_session
336
+ end
337
+
338
+ %w(get post put head delete cookies assigns
339
+ xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
340
+ define_method(method) do |*args|
341
+ reset! unless @integration_session
342
+ # reset the html_document variable, but only for new get/post calls
343
+ @html_document = nil unless %w(cookies assigns).include?(method)
344
+ returning @integration_session.__send__(method, *args) do
345
+ copy_session_variables!
346
+ end
347
+ end
348
+ end
349
+
350
+ # Open a new session instance. If a block is given, the new session is
351
+ # yielded to the block before being returned.
352
+ #
353
+ # session = open_session do |sess|
354
+ # sess.extend(CustomAssertions)
355
+ # end
356
+ #
357
+ # By default, a single session is automatically created for you, but you
358
+ # can use this method to open multiple sessions that ought to be tested
359
+ # simultaneously.
360
+ def open_session(app = nil)
361
+ session = Integration::Session.new(app || self.app)
362
+
363
+ # delegate the fixture accessors back to the test instance
364
+ extras = Module.new { attr_accessor :delegate, :test_result }
365
+ if self.class.respond_to?(:fixture_table_names)
366
+ self.class.fixture_table_names.each do |table_name|
367
+ name = table_name.tr(".", "_")
368
+ next unless respond_to?(name)
369
+ extras.__send__(:define_method, name) { |*args|
370
+ delegate.send(name, *args)
371
+ }
372
+ end
373
+ end
374
+
375
+ # delegate add_assertion to the test case
376
+ extras.__send__(:define_method, :add_assertion) {
377
+ test_result.add_assertion
378
+ }
379
+ session.extend(extras)
380
+ session.delegate = self
381
+ session.test_result = @_result
382
+
383
+ yield session if block_given?
384
+ session
385
+ end
386
+
387
+ # Copy the instance variables from the current session instance into the
388
+ # test instance.
389
+ def copy_session_variables! #:nodoc:
390
+ return unless @integration_session
391
+ %w(controller response request).each do |var|
392
+ instance_variable_set("@#{var}", @integration_session.__send__(var))
393
+ end
394
+ end
395
+
396
+ # Delegate unhandled messages to the current session instance.
397
+ def method_missing(sym, *args, &block)
398
+ reset! unless @integration_session
399
+ if @integration_session.respond_to?(sym)
400
+ returning @integration_session.__send__(sym, *args, &block) do
401
+ copy_session_variables!
402
+ end
403
+ else
404
+ super
405
+ end
406
+ end
407
+ end
408
+ end
409
+
410
+ # An IntegrationTest is one that spans multiple controllers and actions,
411
+ # tying them all together to ensure they work together as expected. It tests
412
+ # more completely than either unit or functional tests do, exercising the
413
+ # entire stack, from the dispatcher to the database.
414
+ #
415
+ # At its simplest, you simply extend IntegrationTest and write your tests
416
+ # using the get/post methods:
417
+ #
418
+ # require "#{File.dirname(__FILE__)}/test_helper"
419
+ #
420
+ # class ExampleTest < ActionController::IntegrationTest
421
+ # fixtures :people
422
+ #
423
+ # def test_login
424
+ # # get the login page
425
+ # get "/login"
426
+ # assert_equal 200, status
427
+ #
428
+ # # post the login and follow through to the home page
429
+ # post "/login", :username => people(:jamis).username,
430
+ # :password => people(:jamis).password
431
+ # follow_redirect!
432
+ # assert_equal 200, status
433
+ # assert_equal "/home", path
434
+ # end
435
+ # end
436
+ #
437
+ # However, you can also have multiple session instances open per test, and
438
+ # even extend those instances with assertions and methods to create a very
439
+ # powerful testing DSL that is specific for your application. You can even
440
+ # reference any named routes you happen to have defined!
441
+ #
442
+ # require "#{File.dirname(__FILE__)}/test_helper"
443
+ #
444
+ # class AdvancedTest < ActionController::IntegrationTest
445
+ # fixtures :people, :rooms
446
+ #
447
+ # def test_login_and_speak
448
+ # jamis, david = login(:jamis), login(:david)
449
+ # room = rooms(:office)
450
+ #
451
+ # jamis.enter(room)
452
+ # jamis.speak(room, "anybody home?")
453
+ #
454
+ # david.enter(room)
455
+ # david.speak(room, "hello!")
456
+ # end
457
+ #
458
+ # private
459
+ #
460
+ # module CustomAssertions
461
+ # def enter(room)
462
+ # # reference a named route, for maximum internal consistency!
463
+ # get(room_url(:id => room.id))
464
+ # assert(...)
465
+ # ...
466
+ # end
467
+ #
468
+ # def speak(room, message)
469
+ # xml_http_request "/say/#{room.id}", :message => message
470
+ # assert(...)
471
+ # ...
472
+ # end
473
+ # end
474
+ #
475
+ # def login(who)
476
+ # open_session do |sess|
477
+ # sess.extend(CustomAssertions)
478
+ # who = people(who)
479
+ # sess.post "/login", :username => who.username,
480
+ # :password => who.password
481
+ # assert(...)
482
+ # end
483
+ # end
484
+ # end
485
+ class IntegrationTest < ActiveSupport::TestCase
486
+ include Integration::Runner
487
+
488
+ @@app = nil
489
+
490
+ def self.app
491
+ # DEPRECATE Rails application fallback
492
+ # This should be set by the initializer
493
+ @@app || (defined?(Rails.application) && Rails.application) || nil
494
+ end
495
+
496
+ def self.app=(app)
497
+ @@app = app
498
+ end
499
+
500
+ def app
501
+ super || self.class.app
502
+ end
503
+ end
504
+ end