actionpack-rack-upgrade-2 2.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (441) hide show
  1. data/CHANGELOG +5250 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +409 -0
  4. data/RUNNING_UNIT_TESTS +24 -0
  5. data/Rakefile +158 -0
  6. data/install.rb +30 -0
  7. data/lib/action_controller.rb +113 -0
  8. data/lib/action_controller/assertions/dom_assertions.rb +55 -0
  9. data/lib/action_controller/assertions/model_assertions.rb +21 -0
  10. data/lib/action_controller/assertions/response_assertions.rb +169 -0
  11. data/lib/action_controller/assertions/routing_assertions.rb +146 -0
  12. data/lib/action_controller/assertions/selector_assertions.rb +638 -0
  13. data/lib/action_controller/assertions/tag_assertions.rb +127 -0
  14. data/lib/action_controller/base.rb +1425 -0
  15. data/lib/action_controller/benchmarking.rb +107 -0
  16. data/lib/action_controller/caching.rb +71 -0
  17. data/lib/action_controller/caching/actions.rb +177 -0
  18. data/lib/action_controller/caching/fragments.rb +120 -0
  19. data/lib/action_controller/caching/pages.rb +152 -0
  20. data/lib/action_controller/caching/sweeper.rb +45 -0
  21. data/lib/action_controller/caching/sweeping.rb +55 -0
  22. data/lib/action_controller/cgi_ext.rb +15 -0
  23. data/lib/action_controller/cgi_ext/cookie.rb +112 -0
  24. data/lib/action_controller/cgi_ext/query_extension.rb +22 -0
  25. data/lib/action_controller/cgi_ext/stdinput.rb +24 -0
  26. data/lib/action_controller/cgi_process.rb +77 -0
  27. data/lib/action_controller/cookies.rb +197 -0
  28. data/lib/action_controller/dispatcher.rb +133 -0
  29. data/lib/action_controller/failsafe.rb +87 -0
  30. data/lib/action_controller/filters.rb +680 -0
  31. data/lib/action_controller/flash.rb +213 -0
  32. data/lib/action_controller/headers.rb +33 -0
  33. data/lib/action_controller/helpers.rb +225 -0
  34. data/lib/action_controller/http_authentication.rb +309 -0
  35. data/lib/action_controller/integration.rb +708 -0
  36. data/lib/action_controller/layout.rb +286 -0
  37. data/lib/action_controller/middleware_stack.rb +119 -0
  38. data/lib/action_controller/middlewares.rb +14 -0
  39. data/lib/action_controller/mime_responds.rb +193 -0
  40. data/lib/action_controller/mime_type.rb +212 -0
  41. data/lib/action_controller/mime_types.rb +21 -0
  42. data/lib/action_controller/params_parser.rb +77 -0
  43. data/lib/action_controller/performance_test.rb +15 -0
  44. data/lib/action_controller/polymorphic_routes.rb +189 -0
  45. data/lib/action_controller/rack_lint_patch.rb +36 -0
  46. data/lib/action_controller/record_identifier.rb +104 -0
  47. data/lib/action_controller/reloader.rb +54 -0
  48. data/lib/action_controller/request.rb +495 -0
  49. data/lib/action_controller/request_forgery_protection.rb +116 -0
  50. data/lib/action_controller/rescue.rb +183 -0
  51. data/lib/action_controller/resources.rb +682 -0
  52. data/lib/action_controller/response.rb +237 -0
  53. data/lib/action_controller/routing.rb +388 -0
  54. data/lib/action_controller/routing/builder.rb +197 -0
  55. data/lib/action_controller/routing/optimisations.rb +130 -0
  56. data/lib/action_controller/routing/recognition_optimisation.rb +167 -0
  57. data/lib/action_controller/routing/route.rb +265 -0
  58. data/lib/action_controller/routing/route_set.rb +503 -0
  59. data/lib/action_controller/routing/routing_ext.rb +49 -0
  60. data/lib/action_controller/routing/segments.rb +343 -0
  61. data/lib/action_controller/session/abstract_store.rb +276 -0
  62. data/lib/action_controller/session/cookie_store.rb +240 -0
  63. data/lib/action_controller/session/mem_cache_store.rb +60 -0
  64. data/lib/action_controller/session_management.rb +54 -0
  65. data/lib/action_controller/status_codes.rb +88 -0
  66. data/lib/action_controller/streaming.rb +181 -0
  67. data/lib/action_controller/string_coercion.rb +29 -0
  68. data/lib/action_controller/templates/rescues/_request_and_response.erb +24 -0
  69. data/lib/action_controller/templates/rescues/_trace.erb +26 -0
  70. data/lib/action_controller/templates/rescues/diagnostics.erb +11 -0
  71. data/lib/action_controller/templates/rescues/layout.erb +29 -0
  72. data/lib/action_controller/templates/rescues/missing_template.erb +2 -0
  73. data/lib/action_controller/templates/rescues/routing_error.erb +10 -0
  74. data/lib/action_controller/templates/rescues/template_error.erb +21 -0
  75. data/lib/action_controller/templates/rescues/unknown_action.erb +2 -0
  76. data/lib/action_controller/test_case.rb +209 -0
  77. data/lib/action_controller/test_process.rb +580 -0
  78. data/lib/action_controller/translation.rb +13 -0
  79. data/lib/action_controller/uploaded_file.rb +44 -0
  80. data/lib/action_controller/url_rewriter.rb +229 -0
  81. data/lib/action_controller/vendor/html-scanner.rb +16 -0
  82. data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
  83. data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
  84. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +173 -0
  85. data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
  86. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
  87. data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
  88. data/lib/action_controller/verification.rb +130 -0
  89. data/lib/action_pack.rb +24 -0
  90. data/lib/action_pack/version.rb +9 -0
  91. data/lib/action_view.rb +58 -0
  92. data/lib/action_view/base.rb +362 -0
  93. data/lib/action_view/helpers.rb +61 -0
  94. data/lib/action_view/helpers/active_record_helper.rb +305 -0
  95. data/lib/action_view/helpers/asset_tag_helper.rb +695 -0
  96. data/lib/action_view/helpers/atom_feed_helper.rb +198 -0
  97. data/lib/action_view/helpers/benchmark_helper.rb +54 -0
  98. data/lib/action_view/helpers/cache_helper.rb +39 -0
  99. data/lib/action_view/helpers/capture_helper.rb +136 -0
  100. data/lib/action_view/helpers/csrf_helper.rb +14 -0
  101. data/lib/action_view/helpers/date_helper.rb +989 -0
  102. data/lib/action_view/helpers/debug_helper.rb +38 -0
  103. data/lib/action_view/helpers/form_helper.rb +1118 -0
  104. data/lib/action_view/helpers/form_options_helper.rb +599 -0
  105. data/lib/action_view/helpers/form_tag_helper.rb +490 -0
  106. data/lib/action_view/helpers/javascript_helper.rb +208 -0
  107. data/lib/action_view/helpers/number_helper.rb +308 -0
  108. data/lib/action_view/helpers/prototype_helper.rb +1305 -0
  109. data/lib/action_view/helpers/raw_output_helper.rb +9 -0
  110. data/lib/action_view/helpers/record_identification_helper.rb +20 -0
  111. data/lib/action_view/helpers/record_tag_helper.rb +58 -0
  112. data/lib/action_view/helpers/sanitize_helper.rb +251 -0
  113. data/lib/action_view/helpers/scriptaculous_helper.rb +226 -0
  114. data/lib/action_view/helpers/tag_helper.rb +151 -0
  115. data/lib/action_view/helpers/text_helper.rb +597 -0
  116. data/lib/action_view/helpers/translation_helper.rb +67 -0
  117. data/lib/action_view/helpers/url_helper.rb +637 -0
  118. data/lib/action_view/inline_template.rb +19 -0
  119. data/lib/action_view/locale/en.yml +117 -0
  120. data/lib/action_view/partials.rb +241 -0
  121. data/lib/action_view/paths.rb +77 -0
  122. data/lib/action_view/reloadable_template.rb +117 -0
  123. data/lib/action_view/renderable.rb +109 -0
  124. data/lib/action_view/renderable_partial.rb +53 -0
  125. data/lib/action_view/template.rb +252 -0
  126. data/lib/action_view/template_error.rb +99 -0
  127. data/lib/action_view/template_handler.rb +34 -0
  128. data/lib/action_view/template_handlers.rb +48 -0
  129. data/lib/action_view/template_handlers/builder.rb +17 -0
  130. data/lib/action_view/template_handlers/erb.rb +25 -0
  131. data/lib/action_view/template_handlers/rjs.rb +13 -0
  132. data/lib/action_view/test_case.rb +162 -0
  133. data/lib/actionpack.rb +2 -0
  134. data/test/abstract_unit.rb +78 -0
  135. data/test/active_record_unit.rb +104 -0
  136. data/test/activerecord/active_record_store_test.rb +221 -0
  137. data/test/activerecord/render_partial_with_record_identification_test.rb +188 -0
  138. data/test/adv_attr_test.rb +20 -0
  139. data/test/controller/action_pack_assertions_test.rb +545 -0
  140. data/test/controller/addresses_render_test.rb +37 -0
  141. data/test/controller/assert_select_test.rb +735 -0
  142. data/test/controller/base_test.rb +217 -0
  143. data/test/controller/benchmark_test.rb +32 -0
  144. data/test/controller/caching_test.rb +743 -0
  145. data/test/controller/capture_test.rb +66 -0
  146. data/test/controller/content_type_test.rb +178 -0
  147. data/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb +0 -0
  148. data/test/controller/controller_fixtures/app/controllers/user_controller.rb +0 -0
  149. data/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb +0 -0
  150. data/test/controller/cookie_test.rb +208 -0
  151. data/test/controller/deprecation/deprecated_base_methods_test.rb +32 -0
  152. data/test/controller/dispatcher_test.rb +144 -0
  153. data/test/controller/dom_assertions_test.rb +53 -0
  154. data/test/controller/failsafe_test.rb +60 -0
  155. data/test/controller/fake_controllers.rb +33 -0
  156. data/test/controller/fake_models.rb +19 -0
  157. data/test/controller/filter_params_test.rb +52 -0
  158. data/test/controller/filters_test.rb +885 -0
  159. data/test/controller/flash_test.rb +174 -0
  160. data/test/controller/header_test.rb +14 -0
  161. data/test/controller/helper_test.rb +224 -0
  162. data/test/controller/html-scanner/cdata_node_test.rb +15 -0
  163. data/test/controller/html-scanner/document_test.rb +148 -0
  164. data/test/controller/html-scanner/node_test.rb +89 -0
  165. data/test/controller/html-scanner/sanitizer_test.rb +281 -0
  166. data/test/controller/html-scanner/tag_node_test.rb +238 -0
  167. data/test/controller/html-scanner/text_node_test.rb +50 -0
  168. data/test/controller/html-scanner/tokenizer_test.rb +131 -0
  169. data/test/controller/http_basic_authentication_test.rb +113 -0
  170. data/test/controller/http_digest_authentication_test.rb +254 -0
  171. data/test/controller/integration_test.rb +526 -0
  172. data/test/controller/layout_test.rb +215 -0
  173. data/test/controller/localized_templates_test.rb +24 -0
  174. data/test/controller/logging_test.rb +46 -0
  175. data/test/controller/middleware_stack_test.rb +90 -0
  176. data/test/controller/mime_responds_test.rb +536 -0
  177. data/test/controller/mime_type_test.rb +93 -0
  178. data/test/controller/output_escaping_test.rb +19 -0
  179. data/test/controller/polymorphic_routes_test.rb +297 -0
  180. data/test/controller/rack_test.rb +308 -0
  181. data/test/controller/record_identifier_test.rb +139 -0
  182. data/test/controller/redirect_test.rb +285 -0
  183. data/test/controller/reloader_test.rb +125 -0
  184. data/test/controller/render_test.rb +1783 -0
  185. data/test/controller/request/json_params_parsing_test.rb +65 -0
  186. data/test/controller/request/multipart_params_parsing_test.rb +177 -0
  187. data/test/controller/request/query_string_parsing_test.rb +120 -0
  188. data/test/controller/request/test_request_test.rb +35 -0
  189. data/test/controller/request/url_encoded_params_parsing_test.rb +146 -0
  190. data/test/controller/request/xml_params_parsing_test.rb +103 -0
  191. data/test/controller/request_forgery_protection_test.rb +233 -0
  192. data/test/controller/request_test.rb +398 -0
  193. data/test/controller/rescue_test.rb +541 -0
  194. data/test/controller/resources_test.rb +1393 -0
  195. data/test/controller/routing_test.rb +2592 -0
  196. data/test/controller/selector_test.rb +628 -0
  197. data/test/controller/send_file_test.rb +171 -0
  198. data/test/controller/session/abstract_store_test.rb +64 -0
  199. data/test/controller/session/cookie_store_test.rb +354 -0
  200. data/test/controller/session/mem_cache_store_test.rb +187 -0
  201. data/test/controller/session/test_session_test.rb +58 -0
  202. data/test/controller/test_test.rb +700 -0
  203. data/test/controller/translation_test.rb +26 -0
  204. data/test/controller/url_rewriter_test.rb +395 -0
  205. data/test/controller/verification_test.rb +270 -0
  206. data/test/controller/view_paths_test.rb +141 -0
  207. data/test/controller/webservice_test.rb +273 -0
  208. data/test/fixtures/_top_level_partial.html.erb +1 -0
  209. data/test/fixtures/_top_level_partial_only.erb +1 -0
  210. data/test/fixtures/addresses/list.erb +1 -0
  211. data/test/fixtures/alternate_helpers/foo_helper.rb +3 -0
  212. data/test/fixtures/bad_customers/_bad_customer.html.erb +1 -0
  213. data/test/fixtures/companies.yml +24 -0
  214. data/test/fixtures/company.rb +10 -0
  215. data/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml +1 -0
  216. data/test/fixtures/content_type/render_default_for_rhtml.rhtml +1 -0
  217. data/test/fixtures/content_type/render_default_for_rjs.rjs +1 -0
  218. data/test/fixtures/content_type/render_default_for_rxml.rxml +1 -0
  219. data/test/fixtures/customers/_customer.html.erb +1 -0
  220. data/test/fixtures/db_definitions/sqlite.sql +49 -0
  221. data/test/fixtures/developer.rb +9 -0
  222. data/test/fixtures/developers.yml +21 -0
  223. data/test/fixtures/developers/_developer.erb +1 -0
  224. data/test/fixtures/developers_projects.yml +13 -0
  225. data/test/fixtures/failsafe/500.html +1 -0
  226. data/test/fixtures/fun/games/_game.erb +1 -0
  227. data/test/fixtures/fun/games/hello_world.erb +1 -0
  228. data/test/fixtures/fun/serious/games/_game.erb +1 -0
  229. data/test/fixtures/functional_caching/_partial.erb +3 -0
  230. data/test/fixtures/functional_caching/formatted_fragment_cached.html.erb +3 -0
  231. data/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs +6 -0
  232. data/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder +5 -0
  233. data/test/fixtures/functional_caching/fragment_cached.html.erb +2 -0
  234. data/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb +1 -0
  235. data/test/fixtures/functional_caching/inline_fragment_cached.html.erb +2 -0
  236. data/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs +1 -0
  237. data/test/fixtures/good_customers/_good_customer.html.erb +1 -0
  238. data/test/fixtures/helpers/abc_helper.rb +5 -0
  239. data/test/fixtures/helpers/fun/games_helper.rb +3 -0
  240. data/test/fixtures/helpers/fun/pdf_helper.rb +3 -0
  241. data/test/fixtures/layout_tests/abs_path_layout.rhtml +1 -0
  242. data/test/fixtures/layout_tests/alt/hello.rhtml +1 -0
  243. data/test/fixtures/layout_tests/alt/layouts/alt.rhtml +0 -0
  244. data/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml +1 -0
  245. data/test/fixtures/layout_tests/layouts/item.rhtml +1 -0
  246. data/test/fixtures/layout_tests/layouts/layout_test.rhtml +1 -0
  247. data/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb +1 -0
  248. data/test/fixtures/layout_tests/layouts/third_party_template_library.mab +1 -0
  249. data/test/fixtures/layout_tests/views/hello.rhtml +1 -0
  250. data/test/fixtures/layouts/_column.html.erb +2 -0
  251. data/test/fixtures/layouts/block_with_layout.erb +3 -0
  252. data/test/fixtures/layouts/builder.builder +3 -0
  253. data/test/fixtures/layouts/default_html.html.erb +1 -0
  254. data/test/fixtures/layouts/partial_with_layout.erb +3 -0
  255. data/test/fixtures/layouts/standard.erb +1 -0
  256. data/test/fixtures/layouts/talk_from_action.erb +2 -0
  257. data/test/fixtures/layouts/xhr.html.erb +2 -0
  258. data/test/fixtures/layouts/yield.erb +2 -0
  259. data/test/fixtures/localized/hello_world.de.html +1 -0
  260. data/test/fixtures/localized/hello_world.en.html +1 -0
  261. data/test/fixtures/mascot.rb +3 -0
  262. data/test/fixtures/mascots.yml +4 -0
  263. data/test/fixtures/mascots/_mascot.html.erb +1 -0
  264. data/test/fixtures/multipart/binary_file +0 -0
  265. data/test/fixtures/multipart/boundary_problem_file +10 -0
  266. data/test/fixtures/multipart/bracketed_param +5 -0
  267. data/test/fixtures/multipart/empty +10 -0
  268. data/test/fixtures/multipart/hello.txt +1 -0
  269. data/test/fixtures/multipart/large_text_file +10 -0
  270. data/test/fixtures/multipart/mixed_files +0 -0
  271. data/test/fixtures/multipart/mona_lisa.jpg +0 -0
  272. data/test/fixtures/multipart/none +9 -0
  273. data/test/fixtures/multipart/single_parameter +5 -0
  274. data/test/fixtures/multipart/text_file +10 -0
  275. data/test/fixtures/override/test/hello_world.erb +1 -0
  276. data/test/fixtures/override2/layouts/test/sub.erb +1 -0
  277. data/test/fixtures/post_test/layouts/post.html.erb +1 -0
  278. data/test/fixtures/post_test/layouts/super_post.iphone.erb +1 -0
  279. data/test/fixtures/post_test/post/index.html.erb +1 -0
  280. data/test/fixtures/post_test/post/index.iphone.erb +1 -0
  281. data/test/fixtures/post_test/super_post/index.html.erb +1 -0
  282. data/test/fixtures/post_test/super_post/index.iphone.erb +1 -0
  283. data/test/fixtures/project.rb +3 -0
  284. data/test/fixtures/projects.yml +7 -0
  285. data/test/fixtures/projects/_project.erb +1 -0
  286. data/test/fixtures/public/404.html +1 -0
  287. data/test/fixtures/public/500.da.html +1 -0
  288. data/test/fixtures/public/500.html +1 -0
  289. data/test/fixtures/public/absolute/test.css +23 -0
  290. data/test/fixtures/public/absolute/test.js +63 -0
  291. data/test/fixtures/public/images/rails.png +0 -0
  292. data/test/fixtures/public/javascripts/application.js +1 -0
  293. data/test/fixtures/public/javascripts/bank.js +1 -0
  294. data/test/fixtures/public/javascripts/controls.js +1 -0
  295. data/test/fixtures/public/javascripts/dragdrop.js +1 -0
  296. data/test/fixtures/public/javascripts/effects.js +1 -0
  297. data/test/fixtures/public/javascripts/prototype.js +1 -0
  298. data/test/fixtures/public/javascripts/robber.js +1 -0
  299. data/test/fixtures/public/javascripts/subdir/subdir.js +1 -0
  300. data/test/fixtures/public/javascripts/version.1.0.js +1 -0
  301. data/test/fixtures/public/stylesheets/bank.css +1 -0
  302. data/test/fixtures/public/stylesheets/robber.css +1 -0
  303. data/test/fixtures/public/stylesheets/subdir/subdir.css +1 -0
  304. data/test/fixtures/public/stylesheets/version.1.0.css +1 -0
  305. data/test/fixtures/quiz/questions/_question.html.erb +1 -0
  306. data/test/fixtures/replies.yml +15 -0
  307. data/test/fixtures/replies/_reply.erb +1 -0
  308. data/test/fixtures/reply.rb +7 -0
  309. data/test/fixtures/respond_to/all_types_with_layout.html.erb +1 -0
  310. data/test/fixtures/respond_to/all_types_with_layout.js.rjs +1 -0
  311. data/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb +1 -0
  312. data/test/fixtures/respond_to/iphone_with_html_response_type.html.erb +1 -0
  313. data/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb +1 -0
  314. data/test/fixtures/respond_to/layouts/missing.html.erb +1 -0
  315. data/test/fixtures/respond_to/layouts/standard.html.erb +1 -0
  316. data/test/fixtures/respond_to/layouts/standard.iphone.erb +1 -0
  317. data/test/fixtures/respond_to/using_defaults.html.erb +1 -0
  318. data/test/fixtures/respond_to/using_defaults.js.rjs +1 -0
  319. data/test/fixtures/respond_to/using_defaults.xml.builder +1 -0
  320. data/test/fixtures/respond_to/using_defaults_with_type_list.html.erb +1 -0
  321. data/test/fixtures/respond_to/using_defaults_with_type_list.js.rjs +1 -0
  322. data/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder +1 -0
  323. data/test/fixtures/scope/test/modgreet.erb +1 -0
  324. data/test/fixtures/session_autoload_test/session_autoload_test/foo.rb +10 -0
  325. data/test/fixtures/shared.html.erb +1 -0
  326. data/test/fixtures/symlink_parent/symlinked_layout.erb +5 -0
  327. data/test/fixtures/test/_counter.html.erb +1 -0
  328. data/test/fixtures/test/_customer.erb +1 -0
  329. data/test/fixtures/test/_customer_counter.erb +1 -0
  330. data/test/fixtures/test/_customer_counter_with_as.erb +1 -0
  331. data/test/fixtures/test/_customer_greeting.erb +1 -0
  332. data/test/fixtures/test/_customer_with_var.erb +1 -0
  333. data/test/fixtures/test/_form.erb +1 -0
  334. data/test/fixtures/test/_from_helper.erb +1 -0
  335. data/test/fixtures/test/_hash_greeting.erb +1 -0
  336. data/test/fixtures/test/_hash_object.erb +2 -0
  337. data/test/fixtures/test/_hello.builder +1 -0
  338. data/test/fixtures/test/_labelling_form.erb +1 -0
  339. data/test/fixtures/test/_layout_for_block_with_args.html.erb +3 -0
  340. data/test/fixtures/test/_layout_for_partial.html.erb +3 -0
  341. data/test/fixtures/test/_local_inspector.html.erb +1 -0
  342. data/test/fixtures/test/_one.html.erb +1 -0
  343. data/test/fixtures/test/_partial.erb +1 -0
  344. data/test/fixtures/test/_partial.html.erb +1 -0
  345. data/test/fixtures/test/_partial.js.erb +1 -0
  346. data/test/fixtures/test/_partial_for_use_in_layout.html.erb +1 -0
  347. data/test/fixtures/test/_partial_only.erb +1 -0
  348. data/test/fixtures/test/_partial_with_only_html_version.html.erb +1 -0
  349. data/test/fixtures/test/_person.erb +2 -0
  350. data/test/fixtures/test/_raise.html.erb +1 -0
  351. data/test/fixtures/test/_two.html.erb +1 -0
  352. data/test/fixtures/test/_utf8_partial.html.erb +1 -0
  353. data/test/fixtures/test/_utf8_partial_magic.html.erb +2 -0
  354. data/test/fixtures/test/action_talk_to_layout.erb +2 -0
  355. data/test/fixtures/test/array_translation.erb +1 -0
  356. data/test/fixtures/test/calling_partial_with_layout.html.erb +1 -0
  357. data/test/fixtures/test/capturing.erb +4 -0
  358. data/test/fixtures/test/content_for.erb +2 -0
  359. data/test/fixtures/test/content_for_concatenated.erb +3 -0
  360. data/test/fixtures/test/content_for_with_parameter.erb +2 -0
  361. data/test/fixtures/test/delete_with_js.rjs +2 -0
  362. data/test/fixtures/test/dont_pick_me +1 -0
  363. data/test/fixtures/test/dot.directory/render_file_with_ivar.erb +1 -0
  364. data/test/fixtures/test/enum_rjs_test.rjs +6 -0
  365. data/test/fixtures/test/formatted_html_erb.html.erb +1 -0
  366. data/test/fixtures/test/formatted_xml_erb.builder +1 -0
  367. data/test/fixtures/test/formatted_xml_erb.html.erb +1 -0
  368. data/test/fixtures/test/formatted_xml_erb.xml.erb +1 -0
  369. data/test/fixtures/test/greeting.erb +1 -0
  370. data/test/fixtures/test/greeting.js.rjs +1 -0
  371. data/test/fixtures/test/hello.builder +4 -0
  372. data/test/fixtures/test/hello_world.da.html.erb +1 -0
  373. data/test/fixtures/test/hello_world.erb +1 -0
  374. data/test/fixtures/test/hello_world.erb~ +1 -0
  375. data/test/fixtures/test/hello_world.pt-BR.html.erb +1 -0
  376. data/test/fixtures/test/hello_world_container.builder +3 -0
  377. data/test/fixtures/test/hello_world_from_rxml.builder +4 -0
  378. data/test/fixtures/test/hello_world_with_layout_false.erb +1 -0
  379. data/test/fixtures/test/hello_xml_world.builder +11 -0
  380. data/test/fixtures/test/hyphen-ated.erb +1 -0
  381. data/test/fixtures/test/implicit_content_type.atom.builder +2 -0
  382. data/test/fixtures/test/list.erb +1 -0
  383. data/test/fixtures/test/malformed/malformed.en.html.erb~ +1 -0
  384. data/test/fixtures/test/malformed/malformed.erb~ +1 -0
  385. data/test/fixtures/test/malformed/malformed.html.erb~ +1 -0
  386. data/test/fixtures/test/nested_layout.erb +3 -0
  387. data/test/fixtures/test/non_erb_block_content_for.builder +4 -0
  388. data/test/fixtures/test/potential_conflicts.erb +4 -0
  389. data/test/fixtures/test/render_explicit_html_template.js.rjs +1 -0
  390. data/test/fixtures/test/render_file_from_template.html.erb +1 -0
  391. data/test/fixtures/test/render_file_with_ivar.erb +1 -0
  392. data/test/fixtures/test/render_file_with_locals.erb +1 -0
  393. data/test/fixtures/test/render_implicit_html_template.js.rjs +1 -0
  394. data/test/fixtures/test/render_implicit_html_template_from_xhr_request.da.html.erb +1 -0
  395. data/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb +1 -0
  396. data/test/fixtures/test/render_implicit_js_template_without_layout.js.erb +1 -0
  397. data/test/fixtures/test/render_to_string_test.erb +1 -0
  398. data/test/fixtures/test/scoped_array_translation.erb +1 -0
  399. data/test/fixtures/test/sub_template_raise.html.erb +1 -0
  400. data/test/fixtures/test/template.erb +1 -0
  401. data/test/fixtures/test/translation.erb +1 -0
  402. data/test/fixtures/test/update_element_with_capture.erb +9 -0
  403. data/test/fixtures/test/using_layout_around_block.html.erb +1 -0
  404. data/test/fixtures/test/using_layout_around_block_with_args.html.erb +1 -0
  405. data/test/fixtures/test/utf8.html.erb +4 -0
  406. data/test/fixtures/test/utf8_magic.html.erb +5 -0
  407. data/test/fixtures/test/utf8_magic_with_bare_partial.html.erb +5 -0
  408. data/test/fixtures/topic.rb +3 -0
  409. data/test/fixtures/topics.yml +22 -0
  410. data/test/fixtures/topics/_topic.html.erb +1 -0
  411. data/test/template/active_record_helper_i18n_test.rb +51 -0
  412. data/test/template/active_record_helper_test.rb +302 -0
  413. data/test/template/asset_tag_helper_test.rb +770 -0
  414. data/test/template/atom_feed_helper_test.rb +315 -0
  415. data/test/template/benchmark_helper_test.rb +86 -0
  416. data/test/template/compiled_templates_test.rb +204 -0
  417. data/test/template/date_helper_i18n_test.rb +121 -0
  418. data/test/template/date_helper_test.rb +2603 -0
  419. data/test/template/erb_util_test.rb +36 -0
  420. data/test/template/form_helper_test.rb +1447 -0
  421. data/test/template/form_options_helper_i18n_test.rb +27 -0
  422. data/test/template/form_options_helper_test.rb +811 -0
  423. data/test/template/form_tag_helper_test.rb +356 -0
  424. data/test/template/javascript_helper_test.rb +106 -0
  425. data/test/template/number_helper_i18n_test.rb +69 -0
  426. data/test/template/number_helper_test.rb +132 -0
  427. data/test/template/prototype_helper_test.rb +639 -0
  428. data/test/template/raw_output_helper_test.rb +21 -0
  429. data/test/template/record_tag_helper_test.rb +58 -0
  430. data/test/template/render_test.rb +329 -0
  431. data/test/template/sanitize_helper_test.rb +57 -0
  432. data/test/template/scriptaculous_helper_test.rb +90 -0
  433. data/test/template/tag_helper_test.rb +98 -0
  434. data/test/template/template_test.rb +32 -0
  435. data/test/template/test_test.rb +54 -0
  436. data/test/template/text_helper_test.rb +597 -0
  437. data/test/template/translation_helper_test.rb +95 -0
  438. data/test/template/url_helper_test.rb +641 -0
  439. data/test/testing_sandbox.rb +15 -0
  440. data/test/view/test_case_test.rb +176 -0
  441. metadata +519 -0
