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.
- data/CHANGELOG +5250 -0
- data/MIT-LICENSE +21 -0
- data/README +409 -0
- data/RUNNING_UNIT_TESTS +24 -0
- data/Rakefile +158 -0
- data/install.rb +30 -0
- data/lib/action_controller.rb +113 -0
- data/lib/action_controller/assertions/dom_assertions.rb +55 -0
- data/lib/action_controller/assertions/model_assertions.rb +21 -0
- data/lib/action_controller/assertions/response_assertions.rb +169 -0
- data/lib/action_controller/assertions/routing_assertions.rb +146 -0
- data/lib/action_controller/assertions/selector_assertions.rb +638 -0
- data/lib/action_controller/assertions/tag_assertions.rb +127 -0
- data/lib/action_controller/base.rb +1425 -0
- data/lib/action_controller/benchmarking.rb +107 -0
- data/lib/action_controller/caching.rb +71 -0
- data/lib/action_controller/caching/actions.rb +177 -0
- data/lib/action_controller/caching/fragments.rb +120 -0
- data/lib/action_controller/caching/pages.rb +152 -0
- data/lib/action_controller/caching/sweeper.rb +45 -0
- data/lib/action_controller/caching/sweeping.rb +55 -0
- data/lib/action_controller/cgi_ext.rb +15 -0
- data/lib/action_controller/cgi_ext/cookie.rb +112 -0
- data/lib/action_controller/cgi_ext/query_extension.rb +22 -0
- data/lib/action_controller/cgi_ext/stdinput.rb +24 -0
- data/lib/action_controller/cgi_process.rb +77 -0
- data/lib/action_controller/cookies.rb +197 -0
- data/lib/action_controller/dispatcher.rb +133 -0
- data/lib/action_controller/failsafe.rb +87 -0
- data/lib/action_controller/filters.rb +680 -0
- data/lib/action_controller/flash.rb +213 -0
- data/lib/action_controller/headers.rb +33 -0
- data/lib/action_controller/helpers.rb +225 -0
- data/lib/action_controller/http_authentication.rb +309 -0
- data/lib/action_controller/integration.rb +708 -0
- data/lib/action_controller/layout.rb +286 -0
- data/lib/action_controller/middleware_stack.rb +119 -0
- data/lib/action_controller/middlewares.rb +14 -0
- data/lib/action_controller/mime_responds.rb +193 -0
- data/lib/action_controller/mime_type.rb +212 -0
- data/lib/action_controller/mime_types.rb +21 -0
- data/lib/action_controller/params_parser.rb +77 -0
- data/lib/action_controller/performance_test.rb +15 -0
- data/lib/action_controller/polymorphic_routes.rb +189 -0
- data/lib/action_controller/rack_lint_patch.rb +36 -0
- data/lib/action_controller/record_identifier.rb +104 -0
- data/lib/action_controller/reloader.rb +54 -0
- data/lib/action_controller/request.rb +495 -0
- data/lib/action_controller/request_forgery_protection.rb +116 -0
- data/lib/action_controller/rescue.rb +183 -0
- data/lib/action_controller/resources.rb +682 -0
- data/lib/action_controller/response.rb +237 -0
- data/lib/action_controller/routing.rb +388 -0
- data/lib/action_controller/routing/builder.rb +197 -0
- data/lib/action_controller/routing/optimisations.rb +130 -0
- data/lib/action_controller/routing/recognition_optimisation.rb +167 -0
- data/lib/action_controller/routing/route.rb +265 -0
- data/lib/action_controller/routing/route_set.rb +503 -0
- data/lib/action_controller/routing/routing_ext.rb +49 -0
- data/lib/action_controller/routing/segments.rb +343 -0
- data/lib/action_controller/session/abstract_store.rb +276 -0
- data/lib/action_controller/session/cookie_store.rb +240 -0
- data/lib/action_controller/session/mem_cache_store.rb +60 -0
- data/lib/action_controller/session_management.rb +54 -0
- data/lib/action_controller/status_codes.rb +88 -0
- data/lib/action_controller/streaming.rb +181 -0
- data/lib/action_controller/string_coercion.rb +29 -0
- data/lib/action_controller/templates/rescues/_request_and_response.erb +24 -0
- data/lib/action_controller/templates/rescues/_trace.erb +26 -0
- data/lib/action_controller/templates/rescues/diagnostics.erb +11 -0
- data/lib/action_controller/templates/rescues/layout.erb +29 -0
- data/lib/action_controller/templates/rescues/missing_template.erb +2 -0
- data/lib/action_controller/templates/rescues/routing_error.erb +10 -0
- data/lib/action_controller/templates/rescues/template_error.erb +21 -0
- data/lib/action_controller/templates/rescues/unknown_action.erb +2 -0
- data/lib/action_controller/test_case.rb +209 -0
- data/lib/action_controller/test_process.rb +580 -0
- data/lib/action_controller/translation.rb +13 -0
- data/lib/action_controller/uploaded_file.rb +44 -0
- data/lib/action_controller/url_rewriter.rb +229 -0
- data/lib/action_controller/vendor/html-scanner.rb +16 -0
- data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
- data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
- data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +173 -0
- data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
- data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
- data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
- data/lib/action_controller/verification.rb +130 -0
- data/lib/action_pack.rb +24 -0
- data/lib/action_pack/version.rb +9 -0
- data/lib/action_view.rb +58 -0
- data/lib/action_view/base.rb +362 -0
- data/lib/action_view/helpers.rb +61 -0
- data/lib/action_view/helpers/active_record_helper.rb +305 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +695 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +198 -0
- data/lib/action_view/helpers/benchmark_helper.rb +54 -0
- data/lib/action_view/helpers/cache_helper.rb +39 -0
- data/lib/action_view/helpers/capture_helper.rb +136 -0
- data/lib/action_view/helpers/csrf_helper.rb +14 -0
- data/lib/action_view/helpers/date_helper.rb +989 -0
- data/lib/action_view/helpers/debug_helper.rb +38 -0
- data/lib/action_view/helpers/form_helper.rb +1118 -0
- data/lib/action_view/helpers/form_options_helper.rb +599 -0
- data/lib/action_view/helpers/form_tag_helper.rb +490 -0
- data/lib/action_view/helpers/javascript_helper.rb +208 -0
- data/lib/action_view/helpers/number_helper.rb +308 -0
- data/lib/action_view/helpers/prototype_helper.rb +1305 -0
- data/lib/action_view/helpers/raw_output_helper.rb +9 -0
- data/lib/action_view/helpers/record_identification_helper.rb +20 -0
- data/lib/action_view/helpers/record_tag_helper.rb +58 -0
- data/lib/action_view/helpers/sanitize_helper.rb +251 -0
- data/lib/action_view/helpers/scriptaculous_helper.rb +226 -0
- data/lib/action_view/helpers/tag_helper.rb +151 -0
- data/lib/action_view/helpers/text_helper.rb +597 -0
- data/lib/action_view/helpers/translation_helper.rb +67 -0
- data/lib/action_view/helpers/url_helper.rb +637 -0
- data/lib/action_view/inline_template.rb +19 -0
- data/lib/action_view/locale/en.yml +117 -0
- data/lib/action_view/partials.rb +241 -0
- data/lib/action_view/paths.rb +77 -0
- data/lib/action_view/reloadable_template.rb +117 -0
- data/lib/action_view/renderable.rb +109 -0
- data/lib/action_view/renderable_partial.rb +53 -0
- data/lib/action_view/template.rb +252 -0
- data/lib/action_view/template_error.rb +99 -0
- data/lib/action_view/template_handler.rb +34 -0
- data/lib/action_view/template_handlers.rb +48 -0
- data/lib/action_view/template_handlers/builder.rb +17 -0
- data/lib/action_view/template_handlers/erb.rb +25 -0
- data/lib/action_view/template_handlers/rjs.rb +13 -0
- data/lib/action_view/test_case.rb +162 -0
- data/lib/actionpack.rb +2 -0
- data/test/abstract_unit.rb +78 -0
- data/test/active_record_unit.rb +104 -0
- data/test/activerecord/active_record_store_test.rb +221 -0
- data/test/activerecord/render_partial_with_record_identification_test.rb +188 -0
- data/test/adv_attr_test.rb +20 -0
- data/test/controller/action_pack_assertions_test.rb +545 -0
- data/test/controller/addresses_render_test.rb +37 -0
- data/test/controller/assert_select_test.rb +735 -0
- data/test/controller/base_test.rb +217 -0
- data/test/controller/benchmark_test.rb +32 -0
- data/test/controller/caching_test.rb +743 -0
- data/test/controller/capture_test.rb +66 -0
- data/test/controller/content_type_test.rb +178 -0
- data/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb +0 -0
- data/test/controller/controller_fixtures/app/controllers/user_controller.rb +0 -0
- data/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb +0 -0
- data/test/controller/cookie_test.rb +208 -0
- data/test/controller/deprecation/deprecated_base_methods_test.rb +32 -0
- data/test/controller/dispatcher_test.rb +144 -0
- data/test/controller/dom_assertions_test.rb +53 -0
- data/test/controller/failsafe_test.rb +60 -0
- data/test/controller/fake_controllers.rb +33 -0
- data/test/controller/fake_models.rb +19 -0
- data/test/controller/filter_params_test.rb +52 -0
- data/test/controller/filters_test.rb +885 -0
- data/test/controller/flash_test.rb +174 -0
- data/test/controller/header_test.rb +14 -0
- data/test/controller/helper_test.rb +224 -0
- data/test/controller/html-scanner/cdata_node_test.rb +15 -0
- data/test/controller/html-scanner/document_test.rb +148 -0
- data/test/controller/html-scanner/node_test.rb +89 -0
- data/test/controller/html-scanner/sanitizer_test.rb +281 -0
- data/test/controller/html-scanner/tag_node_test.rb +238 -0
- data/test/controller/html-scanner/text_node_test.rb +50 -0
- data/test/controller/html-scanner/tokenizer_test.rb +131 -0
- data/test/controller/http_basic_authentication_test.rb +113 -0
- data/test/controller/http_digest_authentication_test.rb +254 -0
- data/test/controller/integration_test.rb +526 -0
- data/test/controller/layout_test.rb +215 -0
- data/test/controller/localized_templates_test.rb +24 -0
- data/test/controller/logging_test.rb +46 -0
- data/test/controller/middleware_stack_test.rb +90 -0
- data/test/controller/mime_responds_test.rb +536 -0
- data/test/controller/mime_type_test.rb +93 -0
- data/test/controller/output_escaping_test.rb +19 -0
- data/test/controller/polymorphic_routes_test.rb +297 -0
- data/test/controller/rack_test.rb +308 -0
- data/test/controller/record_identifier_test.rb +139 -0
- data/test/controller/redirect_test.rb +285 -0
- data/test/controller/reloader_test.rb +125 -0
- data/test/controller/render_test.rb +1783 -0
- data/test/controller/request/json_params_parsing_test.rb +65 -0
- data/test/controller/request/multipart_params_parsing_test.rb +177 -0
- data/test/controller/request/query_string_parsing_test.rb +120 -0
- data/test/controller/request/test_request_test.rb +35 -0
- data/test/controller/request/url_encoded_params_parsing_test.rb +146 -0
- data/test/controller/request/xml_params_parsing_test.rb +103 -0
- data/test/controller/request_forgery_protection_test.rb +233 -0
- data/test/controller/request_test.rb +398 -0
- data/test/controller/rescue_test.rb +541 -0
- data/test/controller/resources_test.rb +1393 -0
- data/test/controller/routing_test.rb +2592 -0
- data/test/controller/selector_test.rb +628 -0
- data/test/controller/send_file_test.rb +171 -0
- data/test/controller/session/abstract_store_test.rb +64 -0
- data/test/controller/session/cookie_store_test.rb +354 -0
- data/test/controller/session/mem_cache_store_test.rb +187 -0
- data/test/controller/session/test_session_test.rb +58 -0
- data/test/controller/test_test.rb +700 -0
- data/test/controller/translation_test.rb +26 -0
- data/test/controller/url_rewriter_test.rb +395 -0
- data/test/controller/verification_test.rb +270 -0
- data/test/controller/view_paths_test.rb +141 -0
- data/test/controller/webservice_test.rb +273 -0
- data/test/fixtures/_top_level_partial.html.erb +1 -0
- data/test/fixtures/_top_level_partial_only.erb +1 -0
- data/test/fixtures/addresses/list.erb +1 -0
- data/test/fixtures/alternate_helpers/foo_helper.rb +3 -0
- data/test/fixtures/bad_customers/_bad_customer.html.erb +1 -0
- data/test/fixtures/companies.yml +24 -0
- data/test/fixtures/company.rb +10 -0
- data/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml +1 -0
- data/test/fixtures/content_type/render_default_for_rhtml.rhtml +1 -0
- data/test/fixtures/content_type/render_default_for_rjs.rjs +1 -0
- data/test/fixtures/content_type/render_default_for_rxml.rxml +1 -0
- data/test/fixtures/customers/_customer.html.erb +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +49 -0
- data/test/fixtures/developer.rb +9 -0
- data/test/fixtures/developers.yml +21 -0
- data/test/fixtures/developers/_developer.erb +1 -0
- data/test/fixtures/developers_projects.yml +13 -0
- data/test/fixtures/failsafe/500.html +1 -0
- data/test/fixtures/fun/games/_game.erb +1 -0
- data/test/fixtures/fun/games/hello_world.erb +1 -0
- data/test/fixtures/fun/serious/games/_game.erb +1 -0
- data/test/fixtures/functional_caching/_partial.erb +3 -0
- data/test/fixtures/functional_caching/formatted_fragment_cached.html.erb +3 -0
- data/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs +6 -0
- data/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder +5 -0
- data/test/fixtures/functional_caching/fragment_cached.html.erb +2 -0
- data/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb +1 -0
- data/test/fixtures/functional_caching/inline_fragment_cached.html.erb +2 -0
- data/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs +1 -0
- data/test/fixtures/good_customers/_good_customer.html.erb +1 -0
- data/test/fixtures/helpers/abc_helper.rb +5 -0
- data/test/fixtures/helpers/fun/games_helper.rb +3 -0
- data/test/fixtures/helpers/fun/pdf_helper.rb +3 -0
- data/test/fixtures/layout_tests/abs_path_layout.rhtml +1 -0
- data/test/fixtures/layout_tests/alt/hello.rhtml +1 -0
- data/test/fixtures/layout_tests/alt/layouts/alt.rhtml +0 -0
- data/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml +1 -0
- data/test/fixtures/layout_tests/layouts/item.rhtml +1 -0
- data/test/fixtures/layout_tests/layouts/layout_test.rhtml +1 -0
- data/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb +1 -0
- data/test/fixtures/layout_tests/layouts/third_party_template_library.mab +1 -0
- data/test/fixtures/layout_tests/views/hello.rhtml +1 -0
- data/test/fixtures/layouts/_column.html.erb +2 -0
- data/test/fixtures/layouts/block_with_layout.erb +3 -0
- data/test/fixtures/layouts/builder.builder +3 -0
- data/test/fixtures/layouts/default_html.html.erb +1 -0
- data/test/fixtures/layouts/partial_with_layout.erb +3 -0
- data/test/fixtures/layouts/standard.erb +1 -0
- data/test/fixtures/layouts/talk_from_action.erb +2 -0
- data/test/fixtures/layouts/xhr.html.erb +2 -0
- data/test/fixtures/layouts/yield.erb +2 -0
- data/test/fixtures/localized/hello_world.de.html +1 -0
- data/test/fixtures/localized/hello_world.en.html +1 -0
- data/test/fixtures/mascot.rb +3 -0
- data/test/fixtures/mascots.yml +4 -0
- data/test/fixtures/mascots/_mascot.html.erb +1 -0
- data/test/fixtures/multipart/binary_file +0 -0
- data/test/fixtures/multipart/boundary_problem_file +10 -0
- data/test/fixtures/multipart/bracketed_param +5 -0
- data/test/fixtures/multipart/empty +10 -0
- data/test/fixtures/multipart/hello.txt +1 -0
- data/test/fixtures/multipart/large_text_file +10 -0
- data/test/fixtures/multipart/mixed_files +0 -0
- data/test/fixtures/multipart/mona_lisa.jpg +0 -0
- data/test/fixtures/multipart/none +9 -0
- data/test/fixtures/multipart/single_parameter +5 -0
- data/test/fixtures/multipart/text_file +10 -0
- data/test/fixtures/override/test/hello_world.erb +1 -0
- data/test/fixtures/override2/layouts/test/sub.erb +1 -0
- data/test/fixtures/post_test/layouts/post.html.erb +1 -0
- data/test/fixtures/post_test/layouts/super_post.iphone.erb +1 -0
- data/test/fixtures/post_test/post/index.html.erb +1 -0
- data/test/fixtures/post_test/post/index.iphone.erb +1 -0
- data/test/fixtures/post_test/super_post/index.html.erb +1 -0
- data/test/fixtures/post_test/super_post/index.iphone.erb +1 -0
- data/test/fixtures/project.rb +3 -0
- data/test/fixtures/projects.yml +7 -0
- data/test/fixtures/projects/_project.erb +1 -0
- data/test/fixtures/public/404.html +1 -0
- data/test/fixtures/public/500.da.html +1 -0
- data/test/fixtures/public/500.html +1 -0
- data/test/fixtures/public/absolute/test.css +23 -0
- data/test/fixtures/public/absolute/test.js +63 -0
- data/test/fixtures/public/images/rails.png +0 -0
- data/test/fixtures/public/javascripts/application.js +1 -0
- data/test/fixtures/public/javascripts/bank.js +1 -0
- data/test/fixtures/public/javascripts/controls.js +1 -0
- data/test/fixtures/public/javascripts/dragdrop.js +1 -0
- data/test/fixtures/public/javascripts/effects.js +1 -0
- data/test/fixtures/public/javascripts/prototype.js +1 -0
- data/test/fixtures/public/javascripts/robber.js +1 -0
- data/test/fixtures/public/javascripts/subdir/subdir.js +1 -0
- data/test/fixtures/public/javascripts/version.1.0.js +1 -0
- data/test/fixtures/public/stylesheets/bank.css +1 -0
- data/test/fixtures/public/stylesheets/robber.css +1 -0
- data/test/fixtures/public/stylesheets/subdir/subdir.css +1 -0
- data/test/fixtures/public/stylesheets/version.1.0.css +1 -0
- data/test/fixtures/quiz/questions/_question.html.erb +1 -0
- data/test/fixtures/replies.yml +15 -0
- data/test/fixtures/replies/_reply.erb +1 -0
- data/test/fixtures/reply.rb +7 -0
- data/test/fixtures/respond_to/all_types_with_layout.html.erb +1 -0
- data/test/fixtures/respond_to/all_types_with_layout.js.rjs +1 -0
- data/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb +1 -0
- data/test/fixtures/respond_to/iphone_with_html_response_type.html.erb +1 -0
- data/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb +1 -0
- data/test/fixtures/respond_to/layouts/missing.html.erb +1 -0
- data/test/fixtures/respond_to/layouts/standard.html.erb +1 -0
- data/test/fixtures/respond_to/layouts/standard.iphone.erb +1 -0
- data/test/fixtures/respond_to/using_defaults.html.erb +1 -0
- data/test/fixtures/respond_to/using_defaults.js.rjs +1 -0
- data/test/fixtures/respond_to/using_defaults.xml.builder +1 -0
- data/test/fixtures/respond_to/using_defaults_with_type_list.html.erb +1 -0
- data/test/fixtures/respond_to/using_defaults_with_type_list.js.rjs +1 -0
- data/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder +1 -0
- data/test/fixtures/scope/test/modgreet.erb +1 -0
- data/test/fixtures/session_autoload_test/session_autoload_test/foo.rb +10 -0
- data/test/fixtures/shared.html.erb +1 -0
- data/test/fixtures/symlink_parent/symlinked_layout.erb +5 -0
- data/test/fixtures/test/_counter.html.erb +1 -0
- data/test/fixtures/test/_customer.erb +1 -0
- data/test/fixtures/test/_customer_counter.erb +1 -0
- data/test/fixtures/test/_customer_counter_with_as.erb +1 -0
- data/test/fixtures/test/_customer_greeting.erb +1 -0
- data/test/fixtures/test/_customer_with_var.erb +1 -0
- data/test/fixtures/test/_form.erb +1 -0
- data/test/fixtures/test/_from_helper.erb +1 -0
- data/test/fixtures/test/_hash_greeting.erb +1 -0
- data/test/fixtures/test/_hash_object.erb +2 -0
- data/test/fixtures/test/_hello.builder +1 -0
- data/test/fixtures/test/_labelling_form.erb +1 -0
- data/test/fixtures/test/_layout_for_block_with_args.html.erb +3 -0
- data/test/fixtures/test/_layout_for_partial.html.erb +3 -0
- data/test/fixtures/test/_local_inspector.html.erb +1 -0
- data/test/fixtures/test/_one.html.erb +1 -0
- data/test/fixtures/test/_partial.erb +1 -0
- data/test/fixtures/test/_partial.html.erb +1 -0
- data/test/fixtures/test/_partial.js.erb +1 -0
- data/test/fixtures/test/_partial_for_use_in_layout.html.erb +1 -0
- data/test/fixtures/test/_partial_only.erb +1 -0
- data/test/fixtures/test/_partial_with_only_html_version.html.erb +1 -0
- data/test/fixtures/test/_person.erb +2 -0
- data/test/fixtures/test/_raise.html.erb +1 -0
- data/test/fixtures/test/_two.html.erb +1 -0
- data/test/fixtures/test/_utf8_partial.html.erb +1 -0
- data/test/fixtures/test/_utf8_partial_magic.html.erb +2 -0
- data/test/fixtures/test/action_talk_to_layout.erb +2 -0
- data/test/fixtures/test/array_translation.erb +1 -0
- data/test/fixtures/test/calling_partial_with_layout.html.erb +1 -0
- data/test/fixtures/test/capturing.erb +4 -0
- data/test/fixtures/test/content_for.erb +2 -0
- data/test/fixtures/test/content_for_concatenated.erb +3 -0
- data/test/fixtures/test/content_for_with_parameter.erb +2 -0
- data/test/fixtures/test/delete_with_js.rjs +2 -0
- data/test/fixtures/test/dont_pick_me +1 -0
- data/test/fixtures/test/dot.directory/render_file_with_ivar.erb +1 -0
- data/test/fixtures/test/enum_rjs_test.rjs +6 -0
- data/test/fixtures/test/formatted_html_erb.html.erb +1 -0
- data/test/fixtures/test/formatted_xml_erb.builder +1 -0
- data/test/fixtures/test/formatted_xml_erb.html.erb +1 -0
- data/test/fixtures/test/formatted_xml_erb.xml.erb +1 -0
- data/test/fixtures/test/greeting.erb +1 -0
- data/test/fixtures/test/greeting.js.rjs +1 -0
- data/test/fixtures/test/hello.builder +4 -0
- data/test/fixtures/test/hello_world.da.html.erb +1 -0
- data/test/fixtures/test/hello_world.erb +1 -0
- data/test/fixtures/test/hello_world.erb~ +1 -0
- data/test/fixtures/test/hello_world.pt-BR.html.erb +1 -0
- data/test/fixtures/test/hello_world_container.builder +3 -0
- data/test/fixtures/test/hello_world_from_rxml.builder +4 -0
- data/test/fixtures/test/hello_world_with_layout_false.erb +1 -0
- data/test/fixtures/test/hello_xml_world.builder +11 -0
- data/test/fixtures/test/hyphen-ated.erb +1 -0
- data/test/fixtures/test/implicit_content_type.atom.builder +2 -0
- data/test/fixtures/test/list.erb +1 -0
- data/test/fixtures/test/malformed/malformed.en.html.erb~ +1 -0
- data/test/fixtures/test/malformed/malformed.erb~ +1 -0
- data/test/fixtures/test/malformed/malformed.html.erb~ +1 -0
- data/test/fixtures/test/nested_layout.erb +3 -0
- data/test/fixtures/test/non_erb_block_content_for.builder +4 -0
- data/test/fixtures/test/potential_conflicts.erb +4 -0
- data/test/fixtures/test/render_explicit_html_template.js.rjs +1 -0
- data/test/fixtures/test/render_file_from_template.html.erb +1 -0
- data/test/fixtures/test/render_file_with_ivar.erb +1 -0
- data/test/fixtures/test/render_file_with_locals.erb +1 -0
- data/test/fixtures/test/render_implicit_html_template.js.rjs +1 -0
- data/test/fixtures/test/render_implicit_html_template_from_xhr_request.da.html.erb +1 -0
- data/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb +1 -0
- data/test/fixtures/test/render_implicit_js_template_without_layout.js.erb +1 -0
- data/test/fixtures/test/render_to_string_test.erb +1 -0
- data/test/fixtures/test/scoped_array_translation.erb +1 -0
- data/test/fixtures/test/sub_template_raise.html.erb +1 -0
- data/test/fixtures/test/template.erb +1 -0
- data/test/fixtures/test/translation.erb +1 -0
- data/test/fixtures/test/update_element_with_capture.erb +9 -0
- data/test/fixtures/test/using_layout_around_block.html.erb +1 -0
- data/test/fixtures/test/using_layout_around_block_with_args.html.erb +1 -0
- data/test/fixtures/test/utf8.html.erb +4 -0
- data/test/fixtures/test/utf8_magic.html.erb +5 -0
- data/test/fixtures/test/utf8_magic_with_bare_partial.html.erb +5 -0
- data/test/fixtures/topic.rb +3 -0
- data/test/fixtures/topics.yml +22 -0
- data/test/fixtures/topics/_topic.html.erb +1 -0
- data/test/template/active_record_helper_i18n_test.rb +51 -0
- data/test/template/active_record_helper_test.rb +302 -0
- data/test/template/asset_tag_helper_test.rb +770 -0
- data/test/template/atom_feed_helper_test.rb +315 -0
- data/test/template/benchmark_helper_test.rb +86 -0
- data/test/template/compiled_templates_test.rb +204 -0
- data/test/template/date_helper_i18n_test.rb +121 -0
- data/test/template/date_helper_test.rb +2603 -0
- data/test/template/erb_util_test.rb +36 -0
- data/test/template/form_helper_test.rb +1447 -0
- data/test/template/form_options_helper_i18n_test.rb +27 -0
- data/test/template/form_options_helper_test.rb +811 -0
- data/test/template/form_tag_helper_test.rb +356 -0
- data/test/template/javascript_helper_test.rb +106 -0
- data/test/template/number_helper_i18n_test.rb +69 -0
- data/test/template/number_helper_test.rb +132 -0
- data/test/template/prototype_helper_test.rb +639 -0
- data/test/template/raw_output_helper_test.rb +21 -0
- data/test/template/record_tag_helper_test.rb +58 -0
- data/test/template/render_test.rb +329 -0
- data/test/template/sanitize_helper_test.rb +57 -0
- data/test/template/scriptaculous_helper_test.rb +90 -0
- data/test/template/tag_helper_test.rb +98 -0
- data/test/template/template_test.rb +32 -0
- data/test/template/test_test.rb +54 -0
- data/test/template/text_helper_test.rb +597 -0
- data/test/template/translation_helper_test.rb +95 -0
- data/test/template/url_helper_test.rb +641 -0
- data/test/testing_sandbox.rb +15 -0
- data/test/view/test_case_test.rb +176 -0
- 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
|