@@ -0,0 +1,309 @@
1
+ module ActionController
2
+ module HttpAuthentication
3
+ # Makes it dead easy to do HTTP Basic authentication.
4
+ #
5
+ # Simple Basic example:
6
+ #
7
+ # class PostsController < ApplicationController
8
+ # USER_NAME, PASSWORD = "dhh", "secret"
9
+ #
10
+ # before_filter :authenticate, :except => [ :index ]
11
+ #
12
+ # def index
13
+ # render :text => "Everyone can see me!"
14
+ # end
15
+ #
16
+ # def edit
17
+ # render :text => "I'm only accessible if you know the password"
18
+ # end
19
+ #
20
+ # private
21
+ # def authenticate
22
+ # authenticate_or_request_with_http_basic do |user_name, password|
23
+ # user_name == USER_NAME && password == PASSWORD
24
+ # end
25
+ # end
26
+ # end
27
+ #
28
+ #
29
+ # Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication,
30
+ # the regular HTML interface is protected by a session approach:
31
+ #
32
+ # class ApplicationController < ActionController::Base
33
+ # before_filter :set_account, :authenticate
34
+ #
35
+ # protected
36
+ # def set_account
37
+ # @account = Account.find_by_url_name(request.subdomains.first)
38
+ # end
39
+ #
40
+ # def authenticate
41
+ # case request.format
42
+ # when Mime::XML, Mime::ATOM
43
+ # if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
44
+ # @current_user = user
45
+ # else
46
+ # request_http_basic_authentication
47
+ # end
48
+ # else
49
+ # if session_authenticated?
50
+ # @current_user = @account.users.find(session[:authenticated][:user_id])
51
+ # else
52
+ # redirect_to(login_url) and return false
53
+ # end
54
+ # end
55
+ # end
56
+ # end
57
+ #
58
+ # In your integration tests, you can do something like this:
59
+ #
60
+ # def test_access_granted_from_xml
61
+ # get(
62
+ # "/notes/1.xml", nil,
63
+ # :authorization => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
64
+ # )
65
+ #
66
+ # assert_equal 200, status
67
+ # end
68
+ #
69
+ # Simple Digest example:
70
+ #
71
+ # require 'digest/md5'
72
+ # class PostsController < ApplicationController
73
+ # REALM = "SuperSecret"
74
+ # USERS = {"dhh" => "secret", #plain text password
75
+ # "dap" => Digest:MD5::hexdigest(["dap",REALM,"secret"].join(":")) #ha1 digest password
76
+ #
77
+ # before_filter :authenticate, :except => [:index]
78
+ #
79
+ # def index
80
+ # render :text => "Everyone can see me!"
81
+ # end
82
+ #
83
+ # def edit
84
+ # render :text => "I'm only accessible if you know the password"
85
+ # end
86
+ #
87
+ # private
88
+ # def authenticate
89
+ # authenticate_or_request_with_http_digest(REALM) do |username|
90
+ # USERS[username]
91
+ # end
92
+ # end
93
+ # end
94
+ #
95
+ # NOTE: The +authenticate_or_request_with_http_digest+ block must return the user's password or the ha1 digest hash so the framework can appropriately
96
+ # hash to check the user's credentials. Returning +nil+ will cause authentication to fail.
97
+ # Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
98
+ # the password file or database is compromised, the attacker would be able to use the ha1 hash to
99
+ # authenticate as the user at this +realm+, but would not have the user's password to try using at
100
+ # other sites.
101
+ #
102
+ # On shared hosts, Apache sometimes doesn't pass authentication headers to
103
+ # FCGI instances. If your environment matches this description and you cannot
104
+ # authenticate, try this rule in your Apache setup:
105
+ #
106
+ # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
107
+ module Basic
108
+ extend self
109
+
110
+ module ControllerMethods
111
+ def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
112
+ authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
113
+ end
114
+
115
+ def authenticate_with_http_basic(&login_procedure)
116
+ HttpAuthentication::Basic.authenticate(self, &login_procedure)
117
+ end
118
+
119
+ def request_http_basic_authentication(realm = "Application")
120
+ HttpAuthentication::Basic.authentication_request(self, realm)
121
+ end
122
+ end
123
+
124
+ def authenticate(controller, &login_procedure)
125
+ unless authorization(controller.request).blank?
126
+ login_procedure.call(*user_name_and_password(controller.request))
127
+ end
128
+ end
129
+
130
+ def user_name_and_password(request)
131
+ decode_credentials(request).split(/:/, 2)
132
+ end
133
+
134
+ def authorization(request)
135
+ request.env['HTTP_AUTHORIZATION'] ||
136
+ request.env['X-HTTP_AUTHORIZATION'] ||
137
+ request.env['X_HTTP_AUTHORIZATION'] ||
138
+ request.env['REDIRECT_X_HTTP_AUTHORIZATION']
139
+ end
140
+
141
+ def decode_credentials(request)
142
+ ActiveSupport::Base64.decode64(authorization(request).split(' ', 2).last || '')
143
+ end
144
+
145
+ def encode_credentials(user_name, password)
146
+ "Basic #{ActiveSupport::Base64.encode64("#{user_name}:#{password}")}"
147
+ end
148
+
149
+ def authentication_request(controller, realm)
150
+ controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
151
+ controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
152
+ end
153
+ end
154
+
155
+ module Digest
156
+ extend self
157
+
158
+ module ControllerMethods
159
+ def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure)
160
+ authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm)
161
+ end
162
+
163
+ # Authenticate with HTTP Digest, returns true or false
164
+ def authenticate_with_http_digest(realm = "Application", &password_procedure)
165
+ HttpAuthentication::Digest.authenticate(self, realm, &password_procedure)
166
+ end
167
+
168
+ # Render output including the HTTP Digest authentication header
169
+ def request_http_digest_authentication(realm = "Application", message = nil)
170
+ HttpAuthentication::Digest.authentication_request(self, realm, message)
171
+ end
172
+ end
173
+
174
+ # Returns false on a valid response, true otherwise
175
+ def authenticate(controller, realm, &password_procedure)
176
+ authorization(controller.request) && validate_digest_response(controller.request, realm, &password_procedure)
177
+ end
178
+
179
+ def authorization(request)
180
+ request.env['HTTP_AUTHORIZATION'] ||
181
+ request.env['X-HTTP_AUTHORIZATION'] ||
182
+ request.env['X_HTTP_AUTHORIZATION'] ||
183
+ request.env['REDIRECT_X_HTTP_AUTHORIZATION']
184
+ end
185
+
186
+ # Returns false unless the request credentials response value matches the expected value.
187
+ # First try the password as a ha1 digest password. If this fails, then try it as a plain
188
+ # text password.
189
+ def validate_digest_response(request, realm, &password_procedure)
190
+ credentials = decode_credentials_header(request)
191
+ valid_nonce = validate_nonce(request, credentials[:nonce])
192
+
193
+ if valid_nonce && realm == credentials[:realm] && opaque == credentials[:opaque]
194
+ password = password_procedure.call(credentials[:username])
195
+ return false unless password
196
+
197
+ method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
198
+ uri = credentials[:uri][0,1] == '/' ? request.request_uri : request.url
199
+
200
+ [true, false].any? do |password_is_ha1|
201
+ expected = expected_response(method, uri, credentials, password, password_is_ha1)
202
+ expected == credentials[:response]
203
+ end
204
+ end
205
+ end
206
+
207
+ # Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
208
+ # Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead
209
+ # of a plain-text password.
210
+ def expected_response(http_method, uri, credentials, password, password_is_ha1=true)
211
+ ha1 = password_is_ha1 ? password : ha1(credentials, password)
212
+ ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':'))
213
+ ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':'))
214
+ end
215
+
216
+ def ha1(credentials, password)
217
+ ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':'))
218
+ end
219
+
220
+ def encode_credentials(http_method, credentials, password, password_is_ha1)
221
+ credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1)
222
+ "Digest " + credentials.sort_by {|x| x[0].to_s }.inject([]) {|a, v| a << "#{v[0]}='#{v[1]}'" }.join(', ')
223
+ end
224
+
225
+ def decode_credentials_header(request)
226
+ decode_credentials(authorization(request))
227
+ end
228
+
229
+ def decode_credentials(header)
230
+ header.to_s.gsub(/^Digest\s+/,'').split(',').inject({}.with_indifferent_access) do |hash, pair|
231
+ key, value = pair.split('=', 2)
232
+ hash[key.strip] = value.to_s.gsub(/^"|"$/,'').gsub(/'/, '')
233
+ hash
234
+ end
235
+ end
236
+
237
+ def authentication_header(controller, realm)
238
+ controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}")
239
+ end
240
+
241
+ def authentication_request(controller, realm, message = nil)
242
+ message ||= "HTTP Digest: Access denied.\n"
243
+ authentication_header(controller, realm)
244
+ controller.__send__ :render, :text => message, :status => :unauthorized
245
+ end
246
+
247
+ # Uses an MD5 digest based on time to generate a value to be used only once.
248
+ #
249
+ # A server-specified data string which should be uniquely generated each time a 401 response is made.
250
+ # It is recommended that this string be base64 or hexadecimal data.
251
+ # Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
252
+ #
253
+ # The contents of the nonce are implementation dependent.
254
+ # The quality of the implementation depends on a good choice.
255
+ # A nonce might, for example, be constructed as the base 64 encoding of
256
+ #
257
+ # => time-stamp H(time-stamp ":" ETag ":" private-key)
258
+ #
259
+ # where time-stamp is a server-generated time or other non-repeating value,
260
+ # ETag is the value of the HTTP ETag header associated with the requested entity,
261
+ # and private-key is data known only to the server.
262
+ # With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and
263
+ # reject the request if it did not match the nonce from that header or
264
+ # if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity.
265
+ # The inclusion of the ETag prevents a replay request for an updated version of the resource.
266
+ # (Note: including the IP address of the client in the nonce would appear to offer the server the ability
267
+ # to limit the reuse of the nonce to the same client that originally got it.
268
+ # However, that would break proxy farms, where requests from a single user often go through different proxies in the farm.
269
+ # Also, IP address spoofing is not that hard.)
270
+ #
271
+ # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
272
+ # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
273
+ # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
274
+ # of this document.
275
+ #
276
+ # The nonce is opaque to the client. Composed of Time, and hash of Time with secret
277
+ # key from the Rails session secret generated upon creation of project. Ensures
278
+ # the time cannot be modifed by client.
279
+ def nonce(time = Time.now)
280
+ t = time.to_i
281
+ hashed = [t, secret_key]
282
+ digest = ::Digest::MD5.hexdigest(hashed.join(":"))
283
+ Base64.encode64("#{t}:#{digest}").gsub("\n", '')
284
+ end
285
+
286
+ # Might want a shorter timeout depending on whether the request
287
+ # is a PUT or POST, and if client is browser or web service.
288
+ # Can be much shorter if the Stale directive is implemented. This would
289
+ # allow a user to use new nonce without prompting user again for their
290
+ # username and password.
291
+ def validate_nonce(request, value, seconds_to_timeout=5*60)
292
+ return false if value.nil?
293
+ t = Base64.decode64(value).split(":").first.to_i
294
+ nonce(t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
295
+ end
296
+
297
+ # Opaque based on random generation - but changing each request?
298
+ def opaque()
299
+ ::Digest::MD5.hexdigest(secret_key)
300
+ end
301
+
302
+ # Set in /initializers/session_store.rb, and loaded even if sessions are not in use.
303
+ def secret_key
304
+ ActionController::Base.session_options[:secret]
305
+ end
306
+
307
+ end
308
+ end
309
+ end
@@ -0,0 +1,708 @@
1
+ require 'stringio'
2
+ require 'uri'
3
+ require 'active_support/test_case'
4
+ require 'action_controller/rack_lint_patch'
5
+
6
+ module ActionController
7
+ module Integration #:nodoc:
8
+ # An integration Session instance represents a set of requests and responses
9
+ # performed sequentially by some virtual user. Because you can instantiate
10
+ # multiple sessions and run them side-by-side, you can also mimic (to some
11
+ # limited extent) multiple simultaneous users interacting with your system.
12
+ #
13
+ # Typically, you will instantiate a new session using
14
+ # IntegrationTest#open_session, rather than instantiating
15
+ # Integration::Session directly.
16
+ class Session
17
+ include Test::Unit::Assertions
18
+ include ActionController::TestCase::Assertions
19
+ include ActionController::TestProcess
20
+
21
+ # Rack application to use
22
+ attr_accessor :application
23
+
24
+ # The integer HTTP status code of the last request.
25
+ attr_reader :status
26
+
27
+ # The status message that accompanied the status code of the last request.
28
+ attr_reader :status_message
29
+
30
+ # The body of the last request.
31
+ attr_reader :body
32
+
33
+ # The URI of the last request.
34
+ attr_reader :path
35
+
36
+ # The hostname used in the last request.
37
+ attr_accessor :host
38
+
39
+ # The remote_addr used in the last request.
40
+ attr_accessor :remote_addr
41
+
42
+ # The Accept header to send.
43
+ attr_accessor :accept
44
+
45
+ # A map of the cookies returned by the last response, and which will be
46
+ # sent with the next request.
47
+ attr_reader :cookies
48
+
49
+ # A map of the headers returned by the last response.
50
+ attr_reader :headers
51
+
52
+ # A reference to the controller instance used by the last request.
53
+ attr_reader :controller
54
+
55
+ # A reference to the request instance used by the last request.
56
+ attr_reader :request
57
+
58
+ # A reference to the response instance used by the last request.
59
+ attr_reader :response
60
+
61
+ # A running counter of the number of requests processed.
62
+ attr_accessor :request_count
63
+
64
+ class MultiPartNeededException < Exception
65
+ end
66
+
67
+ # Create and initialize a new Session instance.
68
+ def initialize(app = nil)
69
+ @application = app || ActionController::Dispatcher.new
70
+ reset!
71
+ end
72
+
73
+ # Resets the instance. This can be used to reset the state information
74
+ # in an existing session instance, so it can be used from a clean-slate
75
+ # condition.
76
+ #
77
+ # session.reset!
78
+ def reset!
79
+ @status = @path = @headers = nil
80
+ @result = @status_message = nil
81
+ @https = false
82
+ @cookies = {}
83
+ @controller = @request = @response = nil
84
+ @request_count = 0
85
+
86
+ self.host = "www.example.com"
87
+ self.remote_addr = "127.0.0.1"
88
+ self.accept = "text/xml,application/xml,application/xhtml+xml," +
89
+ "text/html;q=0.9,text/plain;q=0.8,image/png," +
90
+ "*/*;q=0.5"
91
+
92
+ unless defined? @named_routes_configured
93
+ # install the named routes in this session instance.
94
+ klass = class << self; self; end
95
+ Routing::Routes.install_helpers(klass)
96
+
97
+ # the helpers are made protected by default--we make them public for
98
+ # easier access during testing and troubleshooting.
99
+ klass.module_eval { public *Routing::Routes.named_routes.helpers }
100
+ @named_routes_configured = true
101
+ end
102
+ end
103
+
104
+ # Specify whether or not the session should mimic a secure HTTPS request.
105
+ #
106
+ # session.https!
107
+ # session.https!(false)
108
+ def https!(flag = true)
109
+ @https = flag
110
+ end
111
+
112
+ # Return +true+ if the session is mimicking a secure HTTPS request.
113
+ #
114
+ # if session.https?
115
+ # ...
116
+ # end
117
+ def https?
118
+ @https
119
+ end
120
+
121
+ # Set the host name to use in the next request.
122
+ #
123
+ # session.host! "www.example.com"
124
+ def host!(name)
125
+ @host = name
126
+ end
127
+
128
+ # Follow a single redirect response. If the last response was not a
129
+ # redirect, an exception will be raised. Otherwise, the redirect is
130
+ # performed on the location header.
131
+ def follow_redirect!
132
+ raise "not a redirect! #{@status} #{@status_message}" unless redirect?
133
+ get(interpret_uri(headers['location']))
134
+ status
135
+ end
136
+
137
+ # Performs a request using the specified method, following any subsequent
138
+ # redirect. Note that the redirects are followed until the response is
139
+ # not a redirect--this means you may run into an infinite loop if your
140
+ # redirect loops back to itself.
141
+ def request_via_redirect(http_method, path, parameters = nil, headers = nil)
142
+ send(http_method, path, parameters, headers)
143
+ follow_redirect! while redirect?
144
+ status
145
+ end
146
+
147
+ # Performs a GET request, following any subsequent redirect.
148
+ # See +request_via_redirect+ for more information.
149
+ def get_via_redirect(path, parameters = nil, headers = nil)
150
+ request_via_redirect(:get, path, parameters, headers)
151
+ end
152
+
153
+ # Performs a POST request, following any subsequent redirect.
154
+ # See +request_via_redirect+ for more information.
155
+ def post_via_redirect(path, parameters = nil, headers = nil)
156
+ request_via_redirect(:post, path, parameters, headers)
157
+ end
158
+
159
+ # Performs a PUT request, following any subsequent redirect.
160
+ # See +request_via_redirect+ for more information.
161
+ def put_via_redirect(path, parameters = nil, headers = nil)
162
+ request_via_redirect(:put, path, parameters, headers)
163
+ end
164
+
165
+ # Performs a DELETE request, following any subsequent redirect.
166
+ # See +request_via_redirect+ for more information.
167
+ def delete_via_redirect(path, parameters = nil, headers = nil)
168
+ request_via_redirect(:delete, path, parameters, headers)
169
+ end
170
+
171
+ # Returns +true+ if the last response was a redirect.
172
+ def redirect?
173
+ status/100 == 3
174
+ end
175
+
176
+ # Performs a GET request with the given parameters.
177
+ #
178
+ # - +path+: The URI (as a String) on which you want to perform a GET
179
+ # request.
180
+ # - +parameters+: The HTTP parameters that you want to pass. This may
181
+ # be +nil+,
182
+ # a Hash, or a String that is appropriately encoded
183
+ # (<tt>application/x-www-form-urlencoded</tt> or
184
+ # <tt>multipart/form-data</tt>).
185
+ # - +headers+: Additional HTTP headers to pass, as a Hash. The keys will
186
+ # automatically be upcased, with the prefix 'HTTP_' added if needed.
187
+ #
188
+ # This method returns an Response object, which one can use to
189
+ # inspect the details of the response. Furthermore, if this method was
190
+ # called from an ActionController::IntegrationTest object, then that
191
+ # object's <tt>@response</tt> instance variable will point to the same
192
+ # response object.
193
+ #
194
+ # You can also perform POST, PUT, DELETE, and HEAD requests with +post+,
195
+ # +put+, +delete+, and +head+.
196
+ def get(path, parameters = nil, headers = nil)
197
+ process :get, path, parameters, headers
198
+ end
199
+
200
+ # Performs a POST request with the given parameters. See get() for more
201
+ # details.
202
+ def post(path, parameters = nil, headers = nil)
203
+ process :post, path, parameters, headers
204
+ end
205
+
206
+ # Performs a PUT request with the given parameters. See get() for more
207
+ # details.
208
+ def put(path, parameters = nil, headers = nil)
209
+ process :put, path, parameters, headers
210
+ end
211
+
212
+ # Performs a DELETE request with the given parameters. See get() for
213
+ # more details.
214
+ def delete(path, parameters = nil, headers = nil)
215
+ process :delete, path, parameters, headers
216
+ end
217
+
218
+ # Performs a HEAD request with the given parameters. See get() for more
219
+ # details.
220
+ def head(path, parameters = nil, headers = nil)
221
+ process :head, path, parameters, headers
222
+ end
223
+
224
+ # Performs an XMLHttpRequest request with the given parameters, mirroring
225
+ # a request from the Prototype library.
226
+ #
227
+ # The request_method is :get, :post, :put, :delete or :head; the
228
+ # parameters are +nil+, a hash, or a url-encoded or multipart string;
229
+ # the headers are a hash. Keys are automatically upcased and prefixed
230
+ # with 'HTTP_' if not already.
231
+ def xml_http_request(request_method, path, parameters = nil, headers = nil)
232
+ headers ||= {}
233
+ headers['X-Requested-With'] = 'XMLHttpRequest'
234
+ headers['Accept'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
235
+ process(request_method, path, parameters, headers)
236
+ end
237
+ alias xhr :xml_http_request
238
+
239
+ # Returns the URL for the given options, according to the rules specified
240
+ # in the application's routes.
241
+ def url_for(options)
242
+ controller ?
243
+ controller.url_for(options) :
244
+ generic_url_rewriter.rewrite(options)
245
+ end
246
+
247
+ private
248
+ # Tailors the session based on the given URI, setting the HTTPS value
249
+ # and the hostname.
250
+ def interpret_uri(path)
251
+ location = URI.parse(path)
252
+ https! URI::HTTPS === location if location.scheme
253
+ host! location.host if location.host
254
+ location.query ? "#{location.path}?#{location.query}" : location.path
255
+ end
256
+
257
+ # Performs the actual request.
258
+ def process(method, path, parameters = nil, headers = nil)
259
+ data = requestify(parameters)
260
+ path = interpret_uri(path) if path =~ %r{://}
261
+ path = "/#{path}" unless path[0] == ?/
262
+ @path = path
263
+ env = {}
264
+
265
+ if method == :get
266
+ env["QUERY_STRING"] = data
267
+ data = nil
268
+ end
269
+
270
+ env["QUERY_STRING"] ||= ""
271
+
272
+ data ||= ''
273
+ data.force_encoding(Encoding::ASCII_8BIT) if data.respond_to?(:force_encoding)
274
+ data = data.is_a?(IO) ? data : StringIO.new(data)
275
+
276
+ env.update(
277
+ "REQUEST_METHOD" => method.to_s.upcase,
278
+ "SERVER_NAME" => host,
279
+ "SERVER_PORT" => (https? ? "443" : "80"),
280
+ "HTTPS" => https? ? "on" : "off",
281
+ "rack.url_scheme" => https? ? "https" : "http",
282
+ "SCRIPT_NAME" => "",
283
+
284
+ "REQUEST_URI" => path,
285
+ "PATH_INFO" => path,
286
+ "HTTP_HOST" => host,
287
+ "REMOTE_ADDR" => remote_addr,
288
+ "CONTENT_TYPE" => "application/x-www-form-urlencoded",
289
+ "CONTENT_LENGTH" => data ? data.length.to_s : nil,
290
+ "HTTP_ACCEPT" => accept,
291
+
292
+ "rack.version" => [0,1],
293
+ "rack.input" => data,
294
+ "rack.errors" => StringIO.new,
295
+ "rack.multithread" => true,
296
+ "rack.multiprocess" => true,
297
+ "rack.run_once" => false
298
+ )
299
+
300
+ env['HTTP_COOKIE'] = encode_cookies if cookies.any?
301
+
302
+ (headers || {}).each do |key, value|
303
+ key = key.to_s.upcase.gsub(/-/, "_")
304
+ key = "HTTP_#{key}" unless env.has_key?(key) || key =~ /^HTTP_/
305
+ env[key] = value
306
+ end
307
+
308
+ [ControllerCapture, ActionController::ProcessWithTest].each do |mod|
309
+ unless ActionController::Base < mod
310
+ ActionController::Base.class_eval { include mod }
311
+ end
312
+ end
313
+
314
+ ActionController::Base.clear_last_instantiation!
315
+
316
+ app = Rack::Lint.new(@application)
317
+ status, headers, body = app.call(env)
318
+ @request_count += 1
319
+
320
+ @html_document = nil
321
+
322
+ @status = status.to_i
323
+ @status_message = StatusCodes::STATUS_CODES[@status]
324
+
325
+ @headers = Rack::Utils::HeaderHash.new(headers)
326
+
327
+ cookies = @headers['Set-Cookie']
328
+ cookies = cookies.to_s.split("\n") unless cookies.is_a?(Array)
329
+ cookies.each do |cookie|
330
+ name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
331
+ @cookies[name] = value
332
+ end
333
+
334
+ @body = ""
335
+ if body.respond_to?(:to_str)
336
+ @body << body
337
+ else
338
+ body.each { |part| @body << part }
339
+ end
340
+
341
+ if @controller = ActionController::Base.last_instantiation
342
+ @request = @controller.request
343
+ @response = @controller.response
344
+ @controller.send(:set_test_assigns)
345
+ else
346
+ # Decorate responses from Rack Middleware and Rails Metal
347
+ # as an Response for the purposes of integration testing
348
+ @response = Response.new
349
+ @response.status = status.to_s
350
+ @response.headers.replace(@headers)
351
+ @response.body = @body
352
+ end
353
+
354
+ # Decorate the response with the standard behavior of the
355
+ # TestResponse so that things like assert_response can be
356
+ # used in integration tests.
357
+ @response.extend(TestResponseBehavior)
358
+
359
+ body.close if body.respond_to?(:close)
360
+
361
+ return @status
362
+ rescue MultiPartNeededException
363
+ boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"
364
+ status = process(method, path,
365
+ multipart_body(parameters, boundary),
366
+ (headers || {}).merge(
367
+ {"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"}))
368
+ return status
369
+ end
370
+
371
+ # Encode the cookies hash in a format suitable for passing to a
372
+ # request.
373
+ def encode_cookies
374
+ cookies.inject("") do |string, (name, value)|
375
+ string << "#{name}=#{value}; "
376
+ end
377
+ end
378
+
379
+ # Get a temporary URL writer object
380
+ def generic_url_rewriter
381
+ env = {
382
+ 'REQUEST_METHOD' => "GET",
383
+ 'QUERY_STRING' => "",
384
+ "REQUEST_URI" => "/",
385
+ "HTTP_HOST" => host,
386
+ "SERVER_PORT" => https? ? "443" : "80",
387
+ "HTTPS" => https? ? "on" : "off"
388
+ }
389
+ UrlRewriter.new(Request.new(env), {})
390
+ end
391
+
392
+ def name_with_prefix(prefix, name)
393
+ prefix ? "#{prefix}[#{name}]" : name.to_s
394
+ end
395
+
396
+ # Convert the given parameters to a request string. The parameters may
397
+ # be a string, +nil+, or a Hash.
398
+ def requestify(parameters, prefix=nil)
399
+ if TestUploadedFile === parameters
400
+ raise MultiPartNeededException
401
+ elsif Hash === parameters
402
+ return nil if parameters.empty?
403
+ parameters.map { |k,v|
404
+ requestify(v, name_with_prefix(prefix, k))
405
+ }.join("&")
406
+ elsif Array === parameters
407
+ parameters.map { |v|
408
+ requestify(v, name_with_prefix(prefix, ""))
409
+ }.join("&")
410
+ elsif prefix.nil?
411
+ parameters
412
+ else
413
+ "#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}"
414
+ end
415
+ end
416
+
417
+ def multipart_requestify(params, first=true)
418
+ Array.new.tap do |p|
419
+ params.each do |key, value|
420
+ k = first ? key.to_s : "[#{key.to_s}]"
421
+ if Hash === value
422
+ multipart_requestify(value, false).each do |subkey, subvalue|
423
+ p << [k + subkey, subvalue]
424
+ end
425
+ elsif Array === value
426
+ value.each do |element|
427
+ if Hash === element || Array === element
428
+ multipart_requestify(element, false).each do |subkey, subvalue|
429
+ p << ["#{k}[]#{subkey}", subvalue]
430
+ end
431
+ else
432
+ p << ["#{k}[]", element]
433
+ end
434
+ end
435
+ else
436
+ p << [k, value]
437
+ end
438
+ end
439
+ end
440
+ end
441
+
442
+ def multipart_body(params, boundary)
443
+ multipart_requestify(params).map do |key, value|
444
+ if value.respond_to?(:original_filename)
445
+ File.open(value.path, "rb") do |f|
446
+ f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
447
+
448
+ <<-EOF
449
+ --#{boundary}\r
450
+ Content-Disposition: form-data; name="#{key}"; filename="#{CGI.escape(value.original_filename)}"\r
451
+ Content-Type: #{value.content_type}\r
452
+ Content-Length: #{File.stat(value.path).size}\r
453
+ \r
454
+ #{f.read}\r
455
+ EOF
456
+ end
457
+ else
458
+ <<-EOF
459
+ --#{boundary}\r
460
+ Content-Disposition: form-data; name="#{key}"\r
461
+ \r
462
+ #{value}\r
463
+ EOF
464
+ end
465
+ end.join("")+"--#{boundary}--\r"
466
+ end
467
+
468
+ end
469
+
470
+ # A module used to extend ActionController::Base, so that integration tests
471
+ # can capture the controller used to satisfy a request.
472
+ module ControllerCapture #:nodoc:
473
+ def self.included(base)
474
+ base.extend(ClassMethods)
475
+ base.class_eval do
476
+ class << self
477
+ alias_method_chain :new, :capture
478
+ end
479
+ end
480
+ end
481
+
482
+ module ClassMethods #:nodoc:
483
+ mattr_accessor :last_instantiation
484
+
485
+ def clear_last_instantiation!
486
+ self.last_instantiation = nil
487
+ end
488
+
489
+ def new_with_capture(*args)
490
+ controller = new_without_capture(*args)
491
+ self.last_instantiation ||= controller
492
+ controller
493
+ end
494
+ end
495
+ end
496
+
497
+ module Runner
498
+ def initialize(*args)
499
+ super
500
+ @integration_session = nil
501
+ end
502
+
503
+ # Reset the current session. This is useful for testing multiple sessions
504
+ # in a single test case.
505
+ def reset!
506
+ @integration_session = open_session
507
+ end
508
+
509
+ %w(get post put head delete cookies assigns
510
+ xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
511
+ define_method(method) do |*args|
512
+ reset! unless @integration_session
513
+ # reset the html_document variable, but only for new get/post calls
514
+ @html_document = nil unless %w(cookies assigns).include?(method)
515
+ @integration_session.__send__(method, *args).tap do
516
+ copy_session_variables!
517
+ end
518
+ end
519
+ end
520
+
521
+ # Open a new session instance. If a block is given, the new session is
522
+ # yielded to the block before being returned.
523
+ #
524
+ # session = open_session do |sess|
525
+ # sess.extend(CustomAssertions)
526
+ # end
527
+ #
528
+ # By default, a single session is automatically created for you, but you
529
+ # can use this method to open multiple sessions that ought to be tested
530
+ # simultaneously.
531
+ def open_session(application = nil)
532
+ session = Integration::Session.new(application)
533
+
534
+ # delegate the fixture accessors back to the test instance
535
+ extras = Module.new { attr_accessor :delegate, :test_result }
536
+ if self.class.respond_to?(:fixture_table_names)
537
+ self.class.fixture_table_names.each do |table_name|
538
+ name = table_name.tr(".", "_")
539
+ next unless respond_to?(name, true)
540
+ extras.__send__(:define_method, name) { |*args|
541
+ delegate.send(name, *args)
542
+ }
543
+ end
544
+ end
545
+
546
+ # delegate add_assertion to the test case
547
+ extras.__send__(:define_method, :add_assertion) {
548
+ test_result.add_assertion
549
+ }
550
+ session.extend(extras)
551
+ session.delegate = self
552
+ session.test_result = @_result
553
+
554
+ yield session if block_given?
555
+ session
556
+ end
557
+
558
+ # Copy the instance variables from the current session instance into the
559
+ # test instance.
560
+ def copy_session_variables! #:nodoc:
561
+ return unless @integration_session
562
+ %w(controller response request).each do |var|
563
+ instance_variable_set("@#{var}", @integration_session.__send__(var))
564
+ end
565
+ end
566
+
567
+ # Delegate unhandled messages to the current session instance.
568
+ def method_missing(sym, *args, &block)
569
+ reset! unless @integration_session
570
+ if @integration_session.respond_to?(sym)
571
+ @integration_session.__send__(sym, *args, &block).tap do
572
+ copy_session_variables!
573
+ end
574
+ else
575
+ super
576
+ end
577
+ end
578
+ end
579
+ end
580
+
581
+ # An IntegrationTest is one that spans multiple controllers and actions,
582
+ # tying them all together to ensure they work together as expected. It tests
583
+ # more completely than either unit or functional tests do, exercising the
584
+ # entire stack, from the dispatcher to the database.
585
+ #
586
+ # At its simplest, you simply extend IntegrationTest and write your tests
587
+ # using the get/post methods:
588
+ #
589
+ # require "#{File.dirname(__FILE__)}/test_helper"
590
+ #
591
+ # class ExampleTest < ActionController::IntegrationTest
592
+ # fixtures :people
593
+ #
594
+ # def test_login
595
+ # # get the login page
596
+ # get "/login"
597
+ # assert_equal 200, status
598
+ #
599
+ # # post the login and follow through to the home page
600
+ # post "/login", :username => people(:jamis).username,
601
+ # :password => people(:jamis).password
602
+ # follow_redirect!
603
+ # assert_equal 200, status
604
+ # assert_equal "/home", path
605
+ # end
606
+ # end
607
+ #
608
+ # However, you can also have multiple session instances open per test, and
609
+ # even extend those instances with assertions and methods to create a very
610
+ # powerful testing DSL that is specific for your application. You can even
611
+ # reference any named routes you happen to have defined!
612
+ #
613
+ # require "#{File.dirname(__FILE__)}/test_helper"
614
+ #
615
+ # class AdvancedTest < ActionController::IntegrationTest
616
+ # fixtures :people, :rooms
617
+ #
618
+ # def test_login_and_speak
619
+ # jamis, david = login(:jamis), login(:david)
620
+ # room = rooms(:office)
621
+ #
622
+ # jamis.enter(room)
623
+ # jamis.speak(room, "anybody home?")
624
+ #
625
+ # david.enter(room)
626
+ # david.speak(room, "hello!")
627
+ # end
628
+ #
629
+ # private
630
+ #
631
+ # module CustomAssertions
632
+ # def enter(room)
633
+ # # reference a named route, for maximum internal consistency!
634
+ # get(room_url(:id => room.id))
635
+ # assert(...)
636
+ # ...
637
+ # end
638
+ #
639
+ # def speak(room, message)
640
+ # xml_http_request "/say/#{room.id}", :message => message
641
+ # assert(...)
642
+ # ...
643
+ # end
644
+ # end
645
+ #
646
+ # def login(who)
647
+ # open_session do |sess|
648
+ # sess.extend(CustomAssertions)
649
+ # who = people(who)
650
+ # sess.post "/login", :username => who.username,
651
+ # :password => who.password
652
+ # assert(...)
653
+ # end
654
+ # end
655
+ # end
656
+ class IntegrationTest < ActiveSupport::TestCase
657
+ include Integration::Runner
658
+
659
+ # Work around a bug in test/unit caused by the default test being named
660
+ # as a symbol (:default_test), which causes regex test filters
661
+ # (like "ruby test.rb -n /foo/") to fail because =~ doesn't work on
662
+ # symbols.
663
+ def initialize(name) #:nodoc:
664
+ super(name.to_s)
665
+ end
666
+
667
+ # Work around test/unit's requirement that every subclass of TestCase have
668
+ # at least one test method. Note that this implementation extends to all
669
+ # subclasses, as well, so subclasses of IntegrationTest may also exist
670
+ # without any test methods.
671
+ def run(*args) #:nodoc:
672
+ return if @method_name == "default_test"
673
+ super
674
+ end
675
+
676
+ # Because of how use_instantiated_fixtures and use_transactional_fixtures
677
+ # are defined, we need to treat them as special cases. Otherwise, users
678
+ # would potentially have to set their values for both Test::Unit::TestCase
679
+ # ActionController::IntegrationTest, since by the time the value is set on
680
+ # TestCase, IntegrationTest has already been defined and cannot inherit
681
+ # changes to those variables. So, we make those two attributes
682
+ # copy-on-write.
683
+
684
+ class << self
685
+ def use_transactional_fixtures=(flag) #:nodoc:
686
+ @_use_transactional_fixtures = true
687
+ @use_transactional_fixtures = flag
688
+ end
689
+
690
+ def use_instantiated_fixtures=(flag) #:nodoc:
691
+ @_use_instantiated_fixtures = true
692
+ @use_instantiated_fixtures = flag
693
+ end
694
+
695
+ def use_transactional_fixtures #:nodoc:
696
+ @_use_transactional_fixtures ?
697
+ @use_transactional_fixtures :
698
+ superclass.use_transactional_fixtures
699
+ end
700
+
701
+ def use_instantiated_fixtures #:nodoc:
702
+ @_use_instantiated_fixtures ?
703
+ @use_instantiated_fixtures :
704
+ superclass.use_instantiated_fixtures
705
+ end
706
+ end
707
+ end
708
+ end