diff options
Diffstat (limited to 'railties')
402 files changed, 7801 insertions, 2199 deletions
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 7aee513a99..70c0f5c67b 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,156 +1,164 @@ -* Fix running multiple tests in one `rake` command +## Rails 5.2.0.beta2 (November 28, 2017) ## - e.g. `bin/rake test:models test:controllers` +* No changes. - *Dominic Cleal* -* Add option to configure Ruby's warning behaviour to test runner. +## Rails 5.2.0.beta1 (November 27, 2017) ## + +* Deprecate `after_bundle` callback in Rails plugin templates. *Yuji Yaginuma* -* Initialize git repo when generating new app, if option `--skip-git` - is not provided. +* `rails new` and `rails plugin new` get `Active Storage` by default. + Add ability to skip `Active Storage` with `--skip-active-storage` + and do so automatically when `--skip-active-record` is used. - *Dino Maric* + *bogdanvlviv* -* Install Byebug gem as default in Windows (mingw and x64_mingw) platform. +* Gemfile for new apps: upgrade redis-rb from ~> 3.0 to 4.0. - *Junichi Ito* + *Jeremy Daer* -* Make every Rails command work within engines. +* Add `mini_magick` to default `Gemfile` as comment. - *Sean Collins*, *Yuji Yaginuma* + *Yoshiyuki Hirano* -* Don't generate HTML/ERB templates for scaffold controller with `--api` flag. +* Derive `secret_key_base` from the app name in development and test environments. - Fixes #27591. + Spares away needless secret configs. - *Prathamesh Sonpatki* + *DHH*, *Kasper Timm Hansen* -* Make `Rails.env` fall back to `development` when `RAILS_ENV` and `RACK_ENV` is an empty string. +* Support multiple versions arguments for `gem` method of Generators. - *Daniel Deng* + *Yoshiyuki Hirano* -* Remove deprecated `CONTROLLER` environment variable for `routes` task. +* Add `--skip-yarn` option to the plugin generator. - *Rafael Mendonça França* + *bogdanvlviv* -* Remove deprecated tasks: `rails:update`, `rails:template`, `rails:template:copy`, - `rails:update:configs` and `rails:update:bin`. +* Optimize routes indentation. - *Rafael Mendonça França* + *Yoshiyuki Hirano* -* Remove deprecated file `rails/rack/debugger`. +* Optimize indentation for generator actions. - *Rafael Mendonça França* + *Yoshiyuki Hirano* -* Remove deprecated `config.serve_static_files`. +* Skip unused components when running `bin/rails` in Rails plugin. - *Rafael Mendonça França* + *Yoshiyuki Hirano* -* Remove deprecated `config.static_cache_control`. +* Add `git_source` to `Gemfile` for plugin generator. - *Rafael Mendonça França* + *Yoshiyuki Hirano* -* The `log:clear` task clear all environments log files by default. +* Add `--skip-action-cable` option to the plugin generator. - *Yuji Yaginuma* + *bogdanvlviv* -* Add Webpack support in new apps via the --webpack option, which will delegate to the rails/webpacker gem. +* Deprecate support of use `Rails::Application` subclass to start Rails server. - To generate a new app that has Webpack dependencies configured and binstubs for webpack and webpack-watcher: + *Yuji Yaginuma* - `rails new myapp --webpack` +* Add `ruby x.x.x` version to `Gemfile` and create `.ruby-version` + root file containing the current Ruby version when new Rails applications are + created. - To generate a new app that has Webpack + React configured and an example intalled: + *Alberto Almagro* - `rails new myapp --webpack=react` +* Support `-` as a platform-agnostic way to run a script from stdin with + `rails runner` - *DHH* + *Cody Cutrer* -* Add Yarn support in new apps with a yarn binstub and vendor/package.json. Skippable via --skip-yarn option. +* Add `bootsnap` to default `Gemfile`. - *Liceth Ovalles*, *Guillermo Iguaran*, *DHH* + *Burke Libbey* -* Removed jquery-rails from default stack, instead rails-ujs that is shipped - with Action View is included as default UJS adapter. +* Properly expand shortcuts for environment's name running the `console` + and `dbconsole` commands. - *Guillermo Iguaran* + *Robin Dupret* -* The config file `secrets.yml` is now loaded in with all keys as symbols. - This allows secrets files to contain more complex information without all - child keys being strings while parent keys are symbols. +* Passing the environment's name as a regular argument to the + `rails dbconsole` and `rails console` commands is deprecated. + The `-e` option should be used instead. - *Isaac Sloan* + Previously: -* Add `:skip_sprockets` to `Rails::PluginBuilder::PASSTHROUGH_OPTIONS` + $ bin/rails dbconsole production - *Tsukuru Tanimichi* + Now: -* Allow the use of listen's 3.1.x branch + $ bin/rails dbconsole -e production - *Esteban Santana Santana* + *Robin Dupret*, *Kasper Timm Hansen* -* Run `Minitest.after_run` hooks when running `rails test`. +* Allow passing a custom connection name to the `rails dbconsole` + command when using a 3-level database configuration. - *Michael Grosser* + $ bin/rails dbconsole -c replica -* Run `before_configuration` callbacks as soon as application constant - inherits from `Rails::Application`. + *Robin Dupret*, *Jeremy Daer* - Fixes #19880. +* Skip unused components when running `bin/rails app:update`. + + If the initial app generation skipped Action Cable, Active Record etc., + the update task honors those skips too. *Yuji Yaginuma* -* A generated app should not include Uglifier with `--skip-javascript` option. +* Make Rails' test runner work better with minitest plugins. + + By demoting the Rails test runner to just another minitest plugin — + and thereby not eager loading it — we can co-exist much better with + other minitest plugins such as pride and minitest-focus. + + *Kasper Timm Hansen* - *Ben Pickles* +* Load environment file in `dbconsole` command. -* Set session store to cookie store internally and remove the initializer from - the generated app. + Fixes #29717. - *Prathamesh Sonpatki* + *Yuji Yaginuma* -* Set the server host using the `HOST` environment variable. +* Add `rails secrets:show` command. - *mahnunchik* + *Yuji Yaginuma* -* Add public API to register new folders for `rake notes`: +* Allow mounting the same engine several times in different locations. - config.annotations.register_directories('spec', 'features') + Fixes #20204. - *John Meehan* + *David Rodríguez* -* Display name of the class defining the initializer along with the initializer - name in the output of `rails initializers`. +* Clear screenshot files in `tmp:clear` task. - Before: - disable_dependency_loading + *Yuji Yaginuma* - After: - DemoApp::Application.disable_dependency_loading +* Add `railtie.rb` to the plugin generator - *ta1kt0me* + *Tsukuru Tanimichi* -* Do not run `bundle install` when generating a new plugin. +* Deprecate `capify!` method in generators and templates. - Since bundler 1.12.0, the gemspec is validated so the `bundle install` - command will fail just after the gem is created causing confusion to the - users. This change was a bug fix to correctly validate gemspecs. + *Yuji Yaginuma* + +* Allow irb options to be passed from `rails console` command. - *Rafael Mendonça França* + Fixes #28988. -* Default `config.assets.quiet = true` in the development environment. Suppress - logging of assets requests by default. + *Yuji Yaginuma* - *Kevin McPhillips* +* Added a shared section to `config/database.yml` that will be loaded for all environments. -* Ensure `/rails/info` routes match in development for apps with a catch-all globbing route. + *Pierre Schambacher* - *Nicholas Firth-McCoy* +* Namespace error pages' CSS selectors to stop the styles from bleeding into other pages + when using Turbolinks. -* Added a shared section to `config/secrets.yml` that will be loaded for all environments. + *Jan Krutisch* - *DHH* -Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/railties/CHANGELOG.md) for previous changes. +Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/railties/CHANGELOG.md) for previous changes. diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE index f9e4444f07..cce00cbc3a 100644 --- a/railties/MIT-LICENSE +++ b/railties/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2017 David Heinemeier Hansson +Copyright (c) 2004-2018 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/railties/RDOC_MAIN.rdoc b/railties/RDOC_MAIN.rdoc index ef9bbf3d7e..c70a9f0ba0 100644 --- a/railties/RDOC_MAIN.rdoc +++ b/railties/RDOC_MAIN.rdoc @@ -1,35 +1,50 @@ == Welcome to \Rails -\Rails is a web-application framework that includes everything needed to create -database-backed web applications according to the {Model-View-Controller (MVC)}[http://en.wikipedia.org/wiki/Model-view-controller] pattern. - -Understanding the MVC pattern is key to understanding \Rails. MVC divides your application -into three layers, each with a specific responsibility. - -The View layer is composed of "templates" that are responsible for providing -appropriate representations of your application's resources. Templates -can come in a variety of formats, but most view templates are \HTML with embedded Ruby -code (.erb files). - -The Model layer represents your domain model (such as Account, Product, Person, Post) -and encapsulates the business logic that is specific to your application. In \Rails, -database-backed model classes are derived from ActiveRecord::Base. Active Record allows -you to present the data from database rows as objects and embellish these data objects -with business logic methods. Although most \Rails models are backed by a database, models -can also be ordinary Ruby classes, or Ruby classes that implement a set of interfaces as -provided by the ActiveModel module. You can read more about Active Record in its -{README}[link:files/activerecord/README_rdoc.html]. - -The Controller layer is responsible for handling incoming HTTP requests and providing a -suitable response. Usually this means returning \HTML, but \Rails controllers can also -generate XML, JSON, PDFs, mobile-specific views, and more. Controllers manipulate models -and render view templates in order to generate the appropriate HTTP response. - -In \Rails, the Controller and View layers are handled together by Action Pack. -These two layers are bundled in a single package due to their heavy interdependence. -This is unlike the relationship between Active Record and Action Pack, which are -independent. Each of these packages can be used independently outside of \Rails. You -can read more about Action Pack in its {README}[link:files/actionpack/README_rdoc.html]. +\Rails is a web-application framework that includes everything needed to +create database-backed web applications according to the +{Model-View-Controller (MVC)}[http://en.wikipedia.org/wiki/Model-view-controller] +pattern. + +Understanding the MVC pattern is key to understanding \Rails. MVC divides your +application into three layers, each with a specific responsibility. + +The <em>Model layer</em> represents your domain model (such as Account, Product, +Person, Post, etc.) and encapsulates the business logic that is specific to +your application. In \Rails, database-backed model classes are derived from +ActiveRecord::Base. Active Record allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in its {README}[link:files/activerecord/README_rdoc.html]. +Although most \Rails models are backed by a database, models can also be ordinary +Ruby classes, or Ruby classes that implement a set of interfaces as provided by +the Active Model module. You can read more about Active Model in its {README}[link:files/activemodel/README_rdoc.html]. + +The <em>Controller layer</em> is responsible for handling incoming HTTP requests and +providing a suitable response. Usually this means returning \HTML, but \Rails controllers +can also generate XML, JSON, PDFs, mobile-specific views, and more. Controllers load and +manipulate models, and render view templates in order to generate the appropriate HTTP response. +In \Rails, incoming requests are routed by Action Dispatch to an appropriate controller, and +controller classes are derived from ActionController::Base. Action Dispatch and Action Controller +are bundled together in Action Pack. You can read more about Action Pack in its +{README}[link:files/actionpack/README_rdoc.html]. + +The <em>View layer</em> is composed of "templates" that are responsible for providing +appropriate representations of your application's resources. Templates can +come in a variety of formats, but most view templates are \HTML with embedded +Ruby code (ERB files). Views are typically rendered to generate a controller response, +or to generate the body of an email. In \Rails, View generation is handled by Action View. +You can read more about Action View in its {README}[link:files/actionview/README_rdoc.html]. + +Active Record, Active Model, Action Pack, and Action View can each be used independently outside \Rails. +In addition to that, \Rails also comes with Action Mailer ({README}[link:files/actionmailer/README_rdoc.html]), a library +to generate and send emails; Active Job ({README}[link:files/activejob/README_md.html]), a +framework for declaring jobs and making them run on a variety of queueing +backends; Action Cable ({README}[link:files/actioncable/README_md.html]), a framework to +integrate WebSockets with a \Rails application; +Active Storage ({README}[link:files/activestorage/README_md.html]), a library to attach cloud +and local files to \Rails applications; +and Active Support ({README}[link:files/activesupport/README_rdoc.html]), a collection +of utility classes and standard library extensions that are useful for \Rails, +and may also be used independently outside \Rails. == Getting Started @@ -45,29 +60,32 @@ can read more about Action Pack in its {README}[link:files/actionpack/README_rdo 3. Change directory to +myapp+ and start the web server: - $ cd myapp; rails server + $ cd myapp + $ rails server Run with <tt>--help</tt> or <tt>-h</tt> for options. -4. Go to http://localhost:3000 and you'll see: - - "Yay! You’re on Rails!" +4. Go to <tt>http://localhost:3000</tt> and you'll see: "Yay! You’re on \Rails!" 5. Follow the guidelines to start developing your application. You may find the following resources handy: -* The \README file created within your application. -* {Getting Started with \Rails}[http://guides.rubyonrails.org/getting_started.html]. -* {Ruby on \Rails Tutorial}[http://www.railstutorial.org/book]. -* {Ruby on \Rails Guides}[http://guides.rubyonrails.org]. -* {The API Documentation}[http://api.rubyonrails.org]. + * The \README file created within your application. + * {Getting Started with \Rails}[http://guides.rubyonrails.org/getting_started.html]. + * {Ruby on \Rails Guides}[http://guides.rubyonrails.org]. + * {The API Documentation}[http://api.rubyonrails.org]. + * {Ruby on \Rails Tutorial}[https://www.railstutorial.org/book]. == Contributing -We encourage you to contribute to Ruby on \Rails! Please check out the {Contributing to Rails -guide}[http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html] for guidelines about how -to proceed. {Join us}[http://contributors.rubyonrails.org]! +We encourage you to contribute to Ruby on \Rails! Please check out the +{Contributing to Ruby on \Rails guide}[http://guides.rubyonrails.org/contributing_to_ruby_on_rails.html] for guidelines about how to proceed. {Join us!}[http://contributors.rubyonrails.org] + +Trying to report a possible security vulnerability in \Rails? Please +check out our {security policy}[http://rubyonrails.org/security/] for +guidelines about how to proceed. +Everyone interacting in \Rails and its sub-projects' codebases, issue trackers, chat rooms, and mailing lists is expected to follow the \Rails {code of conduct}[http://rubyonrails.org/conduct/]. == License -Ruby on \Rails is released under the {MIT License}[http://www.opensource.org/licenses/MIT]. +Ruby on \Rails is released under the {MIT License}[https://opensource.org/licenses/MIT]. diff --git a/railties/README.rdoc b/railties/README.rdoc index a25658668c..2715ccac3f 100644 --- a/railties/README.rdoc +++ b/railties/README.rdoc @@ -23,7 +23,7 @@ Source code can be downloaded as part of the Rails project on GitHub Railties is released under the MIT license: -* http://www.opensource.org/licenses/MIT +* https://opensource.org/licenses/MIT == Support @@ -38,4 +38,3 @@ Bug reports can be filed for the Ruby on Rails project here: Feature requests should be discussed on the rails-core mailing list here: * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core - diff --git a/railties/Rakefile b/railties/Rakefile index 680ed03f75..74615d2358 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rake/testtask" task default: :test @@ -9,25 +11,67 @@ task test: "test:isolated" namespace :test do task :isolated do + dash_i = [ + "test", + "lib", + "../activesupport/lib", + "../actionpack/lib", + "../actionview/lib", + "../activemodel/lib" + ].map { |dir| File.expand_path(dir, __dir__) } + + dash_i.reverse_each do |x| + $:.unshift(x) unless $:.include?(x) + end + $-w = true + + require "bundler/setup" unless defined?(Bundler) + require "active_support" + + failing_files = [] + dirs = (ENV["TEST_DIR"] || ENV["TEST_DIRS"] || "**").split(",") test_files = dirs.map { |dir| "test/#{dir}/*_test.rb" } Dir[*test_files].each do |file| - next true if file.include?("fixtures") - dash_i = [ - "test", - "lib", - "#{File.dirname(__FILE__)}/../activesupport/lib", - "#{File.dirname(__FILE__)}/../actionpack/lib", - "#{File.dirname(__FILE__)}/../actionview/lib", - "#{File.dirname(__FILE__)}/../activemodel/lib" - ] - ruby "-w", "-I#{dash_i.join ':'}", file + next true if file.start_with?("test/fixtures/") + + fake_command = Shellwords.join([ + FileUtils::RUBY, + "-w", + *dash_i.map { |dir| "-I#{Pathname.new(dir).relative_path_from(Pathname.pwd)}" }, + file, + ]) + puts fake_command + + # We could run these in parallel, but pretty much all of the + # railties tests already run in parallel, so ¯\_(⊙︿⊙)_/¯ + Process.waitpid fork { + ARGV.clear + Rake.application = nil + + load file + } + + unless $?.success? + failing_files << file + end + end + + unless failing_files.empty? + puts + puts "Failed in:" + failing_files.each do |file| + puts " #{file}" + end + puts + + raise "Failure in isolated test runner" end end end Rake::TestTask.new("test:regular") do |t| - t.libs << "test" << "#{File.dirname(__FILE__)}/../activesupport/lib" + t.libs << "test" << "#{__dir__}/../activesupport/lib" t.pattern = "test/**/*_test.rb" t.warning = false t.verbose = true diff --git a/railties/bin/test b/railties/bin/test index a7beb14b27..c53377cc97 100755 --- a/railties/bin/test +++ b/railties/bin/test @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true COMPONENT_ROOT = File.expand_path("..", __dir__) -require File.expand_path("../tools/test", COMPONENT_ROOT) +require_relative "../../tools/test" diff --git a/railties/exe/rails b/railties/exe/rails index 7e791c1f99..79af4db6b6 100755 --- a/railties/exe/rails +++ b/railties/exe/rails @@ -1,9 +1,10 @@ #!/usr/bin/env ruby +# frozen_string_literal: true -git_path = File.expand_path("../../../.git", __FILE__) +git_path = File.expand_path("../../.git", __dir__) if File.exist?(git_path) - railties_path = File.expand_path("../../lib", __FILE__) + railties_path = File.expand_path("../lib", __dir__) $:.unshift(railties_path) end require "rails/cli" diff --git a/railties/lib/minitest/rails_plugin.rb b/railties/lib/minitest/rails_plugin.rb new file mode 100644 index 0000000000..6901b0bbc8 --- /dev/null +++ b/railties/lib/minitest/rails_plugin.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" +require "rails/test_unit/reporter" +require "rails/test_unit/runner" + +module Minitest + class SuppressedSummaryReporter < SummaryReporter + # Disable extra failure output after a run if output is inline. + def aggregated_results(*) + super unless options[:output_inline] + end + end + + def self.plugin_rails_options(opts, options) + Rails::TestUnit::Runner.attach_before_load_options(opts) + + opts.on("-b", "--backtrace", "Show the complete backtrace") do + options[:full_backtrace] = true + end + + opts.on("-d", "--defer-output", "Output test failures and errors after the test run") do + options[:output_inline] = false + end + + opts.on("-f", "--fail-fast", "Abort test run on first failure or error") do + options[:fail_fast] = true + end + + opts.on("-c", "--[no-]color", "Enable color in the output") do |value| + options[:color] = value + end + + options[:color] = true + options[:output_inline] = true + end + + # Owes great inspiration to test runner trailblazers like RSpec, + # minitest-reporters, maxitest and others. + def self.plugin_rails_init(options) + unless options[:full_backtrace] || ENV["BACKTRACE"] + # Plugin can run without Rails loaded, check before filtering. + Minitest.backtrace_filter = ::Rails.backtrace_cleaner if ::Rails.respond_to?(:backtrace_cleaner) + end + + # Replace progress reporter for colors. + reporter.reporters.delete_if { |reporter| reporter.kind_of?(SummaryReporter) || reporter.kind_of?(ProgressReporter) } + reporter << SuppressedSummaryReporter.new(options[:io], options) + reporter << ::Rails::TestUnitReporter.new(options[:io], options) + end + + # Backwardscompatibility with Rails 5.0 generated plugin test scripts + mattr_reader :run_via, default: {} +end diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index 00add5829d..092105d502 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/ruby_version_check" require "pathname" @@ -87,8 +89,8 @@ module Rails # groups assets: [:development, :test] # # # Returns - # # => [:default, :development, :assets] for Rails.env == "development" - # # => [:default, :production] for Rails.env == "production" + # # => [:default, "development", :assets] for Rails.env == "development" + # # => [:default, "production"] for Rails.env == "production" def groups(*groups) hash = groups.extract_options! env = Rails.env diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb index 7606ea0e46..f5dccd2381 100644 --- a/railties/lib/rails/all.rb +++ b/railties/lib/rails/all.rb @@ -1,7 +1,10 @@ +# frozen_string_literal: true + require "rails" %w( active_record/railtie + active_storage/engine action_controller/railtie action_view/railtie action_mailer/railtie diff --git a/railties/lib/rails/api/generator.rb b/railties/lib/rails/api/generator.rb new file mode 100644 index 0000000000..3405560b74 --- /dev/null +++ b/railties/lib/rails/api/generator.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "sdoc" + +class RDoc::Generator::API < RDoc::Generator::SDoc # :nodoc: + RDoc::RDoc.add_generator self + + def generate_class_tree_level(classes, visited = {}) + # Only process core extensions on the first visit and remove + # Active Storage duplicated classes that are at the top level + # since they aren't nested under a definition of the `ActiveStorage` module. + if visited.empty? + classes = classes.reject { |klass| active_storage?(klass) } + core_exts, classes = classes.partition { |klass| core_extension?(klass) } + + super.unshift([ "Core extensions", "", "", build_core_ext_subtree(core_exts, visited) ]) + else + super + end + end + + private + def build_core_ext_subtree(classes, visited) + classes.map do |klass| + [ klass.name, klass.document_self_or_methods ? klass.path : "", "", + generate_class_tree_level(klass.classes_and_modules, visited) ] + end + end + + def core_extension?(klass) + klass.name != "ActiveSupport" && klass.in_files.any? { |file| file.absolute_name.include?("core_ext") } + end + + def active_storage?(klass) + klass.name != "ActiveStorage" && klass.in_files.all? { |file| file.absolute_name.include?("active_storage") } + end +end diff --git a/railties/lib/rails/api/task.rb b/railties/lib/rails/api/task.rb index bc670b1d75..e7f0557584 100644 --- a/railties/lib/rails/api/task.rb +++ b/railties/lib/rails/api/task.rb @@ -1,4 +1,7 @@ +# frozen_string_literal: true + require "rdoc/task" +require "rails/api/generator" module Rails module API @@ -8,8 +11,7 @@ module Rails include: %w( README.rdoc lib/active_support/**/*.rb - ), - exclude: "lib/active_support/vendor/*" + ) }, "activerecord" => { @@ -64,12 +66,24 @@ module Rails ) }, + "activestorage" => { + include: %w( + README.md + app/**/active_storage/**/*.rb + lib/active_storage/**/*.rb + ) + }, + "railties" => { include: %w( README.rdoc lib/**/*.rb ), - exclude: "lib/rails/generators/rails/**/templates/**/*.rb" + exclude: %w( + lib/rails/generators/**/templates/**/*.rb + lib/rails/test_unit/* + lib/rails/api/generator.rb + ) } } @@ -80,7 +94,7 @@ module Rails # Be lazy computing stuff to have as light impact as possible to # the rest of tasks. before_running_rdoc do - load_and_configure_sdoc + configure_sdoc configure_rdoc_files setup_horo_variables end @@ -91,20 +105,15 @@ module Rails # no-op end - def load_and_configure_sdoc - require "sdoc" - + def configure_sdoc self.title = "Ruby on Rails API" self.rdoc_dir = api_dir options << "-m" << api_main options << "-e" << "UTF-8" - options << "-f" << "sdoc" + options << "-f" << "api" options << "-T" << "rails" - rescue LoadError - $stderr.puts %(Unable to load SDoc, please add\n\n gem 'sdoc', require: false\n\nto the Gemfile.) - exit 1 end def configure_rdoc_files @@ -147,7 +156,7 @@ module Rails end class RepoTask < Task - def load_and_configure_sdoc + def configure_sdoc super options << "-g" # link to GitHub, SDoc flag end diff --git a/railties/lib/rails/app_loader.rb b/railties/lib/rails/app_loader.rb index 525d5f0161..20eb75d95c 100644 --- a/railties/lib/rails/app_loader.rb +++ b/railties/lib/rails/app_loader.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "pathname" require "rails/version" @@ -8,15 +10,26 @@ module Rails RUBY = Gem.ruby EXECUTABLES = ["bin/rails", "script/rails"] BUNDLER_WARNING = <<EOS -Looks like your app's ./bin/rails is a stub that was generated by Bundler. +Beginning in Rails 4, Rails ships with a `rails` binstub at ./bin/rails that +should be used instead of the Bundler-generated `rails` binstub. + +If you are seeing this message, your binstub at ./bin/rails was generated by +Bundler instead of Rails. + +You might need to regenerate your `rails` binstub locally and add it to source +control: + + rails app:update:bin # Bear in mind this generates other binstubs + # too that you may or may not want (like yarn) -In Rails #{Rails::VERSION::MAJOR}, your app's bin/ directory contains executables that are versioned -like any other source code, rather than stubs that are generated on demand. +If you already have Rails binstubs in source control, you might be +inadverently overwriting them during deployment by using bundle install +with the --binstubs option. -Here's how to upgrade: +If your application was created prior to Rails 4, here's how to upgrade: bundle config --delete bin # Turn off Bundler's stub generator - rails app:update:bin # Use the new Rails 5 executables + rails app:update:bin # Use the new Rails executables git add bin # Add bin/ to source control You may need to remove bin/ from your .gitignore as well. diff --git a/railties/lib/rails/app_updater.rb b/railties/lib/rails/app_updater.rb new file mode 100644 index 0000000000..a076d082d5 --- /dev/null +++ b/railties/lib/rails/app_updater.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "rails/generators" +require "rails/generators/rails/app/app_generator" + +module Rails + class AppUpdater # :nodoc: + class << self + def invoke_from_app_generator(method) + app_generator.send(method) + end + + def app_generator + @app_generator ||= begin + gen = Rails::Generators::AppGenerator.new ["rails"], generator_options, destination_root: Rails.root + File.exist?(Rails.root.join("config", "application.rb")) ? gen.send(:app_const) : gen.send(:valid_const?) + gen + end + end + + private + def generator_options + options = { api: !!Rails.application.config.api_only, update: true } + options[:skip_active_record] = !defined?(ActiveRecord::Railtie) + options[:skip_active_storage] = !defined?(ActiveStorage::Engine) || !defined?(ActiveRecord::Railtie) + options[:skip_action_mailer] = !defined?(ActionMailer::Railtie) + options[:skip_action_cable] = !defined?(ActionCable::Engine) + options[:skip_sprockets] = !defined?(Sprockets::Railtie) + options[:skip_puma] = !defined?(Puma) + options + end + end + end +end diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 1a6aed7ce4..a200a1005c 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -1,9 +1,14 @@ +# frozen_string_literal: true + require "yaml" require "active_support/core_ext/hash/keys" require "active_support/core_ext/object/blank" require "active_support/key_generator" require "active_support/message_verifier" +require "active_support/encrypted_configuration" +require "active_support/deprecation" require "rails/engine" +require "rails/secrets" module Rails # An Engine with the responsibility of coordinating the whole boot process. @@ -168,12 +173,10 @@ module Rails # number of iterations selected based on consultation with the google security # team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220 @caching_key_generator ||= - if secrets.secret_key_base - unless secrets.secret_key_base.kind_of?(String) - raise ArgumentError, "`secret_key_base` for #{Rails.env} environment must be a type of String, change this value in `config/secrets.yml`" - end - key_generator = ActiveSupport::KeyGenerator.new(secrets.secret_key_base, iterations: 1000) - ActiveSupport::CachingKeyGenerator.new(key_generator) + if secret_key_base + ActiveSupport::CachingKeyGenerator.new( + ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000) + ) else ActiveSupport::LegacyKeyGenerator.new(secrets.secret_token) end @@ -243,13 +246,11 @@ module Rails # will be used by middlewares and engines to configure themselves. def env_config @app_env_config ||= begin - validate_secret_key_config! - super.merge( "action_dispatch.parameter_filter" => config.filter_parameters, "action_dispatch.redirect_filter" => config.filter_redirect, "action_dispatch.secret_token" => secrets.secret_token, - "action_dispatch.secret_key_base" => secrets.secret_key_base, + "action_dispatch.secret_key_base" => secret_key_base, "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions, "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local, "action_dispatch.logger" => Rails.logger, @@ -259,8 +260,15 @@ module Rails "action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt, "action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt, "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt, + "action_dispatch.authenticated_encrypted_cookie_salt" => config.action_dispatch.authenticated_encrypted_cookie_salt, + "action_dispatch.use_authenticated_cookie_encryption" => config.action_dispatch.use_authenticated_cookie_encryption, + "action_dispatch.encrypted_cookie_cipher" => config.action_dispatch.encrypted_cookie_cipher, + "action_dispatch.signed_cookie_digest" => config.action_dispatch.signed_cookie_digest, "action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer, - "action_dispatch.cookies_digest" => config.action_dispatch.cookies_digest + "action_dispatch.cookies_digest" => config.action_dispatch.cookies_digest, + "action_dispatch.cookies_rotations" => config.action_dispatch.cookies_rotations, + "action_dispatch.content_security_policy" => config.content_security_policy, + "action_dispatch.content_security_policy_report_only" => config.content_security_policy_report_only ) end end @@ -385,24 +393,21 @@ module Rails def secrets @secrets ||= begin secrets = ActiveSupport::OrderedOptions.new - yaml = config.paths["config/secrets"].first - - if File.exist?(yaml) - require "erb" - - all_secrets = YAML.load(ERB.new(IO.read(yaml)).result) || {} - shared_secrets = all_secrets["shared"] - env_secrets = all_secrets[Rails.env] - - secrets.merge!(shared_secrets.deep_symbolize_keys) if shared_secrets - secrets.merge!(env_secrets.deep_symbolize_keys) if env_secrets - end + files = config.paths["config/secrets"].existent + files = files.reject { |path| path.end_with?(".enc") } unless config.read_encrypted_secrets + secrets.merge! Rails::Secrets.parse(files, env: Rails.env) # Fallback to config.secret_key_base if secrets.secret_key_base isn't set secrets.secret_key_base ||= config.secret_key_base # Fallback to config.secret_token if secrets.secret_token isn't set secrets.secret_token ||= config.secret_token + if secrets.secret_token.present? + ActiveSupport::Deprecation.warn( + "`secrets.secret_token` is deprecated in favor of `secret_key_base` and will be removed in Rails 6.0." + ) + end + secrets end end @@ -411,6 +416,67 @@ module Rails @secrets = secrets end + # The secret_key_base is used as the input secret to the application's key generator, which in turn + # is used to create all MessageVerifiers/MessageEncryptors, including the ones that sign and encrypt cookies. + # + # In test and development, this is simply derived as a MD5 hash of the application's name. + # + # In all other environments, we look for it first in ENV["SECRET_KEY_BASE"], + # then credentials.secret_key_base, and finally secrets.secret_key_base. For most applications, + # the correct place to store it is in the encrypted credentials file. + def secret_key_base + if Rails.env.test? || Rails.env.development? + Digest::MD5.hexdigest self.class.name + else + validate_secret_key_base( + ENV["SECRET_KEY_BASE"] || credentials.secret_key_base || secrets.secret_key_base + ) + end + end + + # Decrypts the credentials hash as kept in +config/credentials.yml.enc+. This file is encrypted with + # the Rails master key, which is either taken from <tt>ENV["RAILS_MASTER_KEY"]</tt> or from loading + # +config/master.key+. + def credentials + @credentials ||= encrypted("config/credentials.yml.enc") + end + + # Shorthand to decrypt any encrypted configurations or files. + # + # For any file added with <tt>bin/rails encrypted:edit</tt> call +read+ to decrypt + # the file with the master key. + # The master key is either stored in +config/master.key+ or <tt>ENV["RAILS_MASTER_KEY"]</tt>. + # + # Rails.application.encrypted("config/mystery_man.txt.enc").read + # # => "We've met before, haven't we?" + # + # It's also possible to interpret encrypted YAML files with +config+. + # + # Rails.application.encrypted("config/credentials.yml.enc").config + # # => { next_guys_line: "I don't think so. Where was it you think we met?" } + # + # Any top-level configs are also accessible directly on the return value: + # + # Rails.application.encrypted("config/credentials.yml.enc").next_guys_line + # # => "I don't think so. Where was it you think we met?" + # + # The files or configs can also be encrypted with a custom key. To decrypt with + # a key in the +ENV+, use: + # + # Rails.application.encrypted("config/special_tokens.yml.enc", env_key: "SPECIAL_TOKENS") + # + # Or to decrypt with a file, that should be version control ignored, relative to +Rails.root+: + # + # Rails.application.encrypted("config/special_tokens.yml.enc", key_path: "config/special_tokens.key") + def encrypted(path, key_path: "config/master.key", env_key: "RAILS_MASTER_KEY") + ActiveSupport::EncryptedConfiguration.new( + config_path: Rails.root.join(path), + key_path: Rails.root.join(key_path), + env_key: env_key, + raise_if_missing_key: config.require_master_key + ) + end + def to_app #:nodoc: self end @@ -509,14 +575,13 @@ module Rails default_stack.build_stack end - def validate_secret_key_config! #:nodoc: - if secrets.secret_key_base.blank? - ActiveSupport::Deprecation.warn "You didn't set `secret_key_base`. " \ - "Read the upgrade documentation to learn more about this new config option." - - if secrets.secret_token.blank? - raise "Missing `secret_key_base` for '#{Rails.env}' environment, set this value in `config/secrets.yml`" - end + def validate_secret_key_base(secret_key_base) + if secret_key_base.is_a?(String) && secret_key_base.present? + secret_key_base + elsif secret_key_base + raise ArgumentError, "`secret_key_base` for #{Rails.env} environment must be a type of String`" + elsif secrets.secret_token.blank? + raise ArgumentError, "Missing `secret_key_base` for '#{Rails.env}' environment, set this string with `rails credentials:edit`" end end diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb index 6102af3fff..e3c0759f95 100644 --- a/railties/lib/rails/application/bootstrap.rb +++ b/railties/lib/rails/application/bootstrap.rb @@ -1,7 +1,10 @@ +# frozen_string_literal: true + require "fileutils" require "active_support/notifications" require "active_support/dependencies" require "active_support/descendants_tracker" +require "rails/secrets" module Rails class Application @@ -77,6 +80,10 @@ INFO initializer :bootstrap_hook, group: :all do |app| ActiveSupport.run_load_hooks(:before_initialize, app) end + + initializer :set_secrets_root, group: :all do + Rails::Secrets.root = root + end end end end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index b0d33f87a3..5d8d6740c8 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/kernel/reporting" require "active_support/file_update_checker" require "rails/engine/configuration" @@ -13,44 +15,105 @@ module Rails :railties_order, :relative_url_root, :secret_key_base, :secret_token, :ssl_options, :public_file_server, :session_options, :time_zone, :reload_classes_only_on_change, - :beginning_of_week, :filter_redirect, :x, :enable_dependency_loading + :beginning_of_week, :filter_redirect, :x, :enable_dependency_loading, + :read_encrypted_secrets, :log_level, :content_security_policy_report_only, + :require_master_key - attr_writer :log_level attr_reader :encoding, :api_only def initialize(*) super - self.encoding = Encoding::UTF_8 - @allow_concurrency = nil - @consider_all_requests_local = false - @filter_parameters = [] - @filter_redirect = [] - @helpers_paths = [] - @public_file_server = ActiveSupport::OrderedOptions.new - @public_file_server.enabled = true - @public_file_server.index_name = "index" - @force_ssl = false - @ssl_options = {} - @session_store = nil - @time_zone = "UTC" - @beginning_of_week = :monday - @log_level = nil - @generators = app_generators - @cache_store = [ :file_store, "#{root}/tmp/cache/" ] - @railties_order = [:all] - @relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"] - @reload_classes_only_on_change = true - @file_watcher = ActiveSupport::FileUpdateChecker - @exceptions_app = nil - @autoflush_log = true - @log_formatter = ActiveSupport::Logger::SimpleFormatter.new - @eager_load = nil - @secret_token = nil - @secret_key_base = nil - @api_only = false - @debug_exception_response_format = nil - @x = Custom.new - @enable_dependency_loading = false + self.encoding = Encoding::UTF_8 + @allow_concurrency = nil + @consider_all_requests_local = false + @filter_parameters = [] + @filter_redirect = [] + @helpers_paths = [] + @public_file_server = ActiveSupport::OrderedOptions.new + @public_file_server.enabled = true + @public_file_server.index_name = "index" + @force_ssl = false + @ssl_options = {} + @session_store = nil + @time_zone = "UTC" + @beginning_of_week = :monday + @log_level = :debug + @generators = app_generators + @cache_store = [ :file_store, "#{root}/tmp/cache/" ] + @railties_order = [:all] + @relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"] + @reload_classes_only_on_change = true + @file_watcher = ActiveSupport::FileUpdateChecker + @exceptions_app = nil + @autoflush_log = true + @log_formatter = ActiveSupport::Logger::SimpleFormatter.new + @eager_load = nil + @secret_token = nil + @secret_key_base = nil + @api_only = false + @debug_exception_response_format = nil + @x = Custom.new + @enable_dependency_loading = false + @read_encrypted_secrets = false + @content_security_policy = nil + @content_security_policy_report_only = false + @require_master_key = false + end + + def load_defaults(target_version) + case target_version.to_s + when "5.0" + if respond_to?(:action_controller) + action_controller.per_form_csrf_tokens = true + action_controller.forgery_protection_origin_check = true + end + + ActiveSupport.to_time_preserves_timezone = true + + if respond_to?(:active_record) + active_record.belongs_to_required_by_default = true + end + + self.ssl_options = { hsts: { subdomains: true } } + when "5.1" + load_defaults "5.0" + + if respond_to?(:assets) + assets.unknown_asset_fallback = false + end + + if respond_to?(:action_view) + action_view.form_with_generates_remote_forms = true + end + when "5.2" + load_defaults "5.1" + + if respond_to?(:active_record) + active_record.cache_versioning = true + # Remove the temporary load hook from SQLite3Adapter when this is removed + ActiveSupport.on_load(:active_record_sqlite3adapter) do + ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true + end + end + + if respond_to?(:action_dispatch) + action_dispatch.use_authenticated_cookie_encryption = true + end + + if respond_to?(:active_support) + active_support.use_authenticated_message_encryption = true + end + + if respond_to?(:action_controller) + action_controller.default_protect_from_forgery = true + end + + if respond_to?(:action_view) + action_view.form_with_generates_ids = true + end + else + raise "Unknown version #{target_version.to_s.inspect}" + end end def encoding=(value) @@ -80,7 +143,7 @@ module Rails @paths ||= begin paths = super paths.add "config/database", with: "config/database.yml" - paths.add "config/secrets", with: "config/secrets.yml" + paths.add "config/secrets", with: "config", glob: "secrets.yml{,.enc}" paths.add "config/environment", with: "config/environment.rb" paths.add "lib/templates" paths.add "log", with: "log/#{Rails.env}.log" @@ -93,7 +156,7 @@ module Rails end # Loads and returns the entire raw configuration of database from - # values stored in `config/database.yml`. + # values stored in <tt>config/database.yml</tt>. def database_configuration path = paths["config/database"].existent.first yaml = Pathname.new(path) if path @@ -101,7 +164,14 @@ module Rails config = if yaml && yaml.exist? require "yaml" require "erb" - YAML.load(ERB.new(yaml.read).result) || {} + loaded_yaml = YAML.load(ERB.new(yaml.read).result) || {} + shared = loaded_yaml.delete("shared") + if shared + loaded_yaml.each do |_k, values| + values.reverse_merge!(shared) + end + end + Hash.new(shared).merge(loaded_yaml) elsif ENV["DATABASE_URL"] # Value from ENV['DATABASE_URL'] is set to default database connection # by Active Record. @@ -119,10 +189,6 @@ module Rails raise e, "Cannot load `Rails.application.database_configuration`:\n#{e.message}", e.backtrace end - def log_level - @log_level ||= (Rails.env.production? ? :info : :debug) - end - def colorize_logging ActiveSupport::LogSubscriber.colorize_logging end @@ -167,6 +233,10 @@ module Rails SourceAnnotationExtractor::Annotation end + def content_security_policy(&block) + @content_security_policy ||= ActionDispatch::ContentSecurityPolicy.new(&block) + end + class Custom #:nodoc: def initialize @configurations = Hash.new diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb index 8fe48feefb..0e79ba7da0 100644 --- a/railties/lib/rails/application/default_middleware_stack.rb +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails class Application class DefaultMiddlewareStack @@ -10,7 +12,7 @@ module Rails end def build_stack - ActionDispatch::MiddlewareStack.new.tap do |middleware| + ActionDispatch::MiddlewareStack.new do |middleware| if config.force_ssl middleware.use ::ActionDispatch::SSL, config.ssl_options end @@ -61,6 +63,10 @@ module Rails middleware.use ::ActionDispatch::Flash end + unless config.api_only + middleware.use ::ActionDispatch::ContentSecurityPolicy::Middleware + end + middleware.use ::Rack::Head middleware.use ::Rack::ConditionalGet middleware.use ::Rack::ETag, "no-cache" diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index c027d06663..c4b188aeee 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails class Application module Finisher @@ -56,7 +58,7 @@ module Rails end # This needs to happen before eager load so it happens - # in exactly the same point regardless of config.cache_classes + # in exactly the same point regardless of config.eager_load initializer :run_prepare_callbacks do |app| app.reloader.prepare! end diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb index e02ef629f2..2ef09b4162 100644 --- a/railties/lib/rails/application/routes_reloader.rb +++ b/railties/lib/rails/application/routes_reloader.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/module/delegation" module Rails diff --git a/railties/lib/rails/application_controller.rb b/railties/lib/rails/application_controller.rb index a98e51fd28..fa8793d81a 100644 --- a/railties/lib/rails/application_controller.rb +++ b/railties/lib/rails/application_controller.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + class Rails::ApplicationController < ActionController::Base # :nodoc: - self.view_paths = File.expand_path("../templates", __FILE__) + self.view_paths = File.expand_path("templates", __dir__) layout "application" private diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb index 5c833e12ba..ae8db0f8ef 100644 --- a/railties/lib/rails/backtrace_cleaner.rb +++ b/railties/lib/rails/backtrace_cleaner.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/backtrace_cleaner" module Rails @@ -16,7 +18,7 @@ module Rails add_filter { |line| line.sub(DOT_SLASH, SLASH) } # for tests add_gem_filters - add_silencer { |line| line !~ APP_DIRS_PATTERN } + add_silencer { |line| !APP_DIRS_PATTERN.match?(line) } end private diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb index 973b746068..e56e604fdc 100644 --- a/railties/lib/rails/cli.rb +++ b/railties/lib/rails/cli.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/app_loader" # If we are inside a Rails application this method performs an exec and thus diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb index 9c4bd16aad..9c447c366f 100644 --- a/railties/lib/rails/code_statistics.rb +++ b/railties/lib/rails/code_statistics.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/code_statistics_calculator" require "active_support/core_ext/enumerable" @@ -7,7 +9,8 @@ class CodeStatistics #:nodoc: "Model tests", "Mailer tests", "Job tests", - "Integration tests"] + "Integration tests", + "System tests"] HEADERS = { lines: " Lines", code_lines: " LOC", classes: "Classes", methods: "Methods" } diff --git a/railties/lib/rails/code_statistics_calculator.rb b/railties/lib/rails/code_statistics_calculator.rb index d0194af197..85f86bdbd0 100644 --- a/railties/lib/rails/code_statistics_calculator.rb +++ b/railties/lib/rails/code_statistics_calculator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CodeStatisticsCalculator #:nodoc: attr_reader :lines, :code_lines, :classes, :methods diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb index 13f3b90b6d..812e846837 100644 --- a/railties/lib/rails/command.rb +++ b/railties/lib/rails/command.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support" require "active_support/dependencies/autoload" require "active_support/core_ext/enumerable" @@ -23,19 +25,27 @@ module Rails end def environment # :nodoc: - ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development" + ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence || "development" end # Receives a namespace, arguments and the behavior to invoke the command. - def invoke(namespace, args = [], **config) - namespace = namespace.to_s - namespace = "help" if namespace.blank? || HELP_MAPPINGS.include?(namespace) - namespace = "version" if %w( -v --version ).include? namespace + def invoke(full_namespace, args = [], **config) + namespace = full_namespace = full_namespace.to_s + + if char = namespace =~ /:(\w+)$/ + command_name, namespace = $1, namespace.slice(0, char) + else + command_name = namespace + end + + command_name, namespace = "help", "help" if command_name.blank? || HELP_MAPPINGS.include?(command_name) + command_name, namespace = "version", "version" if %w( -v --version ).include?(command_name) - if command = find_by_namespace(namespace) - command.perform(namespace, args, config) + command = find_by_namespace(namespace, command_name) + if command && command.all_commands[command_name] + command.perform(command_name, args, config) else - find_by_namespace("rake").perform(namespace, args, config) + find_by_namespace("rake").perform(full_namespace, args, config) end end @@ -52,8 +62,10 @@ module Rails # # Notice that "rails:commands:webrat" could be loaded as well, what # Rails looks for is the first and last parts of the namespace. - def find_by_namespace(name) # :nodoc: - lookups = [ name, "rails:#{name}" ] + def find_by_namespace(namespace, command_name = nil) # :nodoc: + lookups = [ namespace ] + lookups << "#{namespace}:#{command_name}" if command_name + lookups.concat lookups.map { |lookup| "rails:#{lookup}" } lookup(lookups) diff --git a/railties/lib/rails/command/actions.rb b/railties/lib/rails/command/actions.rb index fb80e9d997..cbb743346b 100644 --- a/railties/lib/rails/command/actions.rb +++ b/railties/lib/rails/command/actions.rb @@ -1,23 +1,25 @@ +# frozen_string_literal: true + module Rails module Command module Actions - # Change to the application's path if there is no config.ru file in current directory. - # This allows us to run `rails server` from other directories, but still get - # the main config.ru and properly set the tmp directory. + # Change to the application's path if there is no <tt>config.ru</tt> file in current directory. + # This allows us to run <tt>rails server</tt> from other directories, but still get + # the main <tt>config.ru</tt> and properly set the <tt>tmp</tt> directory. def set_application_directory! - Dir.chdir(File.expand_path("../../", APP_PATH)) unless File.exist?(File.expand_path("config.ru")) + Dir.chdir(File.expand_path("../..", APP_PATH)) unless File.exist?(File.expand_path("config.ru")) end - if defined?(ENGINE_PATH) - def require_application_and_environment! - require ENGINE_PATH + def require_application_and_environment! + require ENGINE_PATH if defined?(ENGINE_PATH) - if defined?(APP_PATH) - require APP_PATH - Rails.application.require_environment! - end + if defined?(APP_PATH) + require APP_PATH + Rails.application.require_environment! end + end + if defined?(ENGINE_PATH) def load_tasks Rake.application.init("rails") Rake.application.load_rakefile @@ -29,11 +31,6 @@ module Rails engine.load_generators end else - def require_application_and_environment! - require APP_PATH - Rails.application.require_environment! - end - def load_tasks Rails.application.load_tasks end diff --git a/railties/lib/rails/command/base.rb b/railties/lib/rails/command/base.rb index 1435792536..fa462ef7e9 100644 --- a/railties/lib/rails/command/base.rb +++ b/railties/lib/rails/command/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "thor" require "erb" @@ -56,13 +58,15 @@ module Rails end def perform(command, args, config) # :nodoc: - command = nil if Rails::Command::HELP_MAPPINGS.include?(args.first) + if Rails::Command::HELP_MAPPINGS.include?(args.first) + command, args = "help", [] + end dispatch(command, args.dup, nil, config) end def printing_commands - namespace.sub(/^rails:/, "") + namespaced_commands end def executable @@ -71,7 +75,7 @@ module Rails # Use Rails' default banner. def banner(*) - "#{executable} #{arguments.map(&:usage).join(' ')} [options]".squish! + "#{executable} #{arguments.map(&:usage).join(' ')} [options]".squish end # Sets the base_name taking into account the current class namespace. @@ -108,10 +112,10 @@ module Rails # Default file root to place extra files a command might need, placed # one folder above the command file. # - # For a `Rails::Command::TestCommand` placed in `rails/command/test_command.rb` - # would return `rails/test`. + # For a Rails::Command::TestCommand placed in <tt>rails/command/test_command.rb</tt> + # would return <tt>rails/test</tt>. def default_command_root - path = File.expand_path(File.join("../commands", command_name), __dir__) + path = File.expand_path(File.join("../commands", command_root_namespace), __dir__) path if File.exist?(path) end @@ -129,6 +133,16 @@ module Rails super end end + + def command_root_namespace + (namespace.split(":") - %w( rails )).first + end + + def namespaced_commands + commands.keys.map do |key| + key == command_root_namespace ? key : "#{command_root_namespace}:#{key}" + end + end end def help diff --git a/railties/lib/rails/command/behavior.rb b/railties/lib/rails/command/behavior.rb index 4a92f72f16..7a6dd28e1a 100644 --- a/railties/lib/rails/command/behavior.rb +++ b/railties/lib/rails/command/behavior.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support" module Rails diff --git a/railties/lib/rails/command/environment_argument.rb b/railties/lib/rails/command/environment_argument.rb index 05eac34155..5dc98b113d 100644 --- a/railties/lib/rails/command/environment_argument.rb +++ b/railties/lib/rails/command/environment_argument.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support" module Rails @@ -7,13 +9,24 @@ module Rails included do argument :environment, optional: true, banner: "environment" + + class_option :environment, aliases: "-e", type: :string, + desc: "Specifies the environment to run this console under (test/development/production)." end private def extract_environment_option_from_argument if environment self.options = options.merge(environment: acceptable_environment(environment)) - elsif !options[:environment] + + ActiveSupport::Deprecation.warn "Passing the environment's name as a " \ + "regular argument is deprecated and " \ + "will be removed in the next Rails " \ + "version. Please, use the -e option " \ + "instead." + elsif options[:environment] + self.options = options.merge(environment: acceptable_environment(options[:environment])) + else self.options = options.merge(environment: Rails::Command.environment) end end diff --git a/railties/lib/rails/command/helpers/editor.rb b/railties/lib/rails/command/helpers/editor.rb new file mode 100644 index 0000000000..6191d97672 --- /dev/null +++ b/railties/lib/rails/command/helpers/editor.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "active_support/encrypted_file" + +module Rails + module Command + module Helpers + module Editor + private + def ensure_editor_available(command:) + if ENV["EDITOR"].to_s.empty? + say "No $EDITOR to open file in. Assign one like this:" + say "" + say %(EDITOR="mate --wait" #{command}) + say "" + say "For editors that fork and exit immediately, it's important to pass a wait flag," + say "otherwise the credentials will be saved immediately with no chance to edit." + + false + else + true + end + end + + def catch_editing_exceptions + yield + rescue Interrupt + say "Aborted changing file: nothing saved." + rescue ActiveSupport::EncryptedFile::MissingKeyError => error + say error.message + end + end + end + end +end diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index fff0119c65..77961a0292 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/command" aliases = { diff --git a/railties/lib/rails/commands/application/application_command.rb b/railties/lib/rails/commands/application/application_command.rb index 7675d3b3d1..f77553b830 100644 --- a/railties/lib/rails/commands/application/application_command.rb +++ b/railties/lib/rails/commands/application/application_command.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators" require "rails/generators/rails/app/app_generator" diff --git a/railties/lib/rails/commands/console/console_command.rb b/railties/lib/rails/commands/console/console_command.rb index 62e3aa19df..e35faa5b01 100644 --- a/railties/lib/rails/commands/console/console_command.rb +++ b/railties/lib/rails/commands/console/console_command.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "irb" require "irb/completion" @@ -70,8 +72,19 @@ module Rails class_option :sandbox, aliases: "-s", type: :boolean, default: false, desc: "Rollback database modifications on exit." - class_option :environment, aliases: "-e", type: :string, - desc: "Specifies the environment to run this console under (test/development/production)." + def initialize(args = [], local_options = {}, config = {}) + console_options = [] + + # For the same behavior as OptionParser, leave only options after "--" in ARGV. + termination = local_options.find_index("--") + if termination + console_options = local_options[termination + 1..-1] + local_options = local_options[0...termination] + end + + ARGV.replace(console_options) + super(args, local_options, config) + end def perform extract_environment_option_from_argument @@ -79,8 +92,6 @@ module Rails # RAILS_ENV needs to be set before config/application is required. ENV["RAILS_ENV"] = options[:environment] - ARGV.clear # Clear ARGV so IRB doesn't freak. - require_application_and_environment! Rails::Console.start(Rails.application, options) end diff --git a/railties/lib/rails/commands/credentials/USAGE b/railties/lib/rails/commands/credentials/USAGE new file mode 100644 index 0000000000..85877c71b7 --- /dev/null +++ b/railties/lib/rails/commands/credentials/USAGE @@ -0,0 +1,40 @@ +=== Storing Encrypted Credentials in Source Control + +The Rails `credentials` commands provide access to encrypted credentials, +so you can safely store access tokens, database passwords, and the like +safely inside the app without relying on a mess of ENVs. + +This also allows for atomic deploys: no need to coordinate key changes +to get everything working as the keys are shipped with the code. + +=== Setup + +Applications after Rails 5.2 automatically have a basic credentials file generated +that just contains the secret_key_base used by MessageVerifiers/MessageEncryptors, like the ones +signing and encrypting cookies. + +For applications created prior to Rails 5.2, we'll automatically generate a new +credentials file in `config/credentials.yml.enc` the first time you run `bin/rails credentials:edit`. +If you didn't have a master key saved in `config/master.key`, that'll be created too. + +Don't lose this master key! Put it in a password manager your team can access. +Should you lose it no one, including you, will be able to access any encrypted +credentials. + +Don't commit the key! Add `config/master.key` to your source control's +ignore file. If you use Git, Rails handles this for you. + +Rails also looks for the master key in `ENV["RAILS_MASTER_KEY"]`, if that's easier to manage. + +You could prepend that to your server's start command like this: + + RAILS_MASTER_KEY="very-secret-and-secure" server.start + +=== Editing Credentials + +This will open a temporary file in `$EDITOR` with the decrypted contents to edit +the encrypted credentials. + +When the temporary file is next saved the contents are encrypted and written to +`config/credentials.yml.enc` while the file itself is destroyed to prevent credentials +from leaking. diff --git a/railties/lib/rails/commands/credentials/credentials_command.rb b/railties/lib/rails/commands/credentials/credentials_command.rb new file mode 100644 index 0000000000..385d3976da --- /dev/null +++ b/railties/lib/rails/commands/credentials/credentials_command.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require "active_support" +require "rails/command/helpers/editor" + +module Rails + module Command + class CredentialsCommand < Rails::Command::Base # :nodoc: + include Helpers::Editor + + no_commands do + def help + say "Usage:\n #{self.class.banner}" + say "" + say self.class.desc + end + end + + def edit + require_application_and_environment! + + ensure_editor_available(command: "bin/rails credentials:edit") || (return) + ensure_master_key_has_been_added + ensure_credentials_have_been_added + + catch_editing_exceptions do + change_credentials_in_system_editor + end + + say "New credentials encrypted and saved." + end + + def show + require_application_and_environment! + + say Rails.application.credentials.read.presence || missing_credentials_message + end + + private + def ensure_master_key_has_been_added + master_key_generator.add_master_key_file + master_key_generator.ignore_master_key_file + end + + def ensure_credentials_have_been_added + credentials_generator.add_credentials_file_silently + end + + def change_credentials_in_system_editor + Rails.application.credentials.change do |tmp_path| + system("#{ENV["EDITOR"]} #{tmp_path}") + end + end + + + def master_key_generator + require "rails/generators" + require "rails/generators/rails/master_key/master_key_generator" + + Rails::Generators::MasterKeyGenerator.new + end + + def credentials_generator + require "rails/generators" + require "rails/generators/rails/credentials/credentials_generator" + + Rails::Generators::CredentialsGenerator.new + end + + def missing_credentials_message + if Rails.application.credentials.key.nil? + "Missing master key to decrypt credentials. See bin/rails credentials:help" + else + "No credentials have been added yet. Use bin/rails credentials:edit to change that." + end + end + end + end +end diff --git a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb index 588fb06b15..8df548b5de 100644 --- a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb +++ b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/command/environment_argument" module Rails @@ -11,7 +13,7 @@ module Rails end def start - ENV["RAILS_ENV"] = @options[:environment] || environment + ENV["RAILS_ENV"] ||= @options[:environment] || environment case config["adapter"] when /^(jdbc)?mysql/ @@ -58,7 +60,7 @@ module Rails logon = "" if config["username"] - logon = config["username"] + logon = config["username"].dup logon << "/#{config['password']}" if config["password"] && @options["include_password"] logon << "@#{config['database']}" if config["database"] end @@ -73,7 +75,7 @@ module Rails args += ["-P", "#{config['password']}"] if config["password"] if config["host"] - host_arg = "#{config['host']}" + host_arg = "#{config['host']}".dup host_arg << ":#{config['port']}" if config["port"] args += ["-S", host_arg] end @@ -87,10 +89,15 @@ module Rails def config @config ||= begin - if configurations[environment].blank? + # We need to check whether the user passed the connection the + # first time around to show a consistent error message to people + # relying on 2-level database configuration. + if @options["connection"] && configurations[connection].blank? + raise ActiveRecord::AdapterNotSpecified, "'#{connection}' connection is not configured. Available configuration: #{configurations.inspect}" + elsif configurations[environment].blank? && configurations[connection].blank? raise ActiveRecord::AdapterNotSpecified, "'#{environment}' database is not configured. Available configuration: #{configurations.inspect}" else - configurations[environment] + configurations[environment].presence || configurations[connection] end end end @@ -99,6 +106,10 @@ module Rails Rails.respond_to?(:env) ? Rails.env : Rails::Command.environment end + def connection + @options.fetch(:connection, "primary") + end + private def configurations # :doc: require APP_PATH @@ -140,14 +151,18 @@ module Rails class_option :mode, enum: %w( html list line column ), type: :string, desc: "Automatically put the sqlite3 database in the specified mode (html, list, line, column)." - class_option :header, type: :string + class_option :header, type: :boolean - class_option :environment, aliases: "-e", type: :string, - desc: "Specifies the environment to run this console under (test/development/production)." + class_option :connection, aliases: "-c", type: :string, + desc: "Specifies the connection to use." def perform extract_environment_option_from_argument + # RAILS_ENV needs to be set before config/application is required. + ENV["RAILS_ENV"] = options[:environment] + + require_application_and_environment! Rails::DBConsole.start(options) end end diff --git a/railties/lib/rails/commands/destroy/destroy_command.rb b/railties/lib/rails/commands/destroy/destroy_command.rb index 5b552b2070..dd432d28fd 100644 --- a/railties/lib/rails/commands/destroy/destroy_command.rb +++ b/railties/lib/rails/commands/destroy/destroy_command.rb @@ -1,10 +1,17 @@ +# frozen_string_literal: true + require "rails/generators" module Rails module Command class DestroyCommand < Base # :nodoc: - def help - Rails::Generators.help self.class.command_name + no_commands do + def help + require_application_and_environment! + load_generators + + Rails::Generators.help self.class.command_name + end end def perform(*) @@ -12,9 +19,9 @@ module Rails return help unless generator require_application_and_environment! - Rails.application.load_generators + load_generators - Rails::Generators.invoke generator, args, behavior: :revoke, destination_root: Rails.root + Rails::Generators.invoke generator, args, behavior: :revoke, destination_root: Rails::Command.root end end end diff --git a/railties/lib/rails/commands/encrypted/encrypted_command.rb b/railties/lib/rails/commands/encrypted/encrypted_command.rb new file mode 100644 index 0000000000..912c453f09 --- /dev/null +++ b/railties/lib/rails/commands/encrypted/encrypted_command.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "pathname" +require "active_support" +require "rails/command/helpers/editor" + +module Rails + module Command + class EncryptedCommand < Rails::Command::Base # :nodoc: + include Helpers::Editor + + class_option :key, aliases: "-k", type: :string, + default: "config/master.key", desc: "The Rails.root relative path to the encryption key" + + no_commands do + def help + say "Usage:\n #{self.class.banner}" + say "" + end + end + + def edit(file_path) + require_application_and_environment! + + ensure_editor_available(command: "bin/rails encrypted:edit") || (return) + ensure_encryption_key_has_been_added(options[:key]) + ensure_encrypted_file_has_been_added(file_path, options[:key]) + + catch_editing_exceptions do + change_encrypted_file_in_system_editor(file_path, options[:key]) + end + + say "File encrypted and saved." + rescue ActiveSupport::MessageEncryptor::InvalidMessage + say "Couldn't decrypt #{file_path}. Perhaps you passed the wrong key?" + end + + def show(file_path) + require_application_and_environment! + encrypted = Rails.application.encrypted(file_path, key_path: options[:key]) + + say encrypted.read.presence || missing_encrypted_message(key: encrypted.key, key_path: options[:key], file_path: file_path) + end + + private + def ensure_encryption_key_has_been_added(key_path) + encryption_key_file_generator.add_key_file(key_path) + encryption_key_file_generator.ignore_key_file(key_path) + end + + def ensure_encrypted_file_has_been_added(file_path, key_path) + encrypted_file_generator.add_encrypted_file_silently(file_path, key_path) + end + + def change_encrypted_file_in_system_editor(file_path, key_path) + Rails.application.encrypted(file_path, key_path: key_path).change do |tmp_path| + system("#{ENV["EDITOR"]} #{tmp_path}") + end + end + + + def encryption_key_file_generator + require "rails/generators" + require "rails/generators/rails/encryption_key_file/encryption_key_file_generator" + + Rails::Generators::EncryptionKeyFileGenerator.new + end + + def encrypted_file_generator + require "rails/generators" + require "rails/generators/rails/encrypted_file/encrypted_file_generator" + + Rails::Generators::EncryptedFileGenerator.new + end + + def missing_encrypted_message(key:, key_path:, file_path:) + if key.nil? + "Missing '#{key_path}' to decrypt data. See bin/rails encrypted:help" + else + "File '#{file_path}' does not exist. Use bin/rails encrypted:edit #{file_path} to change that." + end + end + end + end +end diff --git a/railties/lib/rails/commands/generate/generate_command.rb b/railties/lib/rails/commands/generate/generate_command.rb index aa8dab71b0..93d7a0ce3a 100644 --- a/railties/lib/rails/commands/generate/generate_command.rb +++ b/railties/lib/rails/commands/generate/generate_command.rb @@ -1,10 +1,17 @@ +# frozen_string_literal: true + require "rails/generators" module Rails module Command class GenerateCommand < Base # :nodoc: - def help - Rails::Generators.help self.class.command_name + no_commands do + def help + require_application_and_environment! + load_generators + + Rails::Generators.help self.class.command_name + end end def perform(*) diff --git a/railties/lib/rails/commands/help/USAGE b/railties/lib/rails/commands/help/USAGE index c5f8ab72bb..8eb98319d2 100644 --- a/railties/lib/rails/commands/help/USAGE +++ b/railties/lib/rails/commands/help/USAGE @@ -1,13 +1,14 @@ The most common rails commands are: - generate Generate new code (short-cut alias: "g") - console Start the Rails console (short-cut alias: "c") - server Start the Rails server (short-cut alias: "s") - test Run tests (short-cut alias: "t") - dbconsole Start a console for the database specified in config/database.yml - (short-cut alias: "db") + generate Generate new code (short-cut alias: "g") + console Start the Rails console (short-cut alias: "c") + server Start the Rails server (short-cut alias: "s") + test Run tests except system tests (short-cut alias: "t") + test:system Run system tests + dbconsole Start a console for the database specified in config/database.yml + (short-cut alias: "db") <% unless engine? %> - new Create a new Rails application. "rails new my_app" creates a - new application called MyApp in "./my_app" + new Create a new Rails application. "rails new my_app" creates a + new application called MyApp in "./my_app" <% end %> All commands can be run with -h (or --help) for more information. diff --git a/railties/lib/rails/commands/help/help_command.rb b/railties/lib/rails/commands/help/help_command.rb index 90d37217fc..8e5b4d68d3 100644 --- a/railties/lib/rails/commands/help/help_command.rb +++ b/railties/lib/rails/commands/help/help_command.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Command class HelpCommand < Base # :nodoc: diff --git a/railties/lib/rails/commands/new/new_command.rb b/railties/lib/rails/commands/new/new_command.rb index 74d1fa5021..d73d64d899 100644 --- a/railties/lib/rails/commands/new/new_command.rb +++ b/railties/lib/rails/commands/new/new_command.rb @@ -1,8 +1,12 @@ +# frozen_string_literal: true + module Rails module Command class NewCommand < Base # :nodoc: - def help - Rails::Command.invoke :application, [ "--help" ] + no_commands do + def help + Rails::Command.invoke :application, [ "--help" ] + end end def perform(*) diff --git a/railties/lib/rails/commands/plugin/plugin_command.rb b/railties/lib/rails/commands/plugin/plugin_command.rb index b40ab006af..2b192abf9b 100644 --- a/railties/lib/rails/commands/plugin/plugin_command.rb +++ b/railties/lib/rails/commands/plugin/plugin_command.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Command class PluginCommand < Base # :nodoc: diff --git a/railties/lib/rails/commands/rake/rake_command.rb b/railties/lib/rails/commands/rake/rake_command.rb index 075b1fd23d..535df0c430 100644 --- a/railties/lib/rails/commands/rake/rake_command.rb +++ b/railties/lib/rails/commands/rake/rake_command.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Command class RakeCommand < Base # :nodoc: diff --git a/railties/lib/rails/commands/runner/USAGE b/railties/lib/rails/commands/runner/USAGE index b2a6e8493d..24b60037f0 100644 --- a/railties/lib/rails/commands/runner/USAGE +++ b/railties/lib/rails/commands/runner/USAGE @@ -8,6 +8,9 @@ Run the Ruby file located at `path/to/filename.rb` after loading the app: <%= executable %> path/to/filename.rb +Run the Ruby script read from stdin after loading the app: + <%= executable %> - + <% unless Gem.win_platform? %> You can also use the runner command as a shebang line for your executables: diff --git a/railties/lib/rails/commands/runner/runner_command.rb b/railties/lib/rails/commands/runner/runner_command.rb index 4989a7837d..30fbf04982 100644 --- a/railties/lib/rails/commands/runner/runner_command.rb +++ b/railties/lib/rails/commands/runner/runner_command.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Command class RunnerCommand < Base # :nodoc: @@ -5,16 +7,18 @@ module Rails default: Rails::Command.environment.dup, desc: "The environment for the runner to operate under (test/development/production)" - def help - super - puts self.class.desc + no_commands do + def help + super + puts self.class.desc + end end def self.banner(*) - "#{super} [<'Some.ruby(code)'> | <filename.rb>]" + "#{super} [<'Some.ruby(code)'> | <filename.rb> | -]" end - def perform(code_or_file = nil, *file_argv) + def perform(code_or_file = nil, *command_argv) unless code_or_file help exit 1 @@ -25,13 +29,16 @@ module Rails require_application_and_environment! Rails.application.load_runner - if File.exist?(code_or_file) + ARGV.replace(command_argv) + + if code_or_file == "-" + eval($stdin.read, TOPLEVEL_BINDING, "stdin") + elsif File.exist?(code_or_file) $0 = code_or_file - ARGV.replace(file_argv) Kernel.load code_or_file else begin - eval(code_or_file, binding, __FILE__, __LINE__) + eval(code_or_file, TOPLEVEL_BINDING, __FILE__, __LINE__) rescue SyntaxError, NameError => error $stderr.puts "Please specify a valid ruby command or the path of a script to run." $stderr.puts "Run '#{self.class.executable} -h' for help." diff --git a/railties/lib/rails/commands/secrets/USAGE b/railties/lib/rails/commands/secrets/USAGE new file mode 100644 index 0000000000..96e322fe91 --- /dev/null +++ b/railties/lib/rails/commands/secrets/USAGE @@ -0,0 +1,60 @@ +=== Storing Encrypted Secrets in Source Control + +The Rails `secrets` commands helps encrypting secrets to slim a production +environment's `ENV` hash. It's also useful for atomic deploys: no need to +coordinate key changes to get everything working as the keys are shipped +with the code. + +=== Setup + +Run `bin/rails secrets:setup` to opt in and generate the `config/secrets.yml.key` +and `config/secrets.yml.enc` files. + +The latter contains all the keys to be encrypted while the former holds the +encryption key. + +Don't lose the key! Put it in a password manager your team can access. +Should you lose it no one, including you, will be able to access any encrypted +secrets. +Don't commit the key! Add `config/secrets.yml.key` to your source control's +ignore file. If you use Git, Rails handles this for you. + +Rails also looks for the key in `ENV["RAILS_MASTER_KEY"]` if that's easier to +manage. + +You could prepend that to your server's start command like this: + + RAILS_MASTER_KEY="im-the-master-now-hahaha" server.start + + +The `config/secrets.yml.enc` has much the same format as `config/secrets.yml`: + + production: + secret_key_base: so-secret-very-hidden-wow + payment_processing_gateway_key: much-safe-very-gaedwey-wow + +But that's where the similarities between `secrets.yml` and `secrets.yml.enc` +end, e.g. no keys from `secrets.yml` will be moved to `secrets.yml.enc` and +be encrypted. + +A `shared:` top level key is also supported such that any keys there is merged +into the other environments. + +Additionally, Rails won't read encrypted secrets out of the box even if you have +the key. Add this: + + config.read_encrypted_secrets = true + +to the environment you'd like to read encrypted secrets. `bin/rails secrets:setup` +inserts this into the production environment by default. + +=== Editing Secrets + +After `bin/rails secrets:setup`, run `bin/rails secrets:edit`. + +That command opens a temporary file in `$EDITOR` with the decrypted contents of +`config/secrets.yml.enc` to edit the encrypted secrets. + +When the temporary file is next saved the contents are encrypted and written to +`config/secrets.yml.enc` while the file itself is destroyed to prevent secrets +from leaking. diff --git a/railties/lib/rails/commands/secrets/secrets_command.rb b/railties/lib/rails/commands/secrets/secrets_command.rb new file mode 100644 index 0000000000..a36ccf314c --- /dev/null +++ b/railties/lib/rails/commands/secrets/secrets_command.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require "active_support" +require "rails/secrets" + +module Rails + module Command + class SecretsCommand < Rails::Command::Base # :nodoc: + no_commands do + def help + say "Usage:\n #{self.class.banner}" + say "" + say self.class.desc + end + end + + def setup + deprecate_in_favor_of_credentials_and_exit + end + + def edit + if ENV["EDITOR"].to_s.empty? + say "No $EDITOR to open decrypted secrets in. Assign one like this:" + say "" + say %(EDITOR="mate --wait" bin/rails secrets:edit) + say "" + say "For editors that fork and exit immediately, it's important to pass a wait flag," + say "otherwise the secrets will be saved immediately with no chance to edit." + + return + end + + require_application_and_environment! + + Rails::Secrets.read_for_editing do |tmp_path| + system("#{ENV["EDITOR"]} #{tmp_path}") + end + + say "New secrets encrypted and saved." + rescue Interrupt + say "Aborted changing encrypted secrets: nothing saved." + rescue Rails::Secrets::MissingKeyError => error + say error.message + rescue Errno::ENOENT => error + if error.message =~ /secrets\.yml\.enc/ + deprecate_in_favor_of_credentials_and_exit + else + raise + end + end + + def show + say Rails::Secrets.read + end + + private + def deprecate_in_favor_of_credentials_and_exit + say "Encrypted secrets is deprecated in favor of credentials. Run:" + say "bin/rails credentials:help" + + exit 1 + end + end + end +end diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb index d58721f648..703ec59087 100644 --- a/railties/lib/rails/commands/server/server_command.rb +++ b/railties/lib/rails/commands/server/server_command.rb @@ -1,7 +1,11 @@ +# frozen_string_literal: true + require "fileutils" require "optparse" require "action_dispatch" require "rails" +require "active_support/deprecation" +require "active_support/core_ext/string/filters" require "rails/dev_caching" module Rails @@ -18,10 +22,15 @@ module Rails set_environment end - # TODO: this is no longer required but we keep it for the moment to support older config.ru files. def app @app ||= begin app = super + if app.is_a?(Class) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Use `Rails::Application` subclass to start the server is deprecated and will be removed in Rails 6.0. + Please change `run #{app}` to `run Rails.application` in config.ru. + MSG + end app.respond_to?(:to_app) ? app.to_app : app end end @@ -64,9 +73,9 @@ module Rails end def print_boot_information - url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}" + url = "on #{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}" unless use_puma? puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" - puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}" + puts "=> Rails #{Rails.version} application starting in #{Rails.env} #{url}" puts "=> Run `rails server -h` for more startup options" end @@ -91,16 +100,22 @@ module Rails def restart_command "bin/rails server #{ARGV.join(' ')}" end + + def use_puma? + server.to_s == "Rack::Handler::Puma" + end end module Command class ServerCommand < Base # :nodoc: + DEFAULT_PORT = 3000 DEFAULT_PID_PATH = "tmp/pids/server.pid".freeze class_option :port, aliases: "-p", type: :numeric, - desc: "Runs Rails on the specified port.", banner: :port, default: 3000 - class_option :binding, aliases: "-b", type: :string, default: "localhost", - desc: "Binds Rails to the specified IP.", banner: :IP + desc: "Runs Rails on the specified port - defaults to 3000.", banner: :port + class_option :binding, aliases: "-b", type: :string, + desc: "Binds Rails to the specified IP - defaults to 'localhost' in development and '0.0.0.0' in other environments'.", + banner: :IP class_option :config, aliases: "-c", type: :string, default: "config.ru", desc: "Uses a custom rackup configuration.", banner: :file class_option :daemon, aliases: "-d", type: :boolean, default: false, @@ -111,6 +126,8 @@ module Rails desc: "Specifies the PID file." class_option "dev-caching", aliases: "-C", type: :boolean, default: nil, desc: "Specifies whether to perform caching in development." + class_option "restart", type: :boolean, default: nil, hide: true + class_option "early_hints", type: :boolean, default: nil, desc: "Enables HTTP/2 early hints." def initialize(args = [], local_options = {}, config = {}) @original_options = local_options @@ -121,6 +138,7 @@ module Rails def perform set_application_directory! + prepare_restart Rails::Server.new(server_options).tap do |server| # Require application after server sets environment to propagate # the --environment option. @@ -133,28 +151,74 @@ module Rails no_commands do def server_options { - server: @server, - log_stdout: @log_stdout, - Port: port, - Host: host, - DoNotReverseLookup: true, - config: options[:config], - environment: environment, - daemonize: options[:daemon], - pid: pid, - caching: options["dev-caching"], - restart_cmd: restart_command + user_supplied_options: user_supplied_options, + server: @server, + log_stdout: @log_stdout, + Port: port, + Host: host, + DoNotReverseLookup: true, + config: options[:config], + environment: environment, + daemonize: options[:daemon], + pid: pid, + caching: options["dev-caching"], + restart_cmd: restart_command, + early_hints: early_hints } end end private + def user_supplied_options + @user_supplied_options ||= begin + # Convert incoming options array to a hash of flags + # ["-p3001", "-C", "--binding", "127.0.0.1"] # => {"-p"=>true, "-C"=>true, "--binding"=>true} + user_flag = {} + @original_options.each do |command| + if command.to_s.start_with?("--") + option = command.split("=")[0] + user_flag[option] = true + elsif command =~ /\A(-.)/ + user_flag[Regexp.last_match[0]] = true + end + end + + # Collect all options that the user has explicitly defined so we can + # differentiate them from defaults + user_supplied_options = [] + self.class.class_options.select do |key, option| + if option.aliases.any? { |name| user_flag[name] } || user_flag["--#{option.name}"] + name = option.name.to_sym + case name + when :port + name = :Port + when :binding + name = :Host + when :"dev-caching" + name = :caching + when :daemonize + name = :daemon + end + user_supplied_options << name + end + end + user_supplied_options << :Host if ENV["HOST"] + user_supplied_options << :Port if ENV["PORT"] + user_supplied_options.uniq + end + end + def port - ENV.fetch("PORT", options[:port]).to_i + options[:port] || ENV.fetch("PORT", DEFAULT_PORT).to_i end def host - ENV.fetch("HOST", options[:binding]) + if options[:binding] + options[:binding] + else + default_host = environment == "development" ? "localhost" : "0.0.0.0" + ENV.fetch("HOST", default_host) + end end def environment @@ -162,7 +226,11 @@ module Rails end def restart_command - "bin/rails server #{@server} #{@original_options.join(" ")}" + "bin/rails server #{@server} #{@original_options.join(" ")} --restart" + end + + def early_hints + options[:early_hints] end def pid @@ -172,6 +240,10 @@ module Rails def self.banner(*) "rails server [puma, thin etc] [options]" end + + def prepare_restart + FileUtils.rm_f(options[:pid]) if options[:restart] + end end end end diff --git a/railties/lib/rails/commands/test/test_command.rb b/railties/lib/rails/commands/test/test_command.rb index 7bf8f61137..00ea9ac4a6 100644 --- a/railties/lib/rails/commands/test/test_command.rb +++ b/railties/lib/rails/commands/test/test_command.rb @@ -1,19 +1,36 @@ +# frozen_string_literal: true + require "rails/command" -require "rails/test_unit/minitest_plugin" +require "rails/test_unit/runner" +require "rails/test_unit/reporter" module Rails module Command class TestCommand < Base # :nodoc: - def help - perform # Hand over help printing to minitest. + no_commands do + def help + say "Usage: #{Rails::TestUnitReporter.executable} [options] [files or directories]" + say "" + say "You can run a single test by appending a line number to a filename:" + say "" + say " #{Rails::TestUnitReporter.executable} test/models/user_test.rb:27" + say "" + say "You can run multiple files and directories at the same time:" + say "" + say " #{Rails::TestUnitReporter.executable} test/controllers test/integration/login_test.rb" + say "" + say "By default test failures and errors are reported inline during a run." + say "" + + Minitest.run(%w(--help)) + end end def perform(*) - $LOAD_PATH << Rails::Command.root.join("test") - - Minitest.run_via[:rails] = true + $LOAD_PATH << Rails::Command.root.join("test").to_s - require "active_support/testing/autorun" + Rails::TestUnit::Runner.parse_options(ARGV) + Rails::TestUnit::Runner.run(ARGV) end end end diff --git a/railties/lib/rails/commands/version/version_command.rb b/railties/lib/rails/commands/version/version_command.rb index ac745594ee..3e2112b6d4 100644 --- a/railties/lib/rails/commands/version/version_command.rb +++ b/railties/lib/rails/commands/version/version_command.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Command class VersionCommand < Base # :nodoc: diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index fc7d4909f6..d3a54d9364 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/ordered_options" require "active_support/core_ext/object" require "rails/paths" diff --git a/railties/lib/rails/console/app.rb b/railties/lib/rails/console/app.rb index affadc8e09..c37583ce9a 100644 --- a/railties/lib/rails/console/app.rb +++ b/railties/lib/rails/console/app.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/all" require "action_controller" diff --git a/railties/lib/rails/console/helpers.rb b/railties/lib/rails/console/helpers.rb index a33f71dc5b..39fbc55606 100644 --- a/railties/lib/rails/console/helpers.rb +++ b/railties/lib/rails/console/helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module ConsoleMethods # Gets the helper methods available to the controller. diff --git a/railties/lib/rails/dev_caching.rb b/railties/lib/rails/dev_caching.rb index f69275a34a..ff629b2527 100644 --- a/railties/lib/rails/dev_caching.rb +++ b/railties/lib/rails/dev_caching.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "fileutils" module Rails @@ -17,7 +19,6 @@ module Rails end FileUtils.touch "tmp/restart.txt" - FileUtils.rm_f("tmp/pids/server.pid") end def enable_by_argument(caching) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 13af6051ce..6a13a84108 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/railtie" require "rails/engine/railties" require "active_support/core_ext/module/delegation" @@ -40,7 +42,7 @@ module Rails # # class MyEngine < Rails::Engine # # Add a load path for this specific Engine - # config.autoload_paths << File.expand_path("../lib/some/path", __FILE__) + # config.autoload_paths << File.expand_path("lib/some/path", __dir__) # # initializer "my_engine.add_middleware" do |app| # app.middleware.use MyEngine::Middleware @@ -661,7 +663,6 @@ module Rails end def self.find_root_with_flag(flag, root_path, default = nil) #:nodoc: - while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/#{flag}") parent = File.dirname(root_path) root_path = parent != root_path && parent diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb index b9ef63243a..05218640c6 100644 --- a/railties/lib/rails/engine/commands.rb +++ b/railties/lib/rails/engine/commands.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + unless defined?(APP_PATH) if File.exist?(File.expand_path("test/dummy/config/application.rb", ENGINE_ROOT)) APP_PATH = File.expand_path("test/dummy/config/application", ENGINE_ROOT) diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 0c40173c38..6bf0406b21 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/railtie/configuration" module Rails diff --git a/railties/lib/rails/engine/railties.rb b/railties/lib/rails/engine/railties.rb index 9969a1475d..052b74c880 100644 --- a/railties/lib/rails/engine/railties.rb +++ b/railties/lib/rails/engine/railties.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails class Engine < Railtie class Railties diff --git a/railties/lib/rails/engine/updater.rb b/railties/lib/rails/engine/updater.rb new file mode 100644 index 0000000000..be7a47124a --- /dev/null +++ b/railties/lib/rails/engine/updater.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require "rails/generators" +require "rails/generators/rails/plugin/plugin_generator" + +module Rails + class Engine + class Updater + class << self + def generator + @generator ||= Rails::Generators::PluginGenerator.new ["plugin"], + { engine: true }, { destination_root: ENGINE_ROOT } + end + + def run(action) + generator.send(action) + end + end + end + end +end diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb index 9c49e0655a..2cc861a1bd 100644 --- a/railties/lib/rails/gem_version.rb +++ b/railties/lib/rails/gem_version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails # Returns the version of the currently loaded Rails as a <tt>Gem::Version</tt> def self.gem_version @@ -6,9 +8,9 @@ module Rails module VERSION MAJOR = 5 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "alpha" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 99bda728ee..6c9c109f17 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -1,4 +1,6 @@ -activesupport_path = File.expand_path("../../../../activesupport/lib", __FILE__) +# frozen_string_literal: true + +activesupport_path = File.expand_path("../../../activesupport/lib", __dir__) $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) require "thor/group" @@ -10,6 +12,7 @@ require "active_support/core_ext/kernel/singleton_class" require "active_support/core_ext/array/extract_options" require "active_support/core_ext/hash/deep_merge" require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/string/indent" require "active_support/core_ext/string/inflections" module Rails @@ -62,7 +65,8 @@ module Rails stylesheets: true, stylesheet_engine: :css, scaffold_stylesheet: true, - test_framework: false, + system_tests: nil, + test_framework: nil, template_engine: :erb } } @@ -151,6 +155,7 @@ module Rails "#{test}:controller", "#{test}:helper", "#{test}:integration", + "#{test}:system", "#{test}:mailer", "#{test}:model", "#{test}:scaffold", @@ -212,6 +217,8 @@ module Rails rails.map! { |n| n.sub(/^rails:/, "") } rails.delete("app") rails.delete("plugin") + rails.delete("encrypted_secrets") + rails.delete("credentials") hidden_namespaces.each { |n| groups.delete(n.to_s) } @@ -267,8 +274,9 @@ module Rails else options = sorted_groups.flat_map(&:last) suggestions = options.sort_by { |suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3) - msg = "Could not find generator '#{namespace}'. " - msg << "Maybe you meant #{ suggestions.map { |s| "'#{s}'" }.to_sentence(last_word_connector: " or ", locale: :en) }\n" + suggestions.map! { |s| "'#{s}'" } + msg = "Could not find generator '#{namespace}'. ".dup + msg << "Maybe you meant #{ suggestions[0...-1].join(', ')} or #{suggestions[-1]}\n" msg << "Run `rails generate --help` for more options." puts msg end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 0bd0615b7e..3362bf629a 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Generators module Actions @@ -11,17 +13,22 @@ module Rails # # gem "rspec", group: :test # gem "technoweenie-restful-authentication", lib: "restful-authentication", source: "http://gems.github.com/" - # gem "rails", "3.0", git: "git://github.com/rails/rails" + # gem "rails", "3.0", git: "https://github.com/rails/rails" + # gem "RedCloth", ">= 4.1.0", "< 4.2.0" def gem(*args) options = args.extract_options! - name, version = args + name, *versions = args # Set the message to be shown in logs. Uses the git repo if one is given, # otherwise use name (version). parts, message = [ quote(name) ], name.dup - if version ||= options.delete(:version) - parts << quote(version) - message << " (#{version})" + + if versions = versions.any? ? versions : options.delete(:version) + _versions = Array(versions) + _versions.each do |version| + parts << quote(version) + end + message << " (#{_versions.join(", ")})" end message = options[:git] if options[:git] @@ -97,16 +104,16 @@ module Rails # "config.action_controller.asset_host = 'localhost:3000'" # end def environment(data = nil, options = {}) - sentinel = /class [a-z_:]+ < Rails::Application/i - env_file_sentinel = /Rails\.application\.configure do/ - data = yield if !data && block_given? + sentinel = "class Application < Rails::Application\n" + env_file_sentinel = "Rails.application.configure do\n" + data ||= yield if block_given? in_root do if options[:env].nil? - inject_into_file "config/application.rb", "\n #{data}", after: sentinel, verbose: false + inject_into_file "config/application.rb", optimize_indentation(data, 4), after: sentinel, verbose: false else Array(options[:env]).each do |env| - inject_into_file "config/environments/#{env}.rb", "\n #{data}", after: env_file_sentinel, verbose: false + inject_into_file "config/environments/#{env}.rb", optimize_indentation(data, 2), after: env_file_sentinel, verbose: false end end end @@ -137,12 +144,13 @@ module Rails # end # # vendor("foreign.rb", "# Foreign code is fun") - def vendor(filename, data = nil, &block) + def vendor(filename, data = nil) log :vendor, filename - create_file("vendor/#{filename}", data, verbose: false, &block) + data ||= yield if block_given? + create_file("vendor/#{filename}", optimize_indentation(data), verbose: false) end - # Create a new file in the lib/ directory. Code can be specified + # Create a new file in the <tt>lib/</tt> directory. Code can be specified # in a block or a data string can be given. # # lib("crypto.rb") do @@ -150,9 +158,10 @@ module Rails # end # # lib("foreign.rb", "# Foreign code is fun") - def lib(filename, data = nil, &block) + def lib(filename, data = nil) log :lib, filename - create_file("lib/#{filename}", data, verbose: false, &block) + data ||= yield if block_given? + create_file("lib/#{filename}", optimize_indentation(data), verbose: false) end # Create a new +Rakefile+ with the provided code (either in a block or a string). @@ -170,9 +179,10 @@ module Rails # end # # rakefile('seed.rake', 'puts "Planting seeds"') - def rakefile(filename, data = nil, &block) + def rakefile(filename, data = nil) log :rakefile, filename - create_file("lib/tasks/#{filename}", data, verbose: false, &block) + data ||= yield if block_given? + create_file("lib/tasks/#{filename}", optimize_indentation(data), verbose: false) end # Create a new initializer with the provided code (either in a block or a string). @@ -188,9 +198,10 @@ module Rails # end # # initializer("api.rb", "API_KEY = '123456'") - def initializer(filename, data = nil, &block) + def initializer(filename, data = nil) log :initializer, filename - create_file("config/initializers/#{filename}", data, verbose: false, &block) + data ||= yield if block_given? + create_file("config/initializers/#{filename}", optimize_indentation(data), verbose: false) end # Generate something using a generator from Rails or a plugin. @@ -210,15 +221,17 @@ module Rails # rake("db:migrate") # rake("db:migrate", env: "production") # rake("gems:install", sudo: true) + # rake("gems:install", capture: true) def rake(command, options = {}) execute_command :rake, command, options end # Runs the supplied rake task (invoked with 'rails ...') # - # rails("db:migrate") - # rails("db:migrate", env: "production") - # rails("gems:install", sudo: true) + # rails_command("db:migrate") + # rails_command("db:migrate", env: "production") + # rails_command("gems:install", sudo: true) + # rails_command("gems:install", capture: true) def rails_command(command, options = {}) execute_command :rails, command, options end @@ -227,6 +240,7 @@ module Rails # # capify! def capify! + ActiveSupport::Deprecation.warn("`capify!` is deprecated and will be removed in the next version of Rails.") log :capify, "" in_root { run("#{extify(:capify)} .", verbose: false) } end @@ -239,7 +253,7 @@ module Rails sentinel = /\.routes\.draw do\s*\n/m in_root do - inject_into_file "config/routes.rb", " #{routing_code}\n", after: sentinel, verbose: false, force: false + inject_into_file "config/routes.rb", optimize_indentation(routing_code, 2), after: sentinel, verbose: false, force: false end end @@ -280,7 +294,11 @@ module Rails log executor, command env = options[:env] || ENV["RAILS_ENV"] || "development" sudo = options[:sudo] && !Gem.win_platform? ? "sudo " : "" - in_root { run("#{sudo}#{extify(executor)} #{command} RAILS_ENV=#{env}", verbose: false) } + config = { verbose: false } + + config.merge!(capture: options[:capture]) if options[:capture] + + in_root { run("#{sudo}#{extify(executor)} #{command} RAILS_ENV=#{env}", config) } end # Add an extension to the given name based on the platform. @@ -303,6 +321,17 @@ module Rails "'#{value}'" end end + + # Returns optimized string with indentation + def optimize_indentation(value, amount = 0) # :doc: + return "#{value}\n" unless value.is_a?(String) + + if value.lines.size > 1 + value.strip_heredoc.indent(amount) + else + "#{value.strip.indent(amount)}\n" + end + end end end end diff --git a/railties/lib/rails/generators/actions/create_migration.rb b/railties/lib/rails/generators/actions/create_migration.rb index d06609e91e..05bc242447 100644 --- a/railties/lib/rails/generators/actions/create_migration.rb +++ b/railties/lib/rails/generators/actions/create_migration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "fileutils" require "thor/actions" diff --git a/railties/lib/rails/generators/active_model.rb b/railties/lib/rails/generators/active_model.rb index 2679d06fe4..8df8eb2438 100644 --- a/railties/lib/rails/generators/active_model.rb +++ b/railties/lib/rails/generators/active_model.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Generators # ActiveModel is a class to be implemented by each ORM to allow Rails to diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index ea88afe9f4..400f954dcd 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "fileutils" require "digest/md5" require "active_support/core_ext/string/strip" @@ -24,78 +26,81 @@ module Rails end def self.add_shared_options_for(name) - class_option :template, type: :string, aliases: "-m", - desc: "Path to some #{name} template (can be a filesystem path or URL)" + class_option :template, type: :string, aliases: "-m", + desc: "Path to some #{name} template (can be a filesystem path or URL)" + + class_option :database, type: :string, aliases: "-d", default: "sqlite3", + desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})" - class_option :database, type: :string, aliases: "-d", default: "sqlite3", - desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})" + class_option :skip_yarn, type: :boolean, default: false, + desc: "Don't use Yarn for managing JavaScript dependencies" - class_option :javascript, type: :string, aliases: "-j", - desc: "Preconfigure for selected JavaScript library" + class_option :skip_gemfile, type: :boolean, default: false, + desc: "Don't create a Gemfile" - class_option :webpack, type: :string, default: nil, - desc: "Preconfigure for app-like JavaScript with Webpack" + class_option :skip_git, type: :boolean, aliases: "-G", default: false, + desc: "Skip .gitignore file" - class_option :skip_yarn, type: :boolean, default: false, - desc: "Don't use Yarn for managing JavaScript dependencies" + class_option :skip_keeps, type: :boolean, default: false, + desc: "Skip source control .keep files" - class_option :skip_gemfile, type: :boolean, default: false, - desc: "Don't create a Gemfile" + class_option :skip_action_mailer, type: :boolean, aliases: "-M", + default: false, + desc: "Skip Action Mailer files" - class_option :skip_git, type: :boolean, aliases: "-G", default: false, - desc: "Skip .gitignore file" + class_option :skip_active_record, type: :boolean, aliases: "-O", default: false, + desc: "Skip Active Record files" - class_option :skip_keeps, type: :boolean, default: false, - desc: "Skip source control .keep files" + class_option :skip_active_storage, type: :boolean, default: false, + desc: "Skip Active Storage files" - class_option :skip_action_mailer, type: :boolean, aliases: "-M", - default: false, - desc: "Skip Action Mailer files" + class_option :skip_puma, type: :boolean, aliases: "-P", default: false, + desc: "Skip Puma related files" - class_option :skip_active_record, type: :boolean, aliases: "-O", default: false, - desc: "Skip Active Record files" + class_option :skip_action_cable, type: :boolean, aliases: "-C", default: false, + desc: "Skip Action Cable files" - class_option :skip_puma, type: :boolean, aliases: "-P", default: false, - desc: "Skip Puma related files" + class_option :skip_sprockets, type: :boolean, aliases: "-S", default: false, + desc: "Skip Sprockets files" - class_option :skip_action_cable, type: :boolean, aliases: "-C", default: false, - desc: "Skip Action Cable files" + class_option :skip_spring, type: :boolean, default: false, + desc: "Don't install Spring application preloader" - class_option :skip_sprockets, type: :boolean, aliases: "-S", default: false, - desc: "Skip Sprockets files" + class_option :skip_listen, type: :boolean, default: false, + desc: "Don't generate configuration that depends on the listen gem" - class_option :skip_spring, type: :boolean, default: false, - desc: "Don't install Spring application preloader" + class_option :skip_coffee, type: :boolean, default: false, + desc: "Don't use CoffeeScript" - class_option :skip_listen, type: :boolean, default: false, - desc: "Don't generate configuration that depends on the listen gem" + class_option :skip_javascript, type: :boolean, aliases: "-J", default: false, + desc: "Skip JavaScript files" - class_option :skip_coffee, type: :boolean, default: false, - desc: "Don't use CoffeeScript" + class_option :skip_turbolinks, type: :boolean, default: false, + desc: "Skip turbolinks gem" - class_option :skip_javascript, type: :boolean, aliases: "-J", default: false, - desc: "Skip JavaScript files" + class_option :skip_test, type: :boolean, aliases: "-T", default: false, + desc: "Skip test files" - class_option :skip_turbolinks, type: :boolean, default: false, - desc: "Skip turbolinks gem" + class_option :skip_system_test, type: :boolean, default: false, + desc: "Skip system test files" - class_option :skip_test, type: :boolean, aliases: "-T", default: false, - desc: "Skip test files" + class_option :skip_bootsnap, type: :boolean, default: false, + desc: "Skip bootsnap gem" - class_option :dev, type: :boolean, default: false, - desc: "Setup the #{name} with Gemfile pointing to your Rails checkout" + class_option :dev, type: :boolean, default: false, + desc: "Setup the #{name} with Gemfile pointing to your Rails checkout" - class_option :edge, type: :boolean, default: false, - desc: "Setup the #{name} with Gemfile pointing to Rails repository" + class_option :edge, type: :boolean, default: false, + desc: "Setup the #{name} with Gemfile pointing to Rails repository" - class_option :rc, type: :string, default: nil, - desc: "Path to file containing extra configuration options for rails command" + class_option :rc, type: :string, default: nil, + desc: "Path to file containing extra configuration options for rails command" - class_option :no_rc, type: :boolean, default: false, - desc: "Skip loading of extra configuration options from .railsrc file" + class_option :no_rc, type: :boolean, default: false, + desc: "Skip loading of extra configuration options from .railsrc file" - class_option :help, type: :boolean, aliases: "-h", group: :rails, - desc: "Show this help message and quit" + class_option :help, type: :boolean, aliases: "-h", group: :rails, + desc: "Show this help message and quit" end def initialize(*args) @@ -190,15 +195,33 @@ module Rails def webserver_gemfile_entry # :doc: return [] if options[:skip_puma] comment = "Use Puma as the app server" - GemfileEntry.new("puma", "~> 3.0", comment) + GemfileEntry.new("puma", "~> 3.11", comment) end def include_all_railties? # :doc: - options.values_at(:skip_active_record, :skip_action_mailer, :skip_test, :skip_sprockets, :skip_action_cable).none? + [ + options.values_at( + :skip_active_record, + :skip_action_mailer, + :skip_test, + :skip_sprockets, + :skip_action_cable + ), + skip_active_storage? + ].flatten.none? end def comment_if(value) # :doc: - options[value] ? "# " : "" + question = "#{value}?" + + comment = + if respond_to?(question, true) + send(question) + else + options[value] + end + + comment ? "# " : "" end def keeps? # :doc: @@ -209,6 +232,10 @@ module Rails !options[:skip_active_record] && options[:database] == "sqlite3" end + def skip_active_storage? # :doc: + options[:skip_active_storage] || options[:skip_active_record] + end + class GemfileEntry < Struct.new(:name, :version, :comment, :options, :commented_out) def initialize(name, version, comment, options = {}, commented_out = false) super @@ -242,17 +269,14 @@ module Rails end def rails_gemfile_entry - dev_edge_common = [ - GemfileEntry.github("arel", "rails/arel") - ] if options.dev? [ GemfileEntry.path("rails", Rails::Generators::RAILS_DEV_PATH) - ] + dev_edge_common + ] elsif options.edge? [ GemfileEntry.github("rails", "rails/rails") - ] + dev_edge_common + ] else [GemfileEntry.version("rails", rails_version_specifier, @@ -261,14 +285,13 @@ module Rails end def rails_version_specifier(gem_version = Rails.gem_version) - if gem_version.prerelease? - next_series = gem_version - next_series = next_series.bump while next_series.segments.size > 2 - - [">= #{gem_version}", "< #{next_series}"] - elsif gem_version.segments.size == 3 + if gem_version.segments.size == 3 || gem_version.release.segments.size == 3 + # ~> 1.2.3 + # ~> 1.2.3.pre4 "~> #{gem_version}" else + # ~> 1.2.3, >= 1.2.3.4 + # ~> 1.2.3, >= 1.2.3.4.pre5 patch = gem_version.segments[0, 3].join(".") ["~> #{patch}", ">= #{gem_version}"] end @@ -277,9 +300,9 @@ module Rails def gem_for_database # %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver jdbcmysql jdbcsqlite3 jdbcpostgresql ) case options[:database] - when "mysql" then ["mysql2", [">= 0.3.18", "< 0.5"]] + when "mysql" then ["mysql2", ["~> 0.4.4"]] when "postgresql" then ["pg", ["~> 0.18"]] - when "oracle" then ["ruby-oci8", nil] + when "oracle" then ["activerecord-oracle_enhanced-adapter", nil] when "frontbase" then ["ruby-frontbase", nil] when "sqlserver" then ["activerecord-sqlserver-adapter", nil] when "jdbcmysql" then ["activerecord-jdbcmysql-adapter", nil] @@ -295,7 +318,6 @@ module Rails case options[:database] when "postgresql" then options[:database].replace "jdbcpostgresql" when "mysql" then options[:database].replace "jdbcmysql" - when "oracle" then options[:database].replace "jdbc" when "sqlite3" then options[:database].replace "jdbcsqlite3" end end @@ -305,7 +327,7 @@ module Rails return [] if options[:skip_sprockets] gems = [] - gems << GemfileEntry.github("sass-rails", "rails/sass-rails", nil, + gems << GemfileEntry.version("sass-rails", "~> 5.0", "Use SCSS for stylesheets") if !options[:skip_javascript] @@ -321,7 +343,7 @@ module Rails return [] unless options[:webpack] comment = "Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker" - GemfileEntry.github "webpacker", "rails/webpacker", nil, comment + GemfileEntry.new "webpacker", nil, comment end def jbuilder_gemfile_entry @@ -340,11 +362,6 @@ module Rails gems = [javascript_runtime_gemfile_entry] gems << coffee_gemfile_entry unless options[:skip_coffee] - if options[:javascript] - gems << GemfileEntry.version("#{options[:javascript]}-rails", nil, - "Use #{options[:javascript]} as the JavaScript library") - end - unless options[:skip_turbolinks] gems << GemfileEntry.version("turbolinks", "~> 5", "Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks") @@ -358,8 +375,10 @@ module Rails comment = "See https://github.com/rails/execjs#readme for more supported runtimes" if defined?(JRUBY_VERSION) GemfileEntry.version "therubyrhino", nil, comment + elsif RUBY_PLATFORM =~ /mingw|mswin/ + GemfileEntry.version "duktape", nil, comment else - GemfileEntry.new "therubyracer", nil, comment, { platforms: :ruby }, true + GemfileEntry.new "mini_racer", nil, comment, { platforms: :ruby }, true end end @@ -375,7 +394,7 @@ module Rails return [] if options[:skip_action_cable] comment = "Use Redis adapter to run Action Cable in production" gems = [] - gems << GemfileEntry.new("redis", "~> 3.0", comment, {}, true) + gems << GemfileEntry.new("redis", "~> 4.0", comment, {}, true) gems end @@ -411,10 +430,18 @@ module Rails !options[:skip_spring] && !options.dev? && Process.respond_to?(:fork) && !RUBY_PLATFORM.include?("cygwin") end + def depends_on_system_test? + !(options[:skip_system_test] || options[:skip_test] || options[:api]) + end + def depend_on_listen? !options[:skip_listen] && os_supports_listen_out_of_the_box? end + def depend_on_bootsnap? + !options[:skip_bootsnap] && !options[:dev] + end + def os_supports_listen_out_of_the_box? RbConfig::CONFIG["host_os"] =~ /darwin|linux/ end @@ -436,6 +463,16 @@ module Rails end end + def run_active_storage + unless skip_active_storage? + if bundle_install? + rails_command "active_storage:install", capture: options[:quiet] + else + log("Active Storage installation was skipped. Please run `bin/rails active_storage:install` to install Active Storage files.") + end + end + end + def empty_directory_with_keep_file(destination, config = {}) empty_directory(destination, config) keep_file(destination) diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index a650c52626..5523a3f659 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "thor/group" rescue LoadError @@ -16,6 +18,9 @@ module Rails include Thor::Actions include Rails::Generators::Actions + class_option :skip_namespace, type: :boolean, default: false, + desc: "Skip namespace (affects only isolated applications)" + add_runtime_options! strict_args_position! @@ -215,7 +220,7 @@ module Rails # Returns the base root for a common set of generators. This is used to dynamically # guess the default source root. def self.base_root - File.dirname(__FILE__) + __dir__ end # Cache source root and add lib/generators/base/generator/templates to @@ -271,6 +276,40 @@ module Rails end end + # Wrap block with namespace of current application + # if namespace exists and is not skipped + def module_namespacing(&block) # :doc: + content = capture(&block) + content = wrap_with_namespace(content) if namespaced? + concat(content) + end + + def indent(content, multiplier = 2) # :doc: + spaces = " " * multiplier + content.each_line.map { |line| line.blank? ? line : "#{spaces}#{line}" }.join + end + + def wrap_with_namespace(content) # :doc: + content = indent(content).chomp + "module #{namespace.name}\n#{content}\nend\n" + end + + def namespace # :doc: + Rails::Generators.namespace + end + + def namespaced? # :doc: + !options[:skip_namespace] && namespace + end + + def namespace_dirs + @namespace_dirs ||= namespace.name.split("::").map(&:underscore) + end + + def namespaced_path # :doc: + @namespaced_path ||= namespace_dirs.join("/") + end + # Use Rails default banner. def self.banner # :doc: "rails generate #{namespace.sub(/^rails:/, '')} #{arguments.map(&:usage).join(' ')} [options]".gsub(/\s+/, " ") diff --git a/railties/lib/rails/generators/css/assets/assets_generator.rb b/railties/lib/rails/generators/css/assets/assets_generator.rb index 20baf31a34..f657d1e50f 100644 --- a/railties/lib/rails/generators/css/assets/assets_generator.rb +++ b/railties/lib/rails/generators/css/assets/assets_generator.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require "rails/generators/named_base" module Css # :nodoc: module Generators # :nodoc: class AssetsGenerator < Rails::Generators::NamedBase # :nodoc: - source_root File.expand_path("../templates", __FILE__) + source_root File.expand_path("templates", __dir__) def copy_stylesheet copy_file "stylesheet.css", File.join("app/assets/stylesheets", class_path, "#{file_name}.css") diff --git a/railties/lib/rails/generators/css/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/css/scaffold/scaffold_generator.rb index cf534030f9..89c560f382 100644 --- a/railties/lib/rails/generators/css/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/css/scaffold/scaffold_generator.rb @@ -1,15 +1,17 @@ +# frozen_string_literal: true + require "rails/generators/named_base" module Css # :nodoc: module Generators # :nodoc: class ScaffoldGenerator < Rails::Generators::NamedBase # :nodoc: + source_root Rails::Generators::ScaffoldGenerator.source_root + # In order to allow the Sass generators to pick up the default Rails CSS and # transform it, we leave it in a standard location for the CSS stylesheet # generators to handle. For the simple, default case, just copy it over. def copy_stylesheet - dir = Rails::Generators::ScaffoldGenerator.source_root - file = File.join(dir, "scaffold.css") - create_file "app/assets/stylesheets/scaffold.css", File.read(file) + copy_file "scaffold.css", "app/assets/stylesheets/scaffold.css" end end end diff --git a/railties/lib/rails/generators/erb.rb b/railties/lib/rails/generators/erb.rb index d5e326d6ee..ba20bcd32a 100644 --- a/railties/lib/rails/generators/erb.rb +++ b/railties/lib/rails/generators/erb.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/named_base" module Erb # :nodoc: @@ -17,8 +19,8 @@ module Erb # :nodoc: :erb end - def filename_with_extensions(name, format = self.format) - [name, format, handler].compact.join(".") + def filename_with_extensions(name, file_format = format) + [name, file_format, handler].compact.join(".") end end end diff --git a/railties/lib/rails/generators/erb/controller/controller_generator.rb b/railties/lib/rails/generators/erb/controller/controller_generator.rb index 36ecfea09b..8e13744b2a 100644 --- a/railties/lib/rails/generators/erb/controller/controller_generator.rb +++ b/railties/lib/rails/generators/erb/controller/controller_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/erb" module Erb # :nodoc: diff --git a/railties/lib/rails/generators/erb/controller/templates/view.html.erb b/railties/lib/rails/generators/erb/controller/templates/view.html.erb.tt index cd54d13d83..cd54d13d83 100644 --- a/railties/lib/rails/generators/erb/controller/templates/view.html.erb +++ b/railties/lib/rails/generators/erb/controller/templates/view.html.erb.tt diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb index 3f1d9932f6..e2ea66415f 100644 --- a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb +++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/erb" module Erb # :nodoc: diff --git a/railties/lib/rails/generators/erb/mailer/templates/view.html.erb b/railties/lib/rails/generators/erb/mailer/templates/view.html.erb.tt index b5045671b3..b5045671b3 100644 --- a/railties/lib/rails/generators/erb/mailer/templates/view.html.erb +++ b/railties/lib/rails/generators/erb/mailer/templates/view.html.erb.tt diff --git a/railties/lib/rails/generators/erb/mailer/templates/view.text.erb b/railties/lib/rails/generators/erb/mailer/templates/view.text.erb.tt index 342285df19..342285df19 100644 --- a/railties/lib/rails/generators/erb/mailer/templates/view.text.erb +++ b/railties/lib/rails/generators/erb/mailer/templates/view.text.erb.tt diff --git a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb index 0d77ef21da..2fc04e4094 100644 --- a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/erb" require "rails/generators/resource_helpers" diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt index 519b6c8603..518cb1121e 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt @@ -1,4 +1,4 @@ -<%%= form_for(<%= singular_table_name %>) do |f| %> +<%%= form_with(model: <%= model_resource_name %>, local: true) do |form| %> <%% if <%= singular_table_name %>.errors.any? %> <div id="error_explanation"> <h2><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2> @@ -14,21 +14,21 @@ <% attributes.each do |attribute| -%> <div class="field"> <% if attribute.password_digest? -%> - <%%= f.label :password %> - <%%= f.password_field :password %> + <%%= form.label :password %> + <%%= form.password_field :password %> </div> <div class="field"> - <%%= f.label :password_confirmation %> - <%%= f.password_field :password_confirmation %> + <%%= form.label :password_confirmation %> + <%%= form.password_field :password_confirmation %> <% else -%> - <%%= f.label :<%= attribute.column_name %> %> - <%%= f.<%= attribute.field_type %> :<%= attribute.column_name %> %> + <%%= form.label :<%= attribute.column_name %> %> + <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %> %> <% end -%> </div> <% end -%> <div class="actions"> - <%%= f.submit %> + <%%= form.submit %> </div> <%% end %> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb.tt index 81329473d9..81329473d9 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb.tt diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt index 5f4904fee1..e1ede7c713 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt @@ -18,9 +18,9 @@ <% attributes.reject(&:password_digest?).each do |attribute| -%> <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td> <% end -%> - <td><%%= link_to 'Show', <%= singular_table_name %> %></td> - <td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td> - <td><%%= link_to 'Destroy', <%= singular_table_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td> + <td><%%= link_to 'Show', <%= model_resource_name %> %></td> + <td><%%= link_to 'Edit', edit_<%= singular_route_name %>_path(<%= singular_table_name %>) %></td> + <td><%%= link_to 'Destroy', <%= model_resource_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <%% end %> </tbody> @@ -28,4 +28,4 @@ <br> -<%%= link_to 'New <%= singular_table_name.titleize %>', new_<%= singular_table_name %>_path %> +<%%= link_to 'New <%= singular_table_name.titleize %>', new_<%= singular_route_name %>_path %> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb.tt index 9b2b2f4875..9b2b2f4875 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb.tt diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt index 5e634153be..5e634153be 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index baed7bf1e3..2728459968 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/time" module Rails @@ -151,7 +153,7 @@ module Rails end def inject_options - "".tap { |s| options_for_migration.each { |k, v| s << ", #{k}: #{v.inspect}" } } + "".dup.tap { |s| options_for_migration.each { |k, v| s << ", #{k}: #{v.inspect}" } } end def inject_index_options diff --git a/railties/lib/rails/generators/js/assets/assets_generator.rb b/railties/lib/rails/generators/js/assets/assets_generator.rb index 64d706ec91..9d32c666dc 100644 --- a/railties/lib/rails/generators/js/assets/assets_generator.rb +++ b/railties/lib/rails/generators/js/assets/assets_generator.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require "rails/generators/named_base" module Js # :nodoc: module Generators # :nodoc: class AssetsGenerator < Rails::Generators::NamedBase # :nodoc: - source_root File.expand_path("../templates", __FILE__) + source_root File.expand_path("templates", __dir__) def copy_javascript copy_file "javascript.js", File.join("app/assets/javascripts", class_path, "#{file_name}.js") diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb index 82481169c3..1cbccfe461 100644 --- a/railties/lib/rails/generators/migration.rb +++ b/railties/lib/rails/generators/migration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/concern" require "rails/generators/actions/create_migration" diff --git a/railties/lib/rails/generators/model_helpers.rb b/railties/lib/rails/generators/model_helpers.rb index 6f87a18660..50078404b3 100644 --- a/railties/lib/rails/generators/model_helpers.rb +++ b/railties/lib/rails/generators/model_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/active_model" module Rails diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 6f1925928b..98fcc95964 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -1,4 +1,5 @@ -require "active_support/core_ext/module/introspection" +# frozen_string_literal: true + require "rails/generators/base" require "rails/generators/generated_attribute" @@ -6,8 +7,6 @@ module Rails module Generators class NamedBase < Base argument :name, type: :string - class_option :skip_namespace, type: :boolean, default: false, - desc: "Skip namespace (affects only isolated applications)" def initialize(args, *options) #:nodoc: @inside_template = nil @@ -45,24 +44,6 @@ module Rails file_name end - # Wrap block with namespace of current application - # if namespace exists and is not skipped - def module_namespacing(&block) # :doc: - content = capture(&block) - content = wrap_with_namespace(content) if namespaced? - concat(content) - end - - def indent(content, multiplier = 2) # :doc: - spaces = " " * multiplier - content.each_line.map { |line| line.blank? ? line : "#{spaces}#{line}" }.join - end - - def wrap_with_namespace(content) # :doc: - content = indent(content).chomp - "module #{namespace.name}\n#{content}\nend\n" - end - def inside_template # :doc: @inside_template = true yield @@ -74,14 +55,6 @@ module Rails @inside_template end - def namespace # :doc: - Rails::Generators.namespace - end - - def namespaced? # :doc: - !options[:skip_namespace] && namespace - end - def file_path # :doc: @file_path ||= (class_path + [file_name]).join("/") end @@ -95,11 +68,7 @@ module Rails end def namespaced_class_path # :doc: - @namespaced_class_path ||= [namespaced_path] + @class_path - end - - def namespaced_path # :doc: - @namespaced_path ||= namespace.name.split("::").first.underscore + @namespaced_class_path ||= namespace_dirs + @class_path end def class_name # :doc: @@ -130,11 +99,11 @@ module Rails end def index_helper # :doc: - uncountable? ? "#{plural_table_name}_index" : plural_table_name + uncountable? ? "#{plural_route_name}_index" : plural_route_name end def show_helper # :doc: - "#{singular_table_name}_url(@#{singular_table_name})" + "#{singular_route_name}_url(@#{singular_table_name})" end def edit_helper # :doc: @@ -142,7 +111,7 @@ module Rails end def new_helper # :doc: - "new_#{singular_table_name}_url" + "new_#{singular_route_name}_url" end def singular_table_name # :doc: @@ -178,6 +147,35 @@ module Rails end end + def redirect_resource_name # :doc: + model_resource_name(prefix: "@") + end + + def model_resource_name(prefix: "") # :doc: + resource_name = "#{prefix}#{singular_table_name}" + if options[:model_name] + "[#{controller_class_path.map { |name| ":" + name }.join(", ")}, #{resource_name}]" + else + resource_name + end + end + + def singular_route_name # :doc: + if options[:model_name] + "#{controller_class_path.join('_')}_#{singular_table_name}" + else + singular_table_name + end + end + + def plural_route_name # :doc: + if options[:model_name] + "#{controller_class_path.join('_')}_#{plural_table_name}" + else + plural_table_name + end + end + def assign_names!(name) @class_path = name.include?("/") ? name.split("/") : name.split("::") @class_path.map!(&:underscore) @@ -219,7 +217,7 @@ module Rails # def self.check_class_collision(options = {}) # :doc: define_method :check_class_collision do - name = if respond_to?(:controller_class_name) # for ScaffoldBase + name = if respond_to?(:controller_class_name) # for ResourceHelpers controller_class_name else class_name diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 3cf923faf0..bf4570db90 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/app_base" module Rails @@ -32,6 +34,14 @@ module Rails # This allows you to override entire operations, like the creation of the # Gemfile, README, or JavaScript files, without needing to know exactly # what those operations do so you can create another template action. + # + # class CustomAppBuilder < Rails::AppBuilder + # def test + # @generator.gem "rspec-rails", group: [:development, :test] + # run "bundle install" + # generate "rspec:install" + # end + # end class AppBuilder def rakefile template "Rakefile" @@ -41,6 +51,10 @@ module Rails copy_file "README.md", "README.md" end + def ruby_version + template "ruby-version", ".ruby-version" + end + def gemfile template "Gemfile" end @@ -55,10 +69,14 @@ module Rails def version_control if !options[:skip_git] && !options[:pretend] - run "git init" + run "git init", capture: options[:quiet] end end + def package_json + template "package.json" + end + def app directory "app" @@ -76,6 +94,16 @@ module Rails chmod "bin", 0755 & ~File.umask, verbose: false end + def bin_when_updating + bin_yarn_exist = File.exist?("bin/yarn") + + bin + + if options[:api] && !bin_yarn_exist + remove_file "bin/yarn" + end + end + def config empty_directory "config" @@ -83,10 +111,10 @@ module Rails template "routes.rb" template "application.rb" template "environment.rb" - template "secrets.yml" template "cable.yml" unless options[:skip_action_cable] template "puma.rb" unless options[:skip_puma] template "spring.rb" if spring_install? + template "storage.yml" unless skip_active_storage? directory "environments" directory "initializers" @@ -96,24 +124,59 @@ module Rails def config_when_updating cookie_serializer_config_exist = File.exist?("config/initializers/cookies_serializer.rb") - action_cable_config_exist = File.exist?("config/cable.yml") - rack_cors_config_exist = File.exist?("config/initializers/cors.rb") + action_cable_config_exist = File.exist?("config/cable.yml") + active_storage_config_exist = File.exist?("config/storage.yml") + rack_cors_config_exist = File.exist?("config/initializers/cors.rb") + assets_config_exist = File.exist?("config/initializers/assets.rb") + csp_config_exist = File.exist?("config/initializers/content_security_policy.rb") config - gsub_file "config/environments/development.rb", /^(\s+)config\.file_watcher/, '\1# config.file_watcher' - unless cookie_serializer_config_exist gsub_file "config/initializers/cookies_serializer.rb", /json(?!,)/, "marshal" end - unless action_cable_config_exist + if !options[:skip_action_cable] && !action_cable_config_exist template "config/cable.yml" end + if !skip_active_storage? && !active_storage_config_exist + template "config/storage.yml" + end + unless rack_cors_config_exist remove_file "config/initializers/cors.rb" end + + if options[:api] + unless cookie_serializer_config_exist + remove_file "config/initializers/cookies_serializer.rb" + end + + unless assets_config_exist + remove_file "config/initializers/assets.rb" + end + + unless csp_config_exist + remove_file "config/initializers/content_security_policy.rb" + end + end + end + + def master_key + return if options[:pretend] || options[:dummy_app] + + require "rails/generators/rails/master_key/master_key_generator" + master_key_generator = Rails::Generators::MasterKeyGenerator.new([], quiet: options[:quiet]) + master_key_generator.add_master_key_file_silently + master_key_generator.ignore_master_key_file_silently + end + + def credentials + return if options[:pretend] || options[:dummy_app] + + require "rails/generators/rails/credentials/credentials_generator" + Rails::Generators::CredentialsGenerator.new([], quiet: options[:quiet]).add_credentials_file_silently end def database_yml @@ -138,6 +201,11 @@ module Rails directory "public", "public", recursive: false end + def storage + empty_directory_with_keep_file "storage" + empty_directory_with_keep_file "tmp/storage" + end + def test empty_directory_with_keep_file "test/fixtures" empty_directory_with_keep_file "test/fixtures/files" @@ -150,6 +218,12 @@ module Rails template "test/test_helper.rb" end + def system_test + empty_directory_with_keep_file "test/system" + + template "test/application_system_test_case.rb" + end + def tmp empty_directory_with_keep_file "tmp" empty_directory "tmp/cache" @@ -158,20 +232,18 @@ module Rails def vendor empty_directory_with_keep_file "vendor" - - unless options[:skip_yarn] - template "package.json", "vendor/package.json" - end end end module Generators # We need to store the RAILS_DEV_PATH in a constant, otherwise the path # can change in Ruby 1.8.7 when we FileUtils.cd. - RAILS_DEV_PATH = File.expand_path("../../../../../..", File.dirname(__FILE__)) + RAILS_DEV_PATH = File.expand_path("../../../../../..", __dir__) RESERVED_NAMES = %w[application destroy plugin runner test] class AppGenerator < AppBase # :nodoc: + WEBPACKS = %w( react vue angular elm ) + add_shared_options_for "application" # Add bin/rails options @@ -184,6 +256,9 @@ module Rails class_option :skip_bundle, type: :boolean, aliases: "-B", default: false, desc: "Don't run bundle install" + class_option :webpack, type: :string, default: nil, + desc: "Preconfigure for app-like JavaScript with Webpack (options: #{WEBPACKS.join('/')})" + def initialize(*args) super @@ -204,10 +279,12 @@ module Rails def create_root_files build(:readme) build(:rakefile) + build(:ruby_version) build(:configru) build(:gitignore) unless options[:skip_git] build(:gemfile) unless options[:skip_gemfile] build(:version_control) + build(:package_json) unless options[:skip_yarn] end def create_app_files @@ -218,6 +295,11 @@ module Rails build(:bin) end + def update_bin_files + build(:bin_when_updating) + end + remove_task :update_bin_files + def create_config_files build(:config) end @@ -227,6 +309,14 @@ module Rails end remove_task :update_config_files + def create_master_key + build(:master_key) + end + + def create_credentials + build(:credentials) + end + def display_upgrade_guide_info say "\nAfter this, check Rails upgrade guide at http://guides.rubyonrails.org/upgrading_ruby_on_rails.html for more details about upgrading your app." end @@ -258,20 +348,24 @@ module Rails build(:public_directory) end - def create_test_files - build(:test) unless options[:skip_test] - end - def create_tmp_files build(:tmp) end def create_vendor_files build(:vendor) + end - if options[:skip_yarn] - remove_file "vendor/package.json" - end + def create_test_files + build(:test) unless options[:skip_test] + end + + def create_system_test_files + build(:system_test) if depends_on_system_test? + end + + def create_storage_files + build(:storage) unless skip_active_storage? end def delete_app_assets_if_api_option @@ -335,7 +429,6 @@ module Rails def delete_action_cable_files_skipping_action_cable if options[:skip_action_cable] - remove_file "config/cable.yml" remove_file "app/assets/javascripts/cable.js" remove_dir "app/channels" end @@ -344,6 +437,7 @@ module Rails def delete_non_api_initializers_if_api_option if options[:api] remove_file "config/initializers/cookies_serializer.rb" + remove_file "config/initializers/content_security_policy.rb" end end @@ -353,6 +447,12 @@ module Rails end end + def delete_new_framework_defaults + unless options[:update] + remove_file "config/initializers/new_framework_defaults_5_2.rb" + end + end + def delete_bin_yarn_if_skip_yarn_option remove_file "bin/yarn" if options[:skip_yarn] end @@ -363,6 +463,7 @@ module Rails public_task :apply_rails_template, :run_bundle public_task :run_webpack, :generate_spring_binstubs + public_task :run_active_storage def run_after_bundle_callbacks @after_bundle_callbacks.each(&:call) @@ -415,10 +516,6 @@ module Rails end end - def app_secret - SecureRandom.hex(64) - end - def mysql_socket @mysql_socket ||= [ "/tmp/mysql.sock", # default diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt index 24d2fa1284..23bb89f4ce 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt @@ -1,10 +1,11 @@ source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } -git_source(:github) do |repo_name| - repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") - "https://github.com/#{repo_name}.git" -end +ruby <%= "'#{RUBY_VERSION}'" -%> + +<% unless gemfile_entries.first.comment -%> +<% end -%> <% gemfile_entries.each do |gem| -%> <% if gem.comment -%> @@ -19,10 +20,20 @@ end # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' +<% unless skip_active_storage? -%> + +# Use ActiveStorage variant +# gem 'mini_magick', '~> 4.8' +<% end -%> # Use Capistrano for deployment # gem 'capistrano-rails', group: :development +<% if depend_on_bootsnap? -%> +# Reduces boot times through caching; required in config/boot.rb +gem 'bootsnap', '>= 1.1.0', require: false + +<%- end -%> <%- if options.api? -%> # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible # gem 'rack-cors' @@ -36,7 +47,7 @@ end group :development do <%- unless options.api? -%> - # Access an IRB console on exception pages or by using <%%= console %> anywhere in the code. + # Access an interactive console on exception pages or by calling 'console' anywhere in the code. <%- if options.dev? || options.edge? -%> gem 'web-console', github: 'rails/web-console' <%- else -%> @@ -54,6 +65,16 @@ group :development do <% end -%> <% end -%> end + +<%- if depends_on_system_test? -%> +group :test do + # Adds support for Capybara system testing and selenium driver + gem 'capybara', '~> 2.15' + gem 'selenium-webdriver' + # Easy installation and use of chromedriver to run system tests with Chrome + gem 'chromedriver-helper' +end +<%- end -%> <% end -%> # Windows does not include zoneinfo files, so bundle the tzinfo-data gem diff --git a/railties/lib/rails/generators/rails/app/templates/README.md b/railties/lib/rails/generators/rails/app/templates/README.md.tt index 7db80e4ca1..7db80e4ca1 100644 --- a/railties/lib/rails/generators/rails/app/templates/README.md +++ b/railties/lib/rails/generators/rails/app/templates/README.md.tt diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile.tt index e85f913914..e85f913914 100644 --- a/railties/lib/rails/generators/rails/app/templates/Rakefile +++ b/railties/lib/rails/generators/rails/app/templates/Rakefile.tt diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt index 25870f19c8..5183bcd256 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt @@ -1,7 +1,7 @@ // This is a manifest file that'll be compiled into application.js, which will include all the files // listed below. // -// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's // vendor/assets/javascripts directory can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the @@ -11,10 +11,10 @@ // about supported directives. // <% unless options[:skip_javascript] -%> -<% if options[:javascript] -%> -//= require <%= options[:javascript] %> -<% end -%> //= require rails-ujs +<% unless skip_active_storage? -%> +//= require activestorage +<% end -%> <% unless options[:skip_turbolinks] -%> //= require turbolinks <% end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js.tt index 739aa5f022..739aa5f022 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js.tt diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css.tt index 865300bef9..d05ea0f511 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css.tt @@ -2,7 +2,7 @@ * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. * - * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's * vendor/assets/stylesheets directory can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the diff --git a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb.tt index d672697283..d672697283 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb +++ b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb.tt index 0ff5442f47..0ff5442f47 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb +++ b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt index 413354186d..938eff8ed0 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt @@ -1,5 +1,2 @@ -class ApplicationController < ActionController::<%= options[:api] ? "API" : "Base" %> -<%- unless options[:api] -%> - protect_from_forgery with: :exception -<%- end -%> +class ApplicationController < ActionController::<%= options.api? ? "API" : "Base" %> end diff --git a/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb b/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb.tt index de6be7945c..de6be7945c 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb +++ b/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb b/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb.tt index a009ace51c..a009ace51c 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb +++ b/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb b/railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb.tt index 286b2239d1..286b2239d1 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb +++ b/railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb b/railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb.tt index 10a4cba84d..10a4cba84d 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb +++ b/railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/bin/bundle b/railties/lib/rails/generators/rails/app/templates/bin/bundle deleted file mode 100644 index 1123dcf501..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/bin/bundle +++ /dev/null @@ -1,2 +0,0 @@ -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -load Gem.bin_path('bundler', 'bundle') diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/bin/bundle.tt index 30f5120df6..a84f0afe47 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb +++ b/railties/lib/rails/generators/rails/app/templates/bin/bundle.tt @@ -1,3 +1,2 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) - -require 'bundler/setup' # Set up gems listed in the Gemfile. +load Gem.bin_path('bundler', 'bundle') diff --git a/railties/lib/rails/generators/rails/app/templates/bin/rails b/railties/lib/rails/generators/rails/app/templates/bin/rails.tt index 513a2e0183..513a2e0183 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/rails +++ b/railties/lib/rails/generators/rails/app/templates/bin/rails.tt diff --git a/railties/lib/rails/generators/rails/app/templates/bin/rake b/railties/lib/rails/generators/rails/app/templates/bin/rake.tt index d14fc8395b..d14fc8395b 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/rake +++ b/railties/lib/rails/generators/rails/app/templates/bin/rake.tt diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt index c6607dbb2b..233b5a1d95 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt +++ b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt @@ -1,9 +1,8 @@ -require 'pathname' require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -16,12 +15,13 @@ chdir APP_ROOT do puts '== Installing dependencies ==' system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') -<% unless options[:skip_yarn] %> +<% unless options.skip_yarn? -%> + # Install JavaScript dependencies if using Yarn # system('bin/yarn') +<% end -%> +<% unless options.skip_active_record? -%> -<% end %> -<% unless options.skip_active_record -%> # puts "\n== Copying sample files ==" # unless File.exist?('config/database.yml') # cp 'config/database.yml.sample', 'config/database.yml' diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update.tt b/railties/lib/rails/generators/rails/app/templates/bin/update.tt index d23af018c7..70cc71d83b 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/update.tt +++ b/railties/lib/rails/generators/rails/app/templates/bin/update.tt @@ -1,9 +1,8 @@ -require 'pathname' require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -16,7 +15,13 @@ chdir APP_ROOT do puts '== Installing dependencies ==' system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') -<% unless options.skip_active_record -%> +<% unless options.skip_yarn? -%> + + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') +<% end -%> +<% unless options.skip_active_record? -%> + puts "\n== Updating database ==" system! 'bin/rails db:migrate' <% end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/bin/yarn b/railties/lib/rails/generators/rails/app/templates/bin/yarn deleted file mode 100644 index 872438cecb..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/bin/yarn +++ /dev/null @@ -1,9 +0,0 @@ -VENDOR_PATH = File.expand_path('../vendor', __dir__) -Dir.chdir(VENDOR_PATH) do - begin - exec "yarnpkg #{ARGV.join(" ")}" - rescue Errno::ENOENT - puts "Yarn executable was not detected in the system." - puts "Download Yarn at https://yarnpkg.com/en/docs/install" - end -end diff --git a/railties/lib/rails/generators/rails/app/templates/bin/yarn.tt b/railties/lib/rails/generators/rails/app/templates/bin/yarn.tt new file mode 100644 index 0000000000..b4e4d95286 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/bin/yarn.tt @@ -0,0 +1,10 @@ +APP_ROOT = File.expand_path('..', __dir__) +Dir.chdir(APP_ROOT) do + begin + exec "yarnpkg #{ARGV.join(' ')}" + rescue Errno::ENOENT + $stderr.puts "Yarn executable was not detected in the system." + $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" + exit 1 + end +end diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru.tt index f7ba0b527b..f7ba0b527b 100644 --- a/railties/lib/rails/generators/rails/app/templates/config.ru +++ b/railties/lib/rails/generators/rails/app/templates/config.ru.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt index c0a0bd0a3e..9e03e86771 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt @@ -8,6 +8,7 @@ require "rails" require "active_model/railtie" require "active_job/railtie" <%= comment_if :skip_active_record %>require "active_record/railtie" +<%= comment_if :skip_active_storage %>require "active_storage/engine" require "action_controller/railtie" <%= comment_if :skip_action_mailer %>require "action_mailer/railtie" require "action_view/railtie" @@ -22,15 +23,22 @@ Bundler.require(*Rails.groups) module <%= app_const_base %> class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults <%= Rails::VERSION::STRING.to_f %> + # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. -<%- if options[:api] -%> +<%- if options.api? -%> # Only loads a smaller set of middleware suitable for API only apps. # Middleware like session, flash, cookies can be added back manually. # Skip views, helpers and assets when generating a new resource. config.api_only = true +<%- elsif !depends_on_system_test? -%> + + # Don't generate system test files. + config.generators.system_tests = nil <%- end -%> end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt new file mode 100644 index 0000000000..720d36a2a4 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt @@ -0,0 +1,10 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. +<% if depend_on_bootsnap? -%> +require 'bootsnap/setup' # Speed up boot time by caching expensive operations. +<%- end -%> + +if %w[s server c console].any? { |a| ARGV.include?(a) } + puts "=> Booting Rails" +end diff --git a/railties/lib/rails/generators/rails/app/templates/config/cable.yml b/railties/lib/rails/generators/rails/app/templates/config/cable.yml.tt index 1da4913082..8e53156c71 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/cable.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/cable.yml.tt @@ -6,5 +6,5 @@ test: production: adapter: redis - url: redis://localhost:6379/1 + url: <%%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> channel_prefix: <%= app_name %>_production diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt index 917b52e535..917b52e535 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt index d40117a27f..d40117a27f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt index 563be77710..563be77710 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt index 8bc8735a8e..2a67bdca25 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt @@ -7,7 +7,7 @@ # gem 'activerecord-jdbcmysql-adapter' # # And be sure to use new-style password hashing: -# http://dev.mysql.com/doc/refman/5.7/en/old-client.html +# https://dev.mysql.com/doc/refman/5.7/en/password-hashing.html # default: &default adapter: mysql diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt index 70df04079d..70df04079d 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml.tt index 371415e6a8..371415e6a8 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt index 269af1470d..04afaa0596 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt @@ -7,7 +7,7 @@ # gem 'mysql2' # # And be sure to use new-style password hashing: -# http://dev.mysql.com/doc/refman/5.7/en/old-client.html +# https://dev.mysql.com/doc/refman/5.7/en/password-hashing.html # default: &default adapter: mysql2 diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt index d2499ea4fb..6da0601b24 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt @@ -1,4 +1,4 @@ -# Oracle/OCI 8i, 9, 10g +# Oracle/OCI 11g or higher recommended # # Requires Ruby/OCI8: # https://github.com/kubo/ruby-oci8 @@ -17,7 +17,7 @@ # cursor_sharing: similar # default: &default - adapter: oracle + adapter: oracle_enhanced pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: <%= app_name %> password: @@ -45,7 +45,9 @@ test: # On Heroku and other platform providers, you may have a full connection URL # available as an environment variable. For example: # -# DATABASE_URL="oracle://myuser:mypass@localhost/somedatabase" +# DATABASE_URL="oracle-enhanced://myuser:mypass@localhost/somedatabase" +# +# Note that the adapter name uses a dash instead of an underscore. # # You can use this database configuration with: # diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt index 145cfb7f74..145cfb7f74 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt index 9510568124..9510568124 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt index a21555e573..049de65f22 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt @@ -1,4 +1,4 @@ -# SQL Server (2012 or higher recommended) +# SQL Server (2012 or higher required) # # Install the adapters and driver # gem install tiny_tds @@ -12,7 +12,7 @@ default: &default adapter: sqlserver encoding: utf8 username: sa - password: <%= ENV['SA_PASSWORD'] %> + password: <%%= ENV['SA_PASSWORD'] %> host: localhost development: diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb.tt index 426333bb46..426333bb46 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environment.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index 511b4a82eb..a87649b50f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -13,18 +13,24 @@ Rails.application.configure do config.consider_all_requests_local = true # Enable/disable caching. By default caching is disabled. - if Rails.root.join('tmp/caching-dev.txt').exist? + # Run rails dev:cache to toggle caching. + if Rails.root.join('tmp', 'caching-dev.txt').exist? config.action_controller.perform_caching = true config.cache_store = :memory_store config.public_file_server.headers = { - 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" + 'Cache-Control' => "public, max-age=#{2.days.to_i}" } else config.action_controller.perform_caching = false config.cache_store = :null_store end + <%- unless skip_active_storage? -%> + + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + <%- end -%> <%- unless options.skip_action_mailer? -%> # Don't care if the mailer can't send. @@ -40,6 +46,9 @@ Rails.application.configure do # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + <%- end -%> <%- unless options.skip_sprockets? -%> # Debug mode disables concatenation and preprocessing of assets. diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 4a39e43e57..4c0f36db98 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -14,6 +14,10 @@ Rails.application.configure do config.consider_all_requests_local = false config.action_controller.perform_caching = true + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? @@ -31,8 +35,8 @@ Rails.application.configure do config.assets.compile = false # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb - <%- end -%> + <%- end -%> # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.action_controller.asset_host = 'http://assets.example.com' @@ -40,13 +44,18 @@ Rails.application.configure do # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + <%- unless skip_active_storage? -%> + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + + <%- end -%> <%- unless options[:skip_action_cable] -%> # Mount Action Cable outside main process or domain # config.action_cable.mount_path = nil # config.action_cable.url = 'wss://example.com/cable' # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] - <%- end -%> + <%- end -%> # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true @@ -63,14 +72,15 @@ Rails.application.configure do # Use a real queuing backend for Active Job (and separate queues per environment) # config.active_job.queue_adapter = :resque # config.active_job.queue_name_prefix = "<%= app_name %>_#{Rails.env}" + <%- unless options.skip_action_mailer? -%> config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. # config.action_mailer.raise_delivery_errors = false - <%- end -%> + <%- end -%> # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index 56416b3075..ff4c89219a 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -15,7 +15,7 @@ Rails.application.configure do # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { - 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" } # Show full error reports and disable caching. @@ -27,6 +27,12 @@ Rails.application.configure do # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false + + <%- unless skip_active_storage? -%> + # Store uploaded files on the local file system in a temporary directory + config.active_storage.service = :test + + <%- end -%> <%- unless options.skip_action_mailer? -%> config.action_mailer.perform_caching = false @@ -34,8 +40,8 @@ Rails.application.configure do # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test - <%- end -%> + <%- end -%> # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb deleted file mode 100644 index 51639b67a0..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb +++ /dev/null @@ -1,6 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# ApplicationController.renderer.defaults.merge!( -# http_host: 'example.org', -# https: false -# ) diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb.tt new file mode 100644 index 0000000000..89d2efab2b --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb.tt @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt index f5d03fb117..51196ae743 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt @@ -7,7 +7,7 @@ Rails.application.config.assets.version = '1.0' # Rails.application.config.assets.paths << Emoji.images_path <%- unless options[:skip_yarn] -%> # Add Yarn node_modules folder to the asset load path. -Rails.application.config.assets.paths << Rails.root.join('vendor/node_modules') +Rails.application.config.assets.paths << Rails.root.join('node_modules') <%- end -%> # Precompile additional assets. diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb.tt index 59385cdf37..59385cdf37 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt new file mode 100644 index 0000000000..656ded4069 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt @@ -0,0 +1,20 @@ +# Define an application-wide content security policy +# For further information see the following documentation +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + +Rails.application.config.content_security_policy do |p| + p.default_src :self, :https + p.font_src :self, :https, :data + p.img_src :self, :https, :data + p.object_src :none + p.script_src :self, :https + p.style_src :self, :https, :unsafe_inline + + # Specify URI for violation reports + # p.report_uri "/csp-violation-report-endpoint" +end + +# Report CSP violations to a specified URI +# For further information see the following documentation: +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only +# Rails.application.config.content_security_policy_report_only = true diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb.tt index 5a6a32d371..5a6a32d371 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb.tt index 3b1c1b5ed1..3b1c1b5ed1 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt index 4a994e1e7b..4a994e1e7b 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb.tt index ac033bf9dc..ac033bf9dc 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb.tt index dc1899682b..dc1899682b 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt deleted file mode 100644 index 3ad3eba98a..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt +++ /dev/null @@ -1,40 +0,0 @@ -# Be sure to restart your server when you modify this file. -# -# This file contains migration options to ease your Rails 5.0 upgrade. -# -<%- if options[:update] -%> -# Once upgraded flip defaults one by one to migrate to the new default. -# -<%- end -%> -# Read the Guide for Upgrading Ruby on Rails for more info on each option. -<%- unless options[:api] -%> - -# Enable per-form CSRF tokens. Previous versions had false. -Rails.application.config.action_controller.per_form_csrf_tokens = <%= options[:update] ? false : true %> - -# Enable origin-checking CSRF mitigation. Previous versions had false. -Rails.application.config.action_controller.forgery_protection_origin_check = <%= options[:update] ? false : true %> -<%- end -%> - -# Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. -# Previous versions had false. -ActiveSupport.to_time_preserves_timezone = <%= options[:update] ? false : true %> -<%- unless options[:skip_active_record] -%> - -# Require `belongs_to` associations by default. Previous versions had false. -Rails.application.config.active_record.belongs_to_required_by_default = <%= options[:update] ? false : true %> -<%- end -%> - -# Do not halt callback chains when a callback returns false. Previous versions had true. -ActiveSupport.halt_callback_chains_on_return_false = <%= options[:update] ? true : false %> -<%- unless options[:update] -%> - -# Configure SSL options to enable HSTS with subdomains. Previous versions had false. -Rails.application.config.ssl_options = { hsts: { subdomains: true } } -<%- end -%> -<%- unless options[:skip_sprockets] -%> - -# Unknown asset fallback will return the path passed in when the given -# asset is not present in the asset pipeline. -Rails.application.config.assets.unknown_asset_fallback = <%= options[:update] ? true : false %> -<%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt new file mode 100644 index 0000000000..ae665b960a --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt @@ -0,0 +1,27 @@ +# Be sure to restart your server when you modify this file. +# +# This file contains migration options to ease your Rails 5.2 upgrade. +# +# Once upgraded flip defaults one by one to migrate to the new default. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. + +# Make Active Record use stable #cache_key alongside new #cache_version method. +# This is needed for recyclable cache keys. +# Rails.application.config.active_record.cache_versioning = true + +# Use AES-256-GCM authenticated encryption for encrypted cookies. +# Existing cookies will be converted on read then written with the new scheme. +# Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = true + +# Use AES-256-GCM authenticated encryption as default cipher for encrypting messages +# instead of AES-256-CBC, when use_authenticated_message_encryption is set to true. +# Rails.application.config.active_support.use_authenticated_message_encryption = true + +# Add default protection from forgery to ActionController::Base instead of in +# ApplicationController. +# Rails.application.config.action_controller.default_protect_from_forgery = true + +# Store boolean values are in sqlite3 databases as 1 and 0 instead of 't' and +# 'f' after migrating old data. +# Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true diff --git a/railties/lib/rails/generators/rails/app/templates/config/puma.rb b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt index 1e19380dcb..a5eccf816b 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/puma.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt @@ -26,31 +26,9 @@ environment ENV.fetch("RAILS_ENV") { "development" } # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code # before forking the application. This takes advantage of Copy On Write -# process behavior so workers use less memory. If you use this option -# you need to make sure to reconnect any threads in the `on_worker_boot` -# block. +# process behavior so workers use less memory. # # preload_app! -# If you are preloading your application and using Active Record, it's -# recommended that you close any connections to the database before workers -# are forked to prevent connection leakage. -# -# before_fork do -# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) -# end - -# The code in the `on_worker_boot` will be called if you are using -# clustered mode by specifying a number of `workers`. After each worker -# process is booted, this block will be run. If you are using the `preload_app!` -# option, you will want to use this block to reconnect to any threads -# or connections that may have been created at application boot, as Ruby -# cannot share connections between processes. -# -# on_worker_boot do -# ActiveRecord::Base.establish_connection if defined?(ActiveRecord) -# end -# - # Allow puma to be restarted by `rails restart` command. plugin :tmp_restart diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb.tt index 787824f888..787824f888 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/secrets.yml b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml deleted file mode 100644 index 8e995a5df1..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/config/secrets.yml +++ /dev/null @@ -1,30 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Your secret key is used for verifying the integrity of signed cookies. -# If you change this key, all old signed cookies will become invalid! - -# Make sure the secret is at least 30 characters and all random, -# no regular words or you'll be exposed to dictionary attacks. -# You can use `rails secret` to generate a secure secret key. - -# Make sure the secrets in this file are kept private -# if you're sharing your code publicly. - -# Shared secrets are available across all environments. - -shared: - api_key: 123 - -# Environmental secrets are only available for that specific environment. - -development: - secret_key_base: <%= app_secret %> - -test: - secret_key_base: <%= app_secret %> - -# Do not keep production secrets in the repository, -# instead read values from the environment. - -production: - secret_key_base: <%%= ENV["SECRET_KEY_BASE"] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/spring.rb b/railties/lib/rails/generators/rails/app/templates/config/spring.rb.tt index c9119b40c0..9fa7863f99 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/spring.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/spring.rb.tt @@ -1,6 +1,6 @@ -%w( +%w[ .ruby-version .rbenv-vars tmp/restart.txt tmp/caching-dev.txt -).each { |path| Spring.watch(path) } +].each { |path| Spring.watch(path) } diff --git a/railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt new file mode 100644 index 0000000000..1c0cde0b09 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt @@ -0,0 +1,35 @@ +test: + service: Disk + root: <%%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%%= Rails.root.join("storage") %> + +# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket + +# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# path: your_azure_storage_path +# storage_account_name: your_account_name +# storage_access_key: <%%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore.tt index 1768b700d9..2cd8335aba 100644 --- a/railties/lib/rails/generators/rails/app/templates/gitignore +++ b/railties/lib/rails/generators/rails/app/templates/gitignore.tt @@ -21,9 +21,17 @@ !/tmp/.keep <% end -%> -<% unless options[:skip_yarn] -%> -/vendor/node_modules -/vendor/yarn-error.log +<% unless skip_active_storage? -%> +# Ignore uploaded files in development +/storage/* <% end -%> +<% unless options.skip_yarn? -%> +/node_modules +/yarn-error.log + +<% end -%> +<% unless options.api? -%> +/public/assets +<% end -%> .byebug_history diff --git a/railties/lib/rails/generators/rails/app/templates/package.json b/railties/lib/rails/generators/rails/app/templates/package.json.tt index 46db57dcbe..46db57dcbe 100644 --- a/railties/lib/rails/generators/rails/app/templates/package.json +++ b/railties/lib/rails/generators/rails/app/templates/package.json.tt diff --git a/railties/lib/rails/generators/rails/app/templates/public/404.html b/railties/lib/rails/generators/rails/app/templates/public/404.html index b612547fc2..2be3af26fc 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/404.html +++ b/railties/lib/rails/generators/rails/app/templates/public/404.html @@ -4,7 +4,7 @@ <title>The page you were looking for doesn't exist (404)</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> - body { + .rails-default-error-page { background-color: #EFEFEF; color: #2E2F30; text-align: center; @@ -12,13 +12,13 @@ margin: 0; } - div.dialog { + .rails-default-error-page div.dialog { width: 95%; max-width: 33em; margin: 4em auto 0; } - div.dialog > div { + .rails-default-error-page div.dialog > div { border: 1px solid #CCC; border-right-color: #999; border-left-color: #999; @@ -31,13 +31,13 @@ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); } - h1 { + .rails-default-error-page h1 { font-size: 100%; color: #730E15; line-height: 1.5em; } - div.dialog > p { + .rails-default-error-page div.dialog > p { margin: 0 0 1em; padding: 1em; background-color: #F7F7F7; @@ -54,7 +54,7 @@ </style> </head> -<body> +<body class="rails-default-error-page"> <!-- This file lives in public/404.html --> <div class="dialog"> <div> diff --git a/railties/lib/rails/generators/rails/app/templates/public/422.html b/railties/lib/rails/generators/rails/app/templates/public/422.html index a21f82b3bd..c08eac0d1d 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/422.html +++ b/railties/lib/rails/generators/rails/app/templates/public/422.html @@ -4,7 +4,7 @@ <title>The change you wanted was rejected (422)</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> - body { + .rails-default-error-page { background-color: #EFEFEF; color: #2E2F30; text-align: center; @@ -12,13 +12,13 @@ margin: 0; } - div.dialog { + .rails-default-error-page div.dialog { width: 95%; max-width: 33em; margin: 4em auto 0; } - div.dialog > div { + .rails-default-error-page div.dialog > div { border: 1px solid #CCC; border-right-color: #999; border-left-color: #999; @@ -31,13 +31,13 @@ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); } - h1 { + .rails-default-error-page h1 { font-size: 100%; color: #730E15; line-height: 1.5em; } - div.dialog > p { + .rails-default-error-page div.dialog > p { margin: 0 0 1em; padding: 1em; background-color: #F7F7F7; @@ -54,7 +54,7 @@ </style> </head> -<body> +<body class="rails-default-error-page"> <!-- This file lives in public/422.html --> <div class="dialog"> <div> diff --git a/railties/lib/rails/generators/rails/app/templates/public/500.html b/railties/lib/rails/generators/rails/app/templates/public/500.html index 061abc587d..78a030af22 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/500.html +++ b/railties/lib/rails/generators/rails/app/templates/public/500.html @@ -4,7 +4,7 @@ <title>We're sorry, but something went wrong (500)</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> - body { + .rails-default-error-page { background-color: #EFEFEF; color: #2E2F30; text-align: center; @@ -12,13 +12,13 @@ margin: 0; } - div.dialog { + .rails-default-error-page div.dialog { width: 95%; max-width: 33em; margin: 4em auto 0; } - div.dialog > div { + .rails-default-error-page div.dialog > div { border: 1px solid #CCC; border-right-color: #999; border-left-color: #999; @@ -31,13 +31,13 @@ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); } - h1 { + .rails-default-error-page h1 { font-size: 100%; color: #730E15; line-height: 1.5em; } - div.dialog > p { + .rails-default-error-page div.dialog > p { margin: 0 0 1em; padding: 1em; background-color: #F7F7F7; @@ -54,7 +54,7 @@ </style> </head> -<body> +<body class="rails-default-error-page"> <!-- This file lives in public/500.html --> <div class="dialog"> <div> diff --git a/railties/lib/rails/generators/rails/app/templates/ruby-version.tt b/railties/lib/rails/generators/rails/app/templates/ruby-version.tt new file mode 100644 index 0000000000..c444f33b0f --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/ruby-version.tt @@ -0,0 +1 @@ +<%= RUBY_VERSION -%> diff --git a/railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb.tt b/railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb.tt new file mode 100644 index 0000000000..d19212abd5 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb.tt @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt index 2f92168eef..52d68cc77c 100644 --- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb +++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt @@ -1,4 +1,5 @@ -require File.expand_path('../../config/environment', __FILE__) +ENV['RAILS_ENV'] ||= 'test' +require_relative '../config/environment' require 'rails/test_help' class ActiveSupport::TestCase diff --git a/railties/lib/rails/generators/rails/application_record/application_record_generator.rb b/railties/lib/rails/generators/rails/application_record/application_record_generator.rb new file mode 100644 index 0000000000..f6b6e76b1d --- /dev/null +++ b/railties/lib/rails/generators/rails/application_record/application_record_generator.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Rails + module Generators + class ApplicationRecordGenerator < Base # :nodoc: + hook_for :orm, required: true, desc: "ORM to be invoked" + end + end +end diff --git a/railties/lib/rails/generators/rails/assets/assets_generator.rb b/railties/lib/rails/generators/rails/assets/assets_generator.rb index 95d00c2d39..ffb695a1f3 100644 --- a/railties/lib/rails/generators/rails/assets/assets_generator.rb +++ b/railties/lib/rails/generators/rails/assets/assets_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Generators class AssetsGenerator < NamedBase # :nodoc: diff --git a/railties/lib/rails/generators/rails/assets/templates/stylesheet.css b/railties/lib/rails/generators/rails/assets/templates/stylesheet.css index 7594abf268..afad32db02 100644 --- a/railties/lib/rails/generators/rails/assets/templates/stylesheet.css +++ b/railties/lib/rails/generators/rails/assets/templates/stylesheet.css @@ -1,4 +1,4 @@ -/* +/* Place all the styles related to the matching controller here. They will automatically be included in application.css. */ diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb index 06bdb8b5ce..6d45d6e8f8 100644 --- a/railties/lib/rails/generators/rails/controller/controller_generator.rb +++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Generators class ControllerGenerator < NamedBase # :nodoc: @@ -13,12 +15,8 @@ module Rails end def add_routes - unless options[:skip_routes] - actions.reverse_each do |action| - # route prepends two spaces onto the front of the string that is passed, this corrects that. - route indent(generate_routing_code(action), 2)[2..-1] - end - end + return if options[:skip_routes] + route generate_routing_code end hook_for :template_engine, :test_framework, :helper, :assets @@ -26,14 +24,15 @@ module Rails private # This method creates nested route entry for namespaced resources. - # For eg. rails g controller foo/bar/baz index + # For eg. rails g controller foo/bar/baz index show # Will generate - # namespace :foo do # namespace :bar do # get 'baz/index' + # get 'baz/show' # end # end - def generate_routing_code(action) + def generate_routing_code depth = 0 lines = [] @@ -47,7 +46,10 @@ module Rails # Create route # get 'baz/index' - lines << indent(%{get '#{file_name}/#{action}'\n}, depth * 2) + # get 'baz/show' + actions.each do |action| + lines << indent(%{get '#{file_name}/#{action}'\n}, depth * 2) + end # Create `end` ladder # end diff --git a/railties/lib/rails/generators/rails/controller/templates/controller.rb b/railties/lib/rails/generators/rails/controller/templates/controller.rb.tt index 633e0b3177..633e0b3177 100644 --- a/railties/lib/rails/generators/rails/controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/controller/templates/controller.rb.tt diff --git a/railties/lib/rails/generators/rails/credentials/credentials_generator.rb b/railties/lib/rails/generators/rails/credentials/credentials_generator.rb new file mode 100644 index 0000000000..9103b1122e --- /dev/null +++ b/railties/lib/rails/generators/rails/credentials/credentials_generator.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "rails/generators/base" +require "rails/generators/rails/master_key/master_key_generator" +require "active_support/encrypted_configuration" + +module Rails + module Generators + class CredentialsGenerator < Base + def add_credentials_file + unless credentials.content_path.exist? + template = credentials_template + + say "Adding #{credentials.content_path} to store encrypted credentials." + say "" + say "The following content has been encrypted with the Rails master key:" + say "" + say template, :on_green + say "" + + add_credentials_file_silently(template) + + say "You can edit encrypted credentials with `bin/rails credentials:edit`." + say "" + end + end + + def add_credentials_file_silently(template = nil) + unless credentials.content_path.exist? + credentials.write(credentials_template) + end + end + + private + def credentials + ActiveSupport::EncryptedConfiguration.new( + config_path: "config/credentials.yml.enc", + key_path: "config/master.key", + env_key: "RAILS_MASTER_KEY", + raise_if_missing_key: true + ) + end + + def credentials_template + "# aws:\n# access_key_id: 123\n# secret_access_key: 345\n\n" + + "# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.\n" + + "secret_key_base: #{SecureRandom.hex(64)}" + end + end + end +end diff --git a/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb b/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb new file mode 100644 index 0000000000..4ce2fc1d86 --- /dev/null +++ b/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "rails/generators/base" +require "active_support/encrypted_file" + +module Rails + module Generators + class EncryptedFileGenerator < Base + def add_encrypted_file(file_path, key_path) + unless File.exist?(file_path) + say "Adding #{file_path} to store encrypted content." + say "" + say "The following content has been encrypted with the encryption key:" + say "" + say template, :on_green + say "" + + add_encrypted_file_silently(file_path, key_path) + + say "You can edit encrypted file with `bin/rails encrypted:edit #{file_path}`." + say "" + end + end + + def add_encrypted_file_silently(file_path, key_path, template = encrypted_file_template) + unless File.exist?(file_path) + setup = { content_path: file_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true } + ActiveSupport::EncryptedFile.new(setup).write(template) + end + end + + private + def encrypted_file_template + "# aws:\n# access_key_id: 123\n# secret_access_key: 345\n\n" + end + end + end +end diff --git a/railties/lib/rails/generators/rails/encryption_key_file/encryption_key_file_generator.rb b/railties/lib/rails/generators/rails/encryption_key_file/encryption_key_file_generator.rb new file mode 100644 index 0000000000..a396a9661f --- /dev/null +++ b/railties/lib/rails/generators/rails/encryption_key_file/encryption_key_file_generator.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "pathname" +require "rails/generators/base" +require "active_support/encrypted_file" + +module Rails + module Generators + class EncryptionKeyFileGenerator < Base + def add_key_file(key_path) + key_path = Pathname.new(key_path) + + unless key_path.exist? + key = ActiveSupport::EncryptedFile.generate_key + + log "Adding #{key_path} to store the encryption key: #{key}" + log "" + log "Save this in a password manager your team can access." + log "" + log "If you lose the key, no one, including you, can access anything encrypted with it." + + log "" + add_key_file_silently(key_path, key) + log "" + end + end + + def add_key_file_silently(key_path, key = nil) + create_file key_path, key || ActiveSupport::EncryptedFile.generate_key + end + + def ignore_key_file(key_path, ignore: key_ignore(key_path)) + if File.exist?(".gitignore") + unless File.read(".gitignore").include?(ignore) + log "Ignoring #{key_path} so it won't end up in Git history:" + log "" + append_to_file ".gitignore", ignore + log "" + end + else + log "IMPORTANT: Don't commit #{key_path}. Add this to your ignore file:" + log ignore, :on_green + log "" + end + end + + def ignore_key_file_silently(key_path, ignore: key_ignore(key_path)) + append_to_file ".gitignore", ignore if File.exist?(".gitignore") + end + + private + def key_ignore(key_path) + [ "", "/#{key_path}", "" ].join("\n") + end + end + end +end diff --git a/railties/lib/rails/generators/rails/generator/generator_generator.rb b/railties/lib/rails/generators/rails/generator/generator_generator.rb index 299a7da5f1..747acd68d1 100644 --- a/railties/lib/rails/generators/rails/generator/generator_generator.rb +++ b/railties/lib/rails/generators/rails/generator/generator_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Generators class GeneratorGenerator < NamedBase # :nodoc: diff --git a/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt b/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt index d0575772bc..178d5c3f9f 100644 --- a/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt +++ b/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt @@ -1,3 +1,3 @@ class <%= class_name %>Generator < Rails::Generators::NamedBase - source_root File.expand_path('../templates', __FILE__) + source_root File.expand_path('templates', __dir__) end diff --git a/railties/lib/rails/generators/rails/helper/helper_generator.rb b/railties/lib/rails/generators/rails/helper/helper_generator.rb index e48b1b6fb3..3837c10ca0 100644 --- a/railties/lib/rails/generators/rails/helper/helper_generator.rb +++ b/railties/lib/rails/generators/rails/helper/helper_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Generators class HelperGenerator < NamedBase # :nodoc: diff --git a/railties/lib/rails/generators/rails/helper/templates/helper.rb b/railties/lib/rails/generators/rails/helper/templates/helper.rb.tt index b4173151b4..b4173151b4 100644 --- a/railties/lib/rails/generators/rails/helper/templates/helper.rb +++ b/railties/lib/rails/generators/rails/helper/templates/helper.rb.tt diff --git a/railties/lib/rails/generators/rails/integration_test/integration_test_generator.rb b/railties/lib/rails/generators/rails/integration_test/integration_test_generator.rb index 70770ddcb8..975dd8b90c 100644 --- a/railties/lib/rails/generators/rails/integration_test/integration_test_generator.rb +++ b/railties/lib/rails/generators/rails/integration_test/integration_test_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Generators class IntegrationTestGenerator < NamedBase # :nodoc: diff --git a/railties/lib/rails/generators/rails/master_key/master_key_generator.rb b/railties/lib/rails/generators/rails/master_key/master_key_generator.rb new file mode 100644 index 0000000000..7f57340c11 --- /dev/null +++ b/railties/lib/rails/generators/rails/master_key/master_key_generator.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "pathname" +require "rails/generators/base" +require "rails/generators/rails/encryption_key_file/encryption_key_file_generator" +require "active_support/encrypted_file" + +module Rails + module Generators + class MasterKeyGenerator < Base + MASTER_KEY_PATH = Pathname.new("config/master.key") + + def add_master_key_file + unless MASTER_KEY_PATH.exist? + key = ActiveSupport::EncryptedFile.generate_key + + log "Adding #{MASTER_KEY_PATH} to store the master encryption key: #{key}" + log "" + log "Save this in a password manager your team can access." + log "" + log "If you lose the key, no one, including you, can access anything encrypted with it." + + log "" + add_master_key_file_silently(key) + log "" + end + end + + def add_master_key_file_silently(key = nil) + key_file_generator.add_key_file_silently(MASTER_KEY_PATH, key) + end + + def ignore_master_key_file + key_file_generator.ignore_key_file(MASTER_KEY_PATH, ignore: key_ignore) + end + + def ignore_master_key_file_silently + key_file_generator.ignore_key_file_silently(MASTER_KEY_PATH, ignore: key_ignore) + end + + private + def key_file_generator + EncryptionKeyFileGenerator.new([], options) + end + + def key_ignore + [ "", "# Ignore master key for decrypting credentials and more.", "/#{MASTER_KEY_PATH}", "" ].join("\n") + end + end + end +end diff --git a/railties/lib/rails/generators/rails/migration/migration_generator.rb b/railties/lib/rails/generators/rails/migration/migration_generator.rb index fca2a8fef4..c331c135e3 100644 --- a/railties/lib/rails/generators/rails/migration/migration_generator.rb +++ b/railties/lib/rails/generators/rails/migration/migration_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Generators class MigrationGenerator < NamedBase # :nodoc: diff --git a/railties/lib/rails/generators/rails/model/model_generator.rb b/railties/lib/rails/generators/rails/model/model_generator.rb index c32a8a079a..de4de2cae2 100644 --- a/railties/lib/rails/generators/rails/model/model_generator.rb +++ b/railties/lib/rails/generators/rails/model/model_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/model_helpers" module Rails diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index 49259f32c8..a83c911806 100644 --- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -1,4 +1,5 @@ -require "active_support/core_ext/hash/slice" +# frozen_string_literal: true + require "rails/generators/rails/app/app_generator" require "date" @@ -60,7 +61,12 @@ module Rails template "lib/%namespaced_name%.rb" template "lib/tasks/%namespaced_name%_tasks.rake" template "lib/%namespaced_name%/version.rb" - template "lib/%namespaced_name%/engine.rb" if engine? + + if engine? + template "lib/%namespaced_name%/engine.rb" + else + template "lib/%namespaced_name%/railtie.rb" + end end def config @@ -71,8 +77,8 @@ module Rails template "test/test_helper.rb" template "test/%namespaced_name%_test.rb" append_file "Rakefile", <<-EOF -#{rakefile_test_tasks} +#{rakefile_test_tasks} task default: :test EOF if engine? @@ -81,16 +87,18 @@ task default: :test end PASSTHROUGH_OPTIONS = [ - :skip_active_record, :skip_action_mailer, :skip_javascript, :skip_sprockets, :database, - :javascript, :quiet, :pretend, :force, :skip + :skip_active_record, :skip_active_storage, :skip_action_mailer, :skip_javascript, :skip_action_cable, :skip_sprockets, :database, + :javascript, :skip_yarn, :api, :quiet, :pretend, :skip ] def generate_test_dummy(force = false) - opts = (options || {}).slice(*PASSTHROUGH_OPTIONS) + opts = (options.dup || {}).keep_if { |k, _| PASSTHROUGH_OPTIONS.map(&:to_s).include?(k) } opts[:force] = force opts[:skip_bundle] = true - opts[:api] = options.api? opts[:skip_listen] = true + opts[:skip_git] = true + opts[:skip_turbolinks] = true + opts[:dummy_app] = true invoke Rails::Generators::AppGenerator, [ File.expand_path(dummy_path, destination_root) ], opts @@ -112,9 +120,7 @@ task default: :test def test_dummy_clean inside dummy_path do - remove_file ".gitignore" remove_file "db/seeds.rb" - remove_file "doc" remove_file "Gemfile" remove_file "lib/tasks" remove_file "public/robots.txt" @@ -161,7 +167,7 @@ task default: :test gemfile_in_app_path = File.join(rails_app_path, "Gemfile") if File.exist? gemfile_in_app_path - entry = "gem '#{name}', path: '#{relative_path}'" + entry = "\ngem '#{name}', path: '#{relative_path}'" append_file gemfile_in_app_path, entry end end @@ -257,6 +263,10 @@ task default: :test public_task :apply_rails_template def run_after_bundle_callbacks + unless @after_bundle_callbacks.empty? + ActiveSupport::Deprecation.warn("`after_bundle` is deprecated and will be removed in the next version of Rails. ") + end + @after_bundle_callbacks.each do |callback| callback.call end @@ -300,7 +310,7 @@ task default: :test end def engine? - full? || mountable? + full? || mountable? || options[:engine] end def full? @@ -432,7 +442,7 @@ end end def inside_application? - rails_app_path && app_path =~ /^#{rails_app_path}/ + rails_app_path && destination_root.start_with?(rails_app_path.to_s) end def relative_path diff --git a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt index d84d1aabdb..9a8c4bf098 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec +++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt @@ -1,4 +1,4 @@ -$:.push File.expand_path("../lib", __FILE__) +$:.push File.expand_path("lib", __dir__) # Maintain your gem's version: require "<%= namespaced_name %>/version" diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile.tt index 22a4548ff2..290259b4db 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile.tt @@ -1,4 +1,5 @@ source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } <% if options[:skip_gemspec] -%> <%= '# ' if options.dev? || options.edge? -%>gem 'rails', '<%= Array(rails_version_specifier).join("', '") %>' diff --git a/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE b/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt index ff2fb3ba4e..ff2fb3ba4e 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE +++ b/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt diff --git a/railties/lib/rails/generators/rails/plugin/templates/README.md b/railties/lib/rails/generators/rails/plugin/templates/README.md.tt index 9d2b74416e..1632409bea 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/README.md +++ b/railties/lib/rails/generators/rails/plugin/templates/README.md.tt @@ -25,4 +25,4 @@ $ gem install <%= name %> Contribution directions go here. ## License -The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). +The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt index 383d2fb2d1..f3efe21cf1 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/Rakefile +++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt @@ -13,17 +13,16 @@ RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_files.include('README.md') rdoc.rdoc_files.include('lib/**/*.rb') end - <% if engine? && !options[:skip_active_record] && with_dummy_app? -%> -APP_RAKEFILE = File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__) -load 'rails/tasks/engine.rake' -<% end %> +APP_RAKEFILE = File.expand_path("<%= dummy_path -%>/Rakefile", __dir__) +load 'rails/tasks/engine.rake' +<% end -%> <% if engine? -%> -load 'rails/tasks/statistics.rake' -<% end %> +load 'rails/tasks/statistics.rake' +<% end -%> <% unless options[:skip_gemspec] -%> require 'bundler/gem_tasks' -<% end %> +<% end -%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt index c03d9953d4..b3264509fc 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt @@ -1,13 +1,30 @@ # This command will automatically be run when you run "rails" with Rails gems # installed from the root of your application. -ENGINE_ROOT = File.expand_path('../..', __FILE__) -ENGINE_PATH = File.expand_path('../../lib/<%= namespaced_name -%>/engine', __FILE__) -APP_PATH = File.expand_path('../../<%= dummy_path -%>/config/application', __FILE__) +ENGINE_ROOT = File.expand_path('..', __dir__) +ENGINE_PATH = File.expand_path('../lib/<%= namespaced_name -%>/engine', __dir__) +<% if with_dummy_app? -%> +APP_PATH = File.expand_path('../<%= dummy_path -%>/config/application', __dir__) +<% end -%> # Set up gems listed in the Gemfile. -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) +<% if include_all_railties? -%> require 'rails/all' +<% else -%> +require "rails" +# Pick the frameworks you want: +require "active_model/railtie" +require "active_job/railtie" +<%= comment_if :skip_active_record %>require "active_record/railtie" +require "action_controller/railtie" +<%= comment_if :skip_action_mailer %>require "action_mailer/railtie" +require "action_view/railtie" +require "active_storage/engine" +<%= comment_if :skip_action_cable %>require "action_cable/engine" +<%= comment_if :skip_sprockets %>require "sprockets/railtie" +<%= comment_if :skip_test %>require "rails/test_unit/railtie" +<% end -%> require 'rails/engine/commands' diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt index c0fbb84a93..8e7d321626 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt @@ -1,10 +1,4 @@ -$: << File.expand_path(File.expand_path('../../test', __FILE__)) +$: << File.expand_path("../test", __dir__) -require 'bundler/setup' -require 'rails/test_unit/minitest_plugin' - -Rails::TestUnitReporter.executable = 'bin/test' - -Minitest.run_via[:rails] = true - -require "active_support/testing/autorun" +require "bundler/setup" +require "rails/plugin/test" diff --git a/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb.tt index 154452bfe5..154452bfe5 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb.tt diff --git a/railties/lib/rails/generators/rails/plugin/templates/gitignore b/railties/lib/rails/generators/rails/plugin/templates/gitignore deleted file mode 100644 index 54c78d7927..0000000000 --- a/railties/lib/rails/generators/rails/plugin/templates/gitignore +++ /dev/null @@ -1,9 +0,0 @@ -.bundle/ -log/*.log -pkg/ -<% unless options[:skip_test] && options[:dummy_path] == 'test/dummy' -%> -<%= dummy_path %>/db/*.sqlite3 -<%= dummy_path %>/db/*.sqlite3-journal -<%= dummy_path %>/log/*.log -<%= dummy_path %>/tmp/ -<% end -%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/gitignore.tt b/railties/lib/rails/generators/rails/plugin/templates/gitignore.tt new file mode 100644 index 0000000000..7a68da5c4b --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/gitignore.tt @@ -0,0 +1,18 @@ +.bundle/ +log/*.log +pkg/ +<% if with_dummy_app? -%> +<% if sqlite3? -%> +<%= dummy_path %>/db/*.sqlite3 +<%= dummy_path %>/db/*.sqlite3-journal +<% end -%> +<%= dummy_path %>/log/*.log +<% unless options[:skip_yarn] -%> +<%= dummy_path %>/node_modules/ +<%= dummy_path %>/yarn-error.log +<% end -%> +<% unless skip_active_storage? -%> +<%= dummy_path %>/storage/ +<% end -%> +<%= dummy_path %>/tmp/ +<% end -%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb.tt index 40b1c4cee7..3285055eb7 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb.tt @@ -1,5 +1,7 @@ <% if engine? -%> require "<%= namespaced_name %>/engine" - +<% else -%> +require "<%= namespaced_name %>/railtie" <% end -%> + <%= wrap_in_modules "# Your code goes here..." %> diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb.tt index 8938770fc4..8938770fc4 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb.tt diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb.tt new file mode 100644 index 0000000000..7bdf4ee5fb --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb.tt @@ -0,0 +1,5 @@ +<%= wrap_in_modules <<-rb.strip_heredoc + class Railtie < ::Rails::Railtie + end +rb +%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb.tt index b08f4ef9ae..b08f4ef9ae 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb.tt diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake.tt index 88a2c4120f..88a2c4120f 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake.tt diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb.tt index d03b1be878..06ffe2f1ed 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb.tt @@ -3,15 +3,18 @@ require_relative 'boot' <% if include_all_railties? -%> require 'rails/all' <% else -%> +require "rails" # Pick the frameworks you want: +require "active_model/railtie" +require "active_job/railtie" <%= comment_if :skip_active_record %>require "active_record/railtie" +<%= comment_if :skip_active_storage %>require "active_storage/engine" require "action_controller/railtie" -require "action_view/railtie" <%= comment_if :skip_action_mailer %>require "action_mailer/railtie" -require "active_job/railtie" +require "action_view/railtie" <%= comment_if :skip_action_cable %>require "action_cable/engine" -<%= comment_if :skip_test %>require "rails/test_unit/railtie" <%= comment_if :skip_sprockets %>require "sprockets/railtie" +<%= comment_if :skip_test %>require "rails/test_unit/railtie" <% end -%> Bundler.require(*Rails.groups) diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb.tt index c9aef85d40..c9aef85d40 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb.tt diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js b/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js.tt index 8d21b2b6fb..03937cf8ff 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js.tt @@ -1,4 +1,3 @@ - <% unless api? -%> //= link_tree ../images <% end -%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js b/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js.tt index 2f23844f5e..2f23844f5e 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js.tt diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js.tt index e54c6461cc..f3d80c87f5 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js.tt @@ -10,4 +10,7 @@ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details // about supported directives. // +<% unless skip_active_storage? -%> +//= require activestorage +<% end -%> //= require_tree . diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb.tt index 694510edc0..694510edc0 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb.tt diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb.tt index 1ee05d7871..1ee05d7871 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb.tt diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb.tt new file mode 100644 index 0000000000..d19212abd5 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb.tt @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb.tt index f5d1ec2046..29e59d8407 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb.tt @@ -5,4 +5,3 @@ class NavigationTest < ActionDispatch::IntegrationTest # assert true # end end - diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb.tt index e84e403018..755d19ef5d 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb.tt @@ -1,8 +1,11 @@ -require File.expand_path("../../<%= options[:dummy_path] -%>/config/environment.rb", __FILE__) +# Configure Rails Environment +ENV["RAILS_ENV"] = "test" + +require_relative "<%= File.join('..', options[:dummy_path], 'config/environment') -%>" <% unless options[:skip_active_record] -%> -ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../<%= options[:dummy_path] -%>/db/migrate", __FILE__)] +ActiveRecord::Migrator.migrations_paths = [File.expand_path("../<%= options[:dummy_path] -%>/db/migrate", __dir__)] <% if options[:mountable] -%> -ActiveRecord::Migrator.migrations_paths << File.expand_path('../../db/migrate', __FILE__) +ActiveRecord::Migrator.migrations_paths << File.expand_path('../db/migrate', __dir__) <% end -%> <% end -%> require "rails/test_help" @@ -12,13 +15,16 @@ require "rails/test_help" Minitest.backtrace_filter = Minitest::BacktraceFilter.new <% unless engine? -%> +require "rails/test_unit/reporter" Rails::TestUnitReporter.executable = 'bin/test' <% end -%> +<% unless options[:skip_active_record] -%> # Load fixtures from the engine if ActiveSupport::TestCase.respond_to?(:fixture_path=) - ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) + ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__) ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files" ActiveSupport::TestCase.fixtures :all end +<% end -%> diff --git a/railties/lib/rails/generators/rails/resource/USAGE b/railties/lib/rails/generators/rails/resource/USAGE index e359cd574f..66d0ee546a 100644 --- a/railties/lib/rails/generators/rails/resource/USAGE +++ b/railties/lib/rails/generators/rails/resource/USAGE @@ -1,6 +1,6 @@ Description: Stubs out a new resource including an empty model and controller suitable - for a restful, resource-oriented application. Pass the singular model name, + for a RESTful, resource-oriented application. Pass the singular model name, either CamelCased or under_scored, as the first argument, and an optional list of attribute pairs. diff --git a/railties/lib/rails/generators/rails/resource/resource_generator.rb b/railties/lib/rails/generators/rails/resource/resource_generator.rb index 5ac5164af0..3ba25ef0fe 100644 --- a/railties/lib/rails/generators/rails/resource/resource_generator.rb +++ b/railties/lib/rails/generators/rails/resource/resource_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/resource_helpers" require "rails/generators/rails/model/model_generator" diff --git a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb index 42705107ae..9a92991efe 100644 --- a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb +++ b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Generators class ResourceRouteGenerator < NamedBase # :nodoc: @@ -15,37 +17,32 @@ module Rails def add_resource_route return if options[:actions].present? - # iterates over all namespaces and opens up blocks - regular_class_path.each_with_index do |namespace, index| - write("namespace :#{namespace} do", index + 1) + depth = 0 + lines = [] + + # Create 'namespace' ladder + # namespace :foo do + # namespace :bar do + regular_class_path.each do |ns| + lines << indent("namespace :#{ns} do\n", depth * 2) + depth += 1 end # inserts the primary resource - write("resources :#{file_name.pluralize}", route_length + 1) + # Create route + # resources 'products' + lines << indent(%{resources :#{file_name.pluralize}\n}, depth * 2) - # ends blocks - regular_class_path.each_index do |index| - write("end", route_length - index) + # Create `end` ladder + # end + # end + until depth.zero? + depth -= 1 + lines << indent("end\n", depth * 2) end - # route prepends two spaces onto the front of the string that is passed, this corrects that. - # Also it adds a \n to the end of each line, as route already adds that - # we need to correct that too. - route route_string[2..-2] + route lines.join end - - private - def route_string - @route_string ||= "" - end - - def write(str, indent) - route_string << "#{" " * indent}#{str}\n" - end - - def route_length - regular_class_path.length - end end end end diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb index ed6bf7f7d7..8beb7416c0 100644 --- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/rails/resource/resource_generator" module Rails @@ -6,6 +8,7 @@ module Rails remove_hook_for :resource_controller remove_class_option :actions + class_option :api, type: :boolean class_option :stylesheets, type: :boolean, desc: "Generate Stylesheets" class_option :stylesheet_engine, desc: "Engine for Stylesheets" class_option :assets, type: :boolean diff --git a/railties/lib/rails/generators/rails/scaffold_controller/USAGE b/railties/lib/rails/generators/rails/scaffold_controller/USAGE index 8ba4c5ccbc..28f229510b 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/USAGE +++ b/railties/lib/rails/generators/rails/scaffold_controller/USAGE @@ -12,7 +12,7 @@ Description: Example: `rails generate scaffold_controller CreditCard` - Credit card controller with URLs like /credit_card/debit. + Credit card controller with URLs like /credit_cards. Controller: app/controllers/credit_cards_controller.rb Test: test/controllers/credit_cards_controller_test.rb Views: app/views/credit_cards/index.html.erb [...] diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb index cf97c22160..7030561a33 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/resource_helpers" module Rails diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb.tt index 400afec6dc..400afec6dc 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb.tt diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt index 42b9e34274..05f1c2b2d3 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt @@ -29,7 +29,7 @@ class <%= controller_class_name %>Controller < ApplicationController @<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %> if @<%= orm_instance.save %> - redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %> + redirect_to <%= redirect_resource_name %>, notice: <%= "'#{human_name} was successfully created.'" %> else render :new end @@ -38,7 +38,7 @@ class <%= controller_class_name %>Controller < ApplicationController # PATCH/PUT <%= route_url %>/1 def update if @<%= orm_instance.update("#{singular_table_name}_params") %> - redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %> + redirect_to <%= redirect_resource_name %>, notice: <%= "'#{human_name} was successfully updated.'" %> else render :edit end diff --git a/railties/lib/rails/generators/rails/system_test/USAGE b/railties/lib/rails/generators/rails/system_test/USAGE new file mode 100644 index 0000000000..f11a99e008 --- /dev/null +++ b/railties/lib/rails/generators/rails/system_test/USAGE @@ -0,0 +1,10 @@ +Description: + Stubs out a new system test. Pass the name of the test, either + CamelCased or under_scored, as an argument. + + This generator invokes the current system tool, which defaults to + TestUnit. + +Example: + `rails generate system_test GeneralStories` creates a GeneralStories + system test in test/system/general_stories_test.rb diff --git a/railties/lib/rails/generators/rails/system_test/system_test_generator.rb b/railties/lib/rails/generators/rails/system_test/system_test_generator.rb new file mode 100644 index 0000000000..7169e1bd3b --- /dev/null +++ b/railties/lib/rails/generators/rails/system_test/system_test_generator.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Rails + module Generators + class SystemTestGenerator < NamedBase # :nodoc: + hook_for :system_tests, as: :system + end + end +end diff --git a/railties/lib/rails/generators/rails/task/task_generator.rb b/railties/lib/rails/generators/rails/task/task_generator.rb index bb96bdf0dd..b7290a7447 100644 --- a/railties/lib/rails/generators/rails/task/task_generator.rb +++ b/railties/lib/rails/generators/rails/task/task_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Generators class TaskGenerator < NamedBase # :nodoc: diff --git a/railties/lib/rails/generators/rails/task/templates/task.rb b/railties/lib/rails/generators/rails/task/templates/task.rb.tt index 1e3ed5f158..1e3ed5f158 100644 --- a/railties/lib/rails/generators/rails/task/templates/task.rb +++ b/railties/lib/rails/generators/rails/task/templates/task.rb.tt diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb index e7cb722473..a146a8fda6 100644 --- a/railties/lib/rails/generators/resource_helpers.rb +++ b/railties/lib/rails/generators/resource_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/active_model" require "rails/generators/model_helpers" diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb index 3eec929aeb..5c71bf0be9 100644 --- a/railties/lib/rails/generators/test_case.rb +++ b/railties/lib/rails/generators/test_case.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators" require "rails/generators/testing/behaviour" require "rails/generators/testing/setup_and_teardown" @@ -14,7 +16,7 @@ module Rails # # class AppGeneratorTest < Rails::Generators::TestCase # tests AppGenerator - # destination File.expand_path("../tmp", File.dirname(__FILE__)) + # destination File.expand_path("../tmp", __dir__) # end # # If you want to ensure your destination root is clean before running each test, @@ -22,7 +24,7 @@ module Rails # # class AppGeneratorTest < Rails::Generators::TestCase # tests AppGenerator - # destination File.expand_path("../tmp", File.dirname(__FILE__)) + # destination File.expand_path("../tmp", __dir__) # setup :prepare_destination # end class TestCase < ActiveSupport::TestCase diff --git a/railties/lib/rails/generators/test_unit.rb b/railties/lib/rails/generators/test_unit.rb index 722efcf492..1005ac557c 100644 --- a/railties/lib/rails/generators/test_unit.rb +++ b/railties/lib/rails/generators/test_unit.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/named_base" module TestUnit # :nodoc: diff --git a/railties/lib/rails/generators/test_unit/controller/controller_generator.rb b/railties/lib/rails/generators/test_unit/controller/controller_generator.rb index ac528d94f1..1a9ac6bf2a 100644 --- a/railties/lib/rails/generators/test_unit/controller/controller_generator.rb +++ b/railties/lib/rails/generators/test_unit/controller/controller_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/test_unit" module TestUnit # :nodoc: diff --git a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb.tt index ff41fef9e9..ff41fef9e9 100644 --- a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb.tt diff --git a/railties/lib/rails/generators/test_unit/generator/generator_generator.rb b/railties/lib/rails/generators/test_unit/generator/generator_generator.rb index 6b6e094453..19be4f2f51 100644 --- a/railties/lib/rails/generators/test_unit/generator/generator_generator.rb +++ b/railties/lib/rails/generators/test_unit/generator/generator_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/test_unit" module TestUnit # :nodoc: diff --git a/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb b/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb.tt index a7f1fc4fba..a7f1fc4fba 100644 --- a/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb +++ b/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb.tt diff --git a/railties/lib/rails/generators/test_unit/helper/helper_generator.rb b/railties/lib/rails/generators/test_unit/helper/helper_generator.rb index 6674a15fa3..77308dcf7d 100644 --- a/railties/lib/rails/generators/test_unit/helper/helper_generator.rb +++ b/railties/lib/rails/generators/test_unit/helper/helper_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/test_unit" module TestUnit # :nodoc: diff --git a/railties/lib/rails/generators/test_unit/integration/integration_generator.rb b/railties/lib/rails/generators/test_unit/integration/integration_generator.rb index 9d065c1297..ae307c5cd9 100644 --- a/railties/lib/rails/generators/test_unit/integration/integration_generator.rb +++ b/railties/lib/rails/generators/test_unit/integration/integration_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/test_unit" module TestUnit # :nodoc: diff --git a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb.tt index dea7e22196..118e0f1271 100644 --- a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb +++ b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb.tt @@ -1,7 +1,9 @@ require 'test_helper' +<% module_namespacing do -%> class <%= class_name %>Test < ActionDispatch::IntegrationTest # test "the truth" do # assert true # end end +<% end -%> diff --git a/railties/lib/rails/generators/test_unit/job/job_generator.rb b/railties/lib/rails/generators/test_unit/job/job_generator.rb index 6975252b99..a99ce914c0 100644 --- a/railties/lib/rails/generators/test_unit/job/job_generator.rb +++ b/railties/lib/rails/generators/test_unit/job/job_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/test_unit" module TestUnit # :nodoc: @@ -6,7 +8,7 @@ module TestUnit # :nodoc: check_class_collision suffix: "JobTest" def create_test_file - template "unit_test.rb.erb", File.join("test/jobs", class_path, "#{file_name}_job_test.rb") + template "unit_test.rb", File.join("test/jobs", class_path, "#{file_name}_job_test.rb") end end end diff --git a/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb b/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.tt index f5351d0ec6..f5351d0ec6 100644 --- a/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb +++ b/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.tt diff --git a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb index 67bff8e4f9..610d47a729 100644 --- a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb +++ b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/test_unit" module TestUnit # :nodoc: diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb.tt index a2f2d30de5..a2f2d30de5 100644 --- a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb.tt diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb.tt index b063cbc47b..b063cbc47b 100644 --- a/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb +++ b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb.tt diff --git a/railties/lib/rails/generators/test_unit/model/model_generator.rb b/railties/lib/rails/generators/test_unit/model/model_generator.rb index 99495d5247..02d7502592 100644 --- a/railties/lib/rails/generators/test_unit/model/model_generator.rb +++ b/railties/lib/rails/generators/test_unit/model/model_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/test_unit" module TestUnit # :nodoc: diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml.tt index 0681780c97..0681780c97 100644 --- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml +++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml.tt diff --git a/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb b/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb.tt index c9bc7d5b90..c9bc7d5b90 100644 --- a/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb +++ b/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb.tt diff --git a/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb b/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb index f1c9b6da5b..0657bc2389 100644 --- a/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/test_unit" module TestUnit # :nodoc: diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb index 8840a86d0d..b6c13b41ae 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/test_unit" require "rails/generators/resource_helpers" @@ -11,18 +13,25 @@ module TestUnit # :nodoc: class_option :api, type: :boolean, desc: "Generates API functional tests" + class_option :system_tests, type: :string, + desc: "Skip system test files" + argument :attributes, type: :array, default: [], banner: "field:type field:type" def create_test_files template_file = options.api? ? "api_functional_test.rb" : "functional_test.rb" template template_file, File.join("test/controllers", controller_class_path, "#{controller_file_name}_controller_test.rb") + + unless options.api? || options[:system_tests].nil? + template "system_test.rb", File.join("test/system", class_path, "#{file_name.pluralize}_test.rb") + end end def fixture_name @fixture_name ||= if mountable_engine? - "%s_%s" % [namespaced_path, table_name] + (namespace_dirs + [table_name]).join("_") else table_name end @@ -30,16 +39,20 @@ module TestUnit # :nodoc: private + def attributes_string + attributes_hash.map { |k, v| "#{k}: #{v}" }.join(", ") + end + def attributes_hash - return if attributes_names.empty? + return {} if attributes_names.empty? attributes_names.map do |name| if %w(password password_confirmation).include?(name) && attributes.any?(&:password_digest?) - "#{name}: 'secret'" + ["#{name}", "'secret'"] else - "#{name}: @#{singular_table_name}.#{name}" + ["#{name}", "@#{singular_table_name}.#{name}"] end - end.sort.join(", ") + end.sort.to_h end end end diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb.tt index c469c188e6..f21861d8e6 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb.tt @@ -17,7 +17,7 @@ class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTe test "should create <%= singular_table_name %>" do assert_difference('<%= class_name %>.count') do - post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }, as: :json + post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_string} }" %> }, as: :json end assert_response 201 @@ -29,7 +29,7 @@ class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTe end test "should update <%= singular_table_name %>" do - patch <%= show_helper %>, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }, as: :json + patch <%= show_helper %>, params: { <%= "#{singular_table_name}: { #{attributes_string} }" %> }, as: :json assert_response 200 end diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb.tt index c33375b7b4..195d60be20 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb.tt @@ -22,7 +22,7 @@ class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTe test "should create <%= singular_table_name %>" do assert_difference('<%= class_name %>.count') do - post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> } + post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_string} }" %> } end assert_redirected_to <%= singular_table_name %>_url(<%= class_name %>.last) @@ -39,7 +39,7 @@ class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTe end test "should update <%= singular_table_name %>" do - patch <%= show_helper %>, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> } + patch <%= show_helper %>, params: { <%= "#{singular_table_name}: { #{attributes_string} }" %> } assert_redirected_to <%= singular_table_name %>_url(<%= "@#{singular_table_name}" %>) end diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt b/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt new file mode 100644 index 0000000000..f83f5a5c62 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt @@ -0,0 +1,49 @@ +require "application_system_test_case" + +<% module_namespacing do -%> +class <%= class_name.pluralize %>Test < ApplicationSystemTestCase + setup do + @<%= singular_table_name %> = <%= fixture_name %>(:one) + end + + test "visiting the index" do + visit <%= plural_table_name %>_url + assert_selector "h1", text: "<%= class_name.pluralize.titleize %>" + end + + test "creating a <%= human_name %>" do + visit <%= plural_table_name %>_url + click_on "New <%= class_name.titleize %>" + + <%- attributes_hash.each do |attr, value| -%> + fill_in "<%= attr.humanize.titleize %>", with: <%= value %> + <%- end -%> + click_on "Create <%= human_name %>" + + assert_text "<%= human_name %> was successfully created" + click_on "Back" + end + + test "updating a <%= human_name %>" do + visit <%= plural_table_name %>_url + click_on "Edit", match: :first + + <%- attributes_hash.each do |attr, value| -%> + fill_in "<%= attr.humanize.titleize %>", with: <%= value %> + <%- end -%> + click_on "Update <%= human_name %>" + + assert_text "<%= human_name %> was successfully updated" + click_on "Back" + end + + test "destroying a <%= human_name %>" do + visit <%= plural_table_name %>_url + page.accept_confirm do + click_on "Destroy", match: :first + end + + assert_text "<%= human_name %> was successfully destroyed" + end +end +<% end -%> diff --git a/railties/lib/rails/generators/test_unit/system/system_generator.rb b/railties/lib/rails/generators/test_unit/system/system_generator.rb new file mode 100644 index 0000000000..08504d4124 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/system/system_generator.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "rails/generators/test_unit" + +module TestUnit # :nodoc: + module Generators # :nodoc: + class SystemGenerator < Base # :nodoc: + check_class_collision suffix: "Test" + + def create_test_files + if !File.exist?(File.join("test/application_system_test_case.rb")) + template "application_system_test_case.rb", File.join("test", "application_system_test_case.rb") + end + + template "system_test.rb", File.join("test/system", class_path, "#{file_name.pluralize}_test.rb") + end + end + end +end diff --git a/railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb.tt b/railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb.tt new file mode 100644 index 0000000000..d19212abd5 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb.tt @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/railties/lib/rails/generators/test_unit/system/templates/system_test.rb.tt b/railties/lib/rails/generators/test_unit/system/templates/system_test.rb.tt new file mode 100644 index 0000000000..b5ce2ba5c8 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/system/templates/system_test.rb.tt @@ -0,0 +1,9 @@ +require "application_system_test_case" + +class <%= class_name.pluralize %>Test < ApplicationSystemTestCase + # test "visiting the index" do + # visit <%= plural_table_name %>_url + # + # assert_selector "h1", text: "<%= class_name %>" + # end +end diff --git a/railties/lib/rails/generators/testing/assertions.rb b/railties/lib/rails/generators/testing/assertions.rb index 1cabf4e28c..c4cff9090b 100644 --- a/railties/lib/rails/generators/testing/assertions.rb +++ b/railties/lib/rails/generators/testing/assertions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Generators module Testing @@ -113,7 +115,11 @@ module Rails # # assert_field_default_value :string, "MyString" def assert_field_default_value(attribute_type, value) - assert_equal(value, create_generated_attribute(attribute_type).default) + if value.nil? + assert_nil(create_generated_attribute(attribute_type).default) + else + assert_equal(value, create_generated_attribute(attribute_type).default) + end end end end diff --git a/railties/lib/rails/generators/testing/behaviour.rb b/railties/lib/rails/generators/testing/behaviour.rb index 64d641d096..6ab88bd59f 100644 --- a/railties/lib/rails/generators/testing/behaviour.rb +++ b/railties/lib/rails/generators/testing/behaviour.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/class/attribute" require "active_support/core_ext/module/delegation" require "active_support/core_ext/hash/reverse_merge" @@ -14,12 +16,12 @@ module Rails include ActiveSupport::Testing::Stream included do - class_attribute :destination_root, :current_path, :generator_class, :default_arguments - # Generators frequently change the current path using +FileUtils.cd+. # So we need to store the path at file load and revert back to it after each test. - self.current_path = File.expand_path(Dir.pwd) - self.default_arguments = [] + class_attribute :current_path, default: File.expand_path(Dir.pwd) + class_attribute :default_arguments, default: [] + class_attribute :destination_root + class_attribute :generator_class end module ClassMethods @@ -40,7 +42,7 @@ module Rails # Sets the destination of generator files: # - # destination File.expand_path("../tmp", File.dirname(__FILE__)) + # destination File.expand_path("../tmp", __dir__) def destination(path) self.destination_root = path end @@ -51,7 +53,7 @@ module Rails # # class AppGeneratorTest < Rails::Generators::TestCase # tests AppGenerator - # destination File.expand_path("../tmp", File.dirname(__FILE__)) + # destination File.expand_path("../tmp", __dir__) # setup :prepare_destination # # test "database.yml is not created when skipping Active Record" do diff --git a/railties/lib/rails/generators/testing/setup_and_teardown.rb b/railties/lib/rails/generators/testing/setup_and_teardown.rb index 73102a283f..4374aa5b8c 100644 --- a/railties/lib/rails/generators/testing/setup_and_teardown.rb +++ b/railties/lib/rails/generators/testing/setup_and_teardown.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Generators module Testing diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index fc064dac32..d8f361f524 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "cgi" module Rails @@ -5,8 +7,9 @@ module Rails # Rails::InfoController responses. These include the active Rails version, # Ruby version, Rack version, and so on. module Info - mattr_accessor :properties - class << (@@properties = []) + mattr_accessor :properties, default: [] + + class << @@properties def names map(&:first) end @@ -38,7 +41,7 @@ module Rails alias inspect to_s def to_html - "<table>".tap do |table| + "<table>".dup.tap do |table| properties.each do |(name, value)| table << %(<tr><td class="name">#{CGI.escapeHTML(name.to_s)}</td>) formatted_value = if value.kind_of?(Array) diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb index 8b553aea79..b4f4a5922a 100644 --- a/railties/lib/rails/info_controller.rb +++ b/railties/lib/rails/info_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/application_controller" require "action_dispatch/routing/inspector" diff --git a/railties/lib/rails/initializable.rb b/railties/lib/rails/initializable.rb index a2615d5efd..5410e17153 100644 --- a/railties/lib/rails/initializable.rb +++ b/railties/lib/rails/initializable.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "tsort" module Rails diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb index 000ce40fbc..66636e5d6b 100644 --- a/railties/lib/rails/mailers_controller.rb +++ b/railties/lib/rails/mailers_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/application_controller" class Rails::MailersController < Rails::ApplicationController # :nodoc: @@ -6,6 +8,8 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc: before_action :require_local!, unless: :show_previews? before_action :find_preview, only: :preview + helper_method :part_query + def index @previews = ActionMailer::Preview.all @page_title = "Mailer Previews" @@ -19,7 +23,7 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc: @email_action = File.basename(params[:path]) if @preview.email_exists?(@email_action) - @email = @preview.call(@email_action) + @email = @preview.call(@email_action, params) if params[:part] part_type = Mime::Type.lookup(params[:part]) @@ -76,4 +80,8 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc: @email end end + + def part_query(mime_type) + request.query_parameters.merge(part: mime_type).to_query + end end diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index af3be10a31..87222563fd 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Paths # This object is an extended hash that behaves as root of the <tt>Rails::Paths</tt> system. @@ -205,7 +207,14 @@ module Rails # Returns all expanded paths but only if they exist in the filesystem. def existent - expanded.select { |f| File.exist?(f) } + expanded.select do |f| + does_exist = File.exist?(f) + + if !does_exist && File.symlink?(f) + raise "File #{f.inspect} is a symlink that does not point to a valid file" + end + does_exist + end end def existent_directories diff --git a/railties/lib/rails/plugin/test.rb b/railties/lib/rails/plugin/test.rb new file mode 100644 index 0000000000..18b6fd1757 --- /dev/null +++ b/railties/lib/rails/plugin/test.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "rails/test_unit/runner" +require "rails/test_unit/reporter" + +Rails::TestUnitReporter.executable = "bin/test" + +Rails::TestUnit::Runner.parse_options(ARGV) +Rails::TestUnit::Runner.run(ARGV) diff --git a/railties/lib/rails/rack.rb b/railties/lib/rails/rack.rb index a4c4527a72..579fb25cc4 100644 --- a/railties/lib/rails/rack.rb +++ b/railties/lib/rails/rack.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails module Rack autoload :Logger, "rails/rack/logger" diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb index 853fc26051..ec5212ee76 100644 --- a/railties/lib/rails/rack/logger.rb +++ b/railties/lib/rails/rack/logger.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/time/conversions" require "active_support/core_ext/object/blank" require "active_support/log_subscriber" diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 474118c0a9..88dd932370 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/initializable" require "active_support/inflector" require "active_support/core_ext/module/introspection" @@ -103,6 +105,9 @@ module Rails # end # end # + # Since filenames on the load path are shared across gems, be sure that files you load + # through a railtie have unique names. + # # == Application and Engine # # An engine is nothing more than a railtie with some initializers already set. And since @@ -162,10 +167,6 @@ module Rails @instance ||= new end - def respond_to_missing?(*args) - instance.respond_to?(*args) || super - end - # Allows you to configure the railtie. This is the same method seen in # Railtie::Configurable, but this module is no longer required for all # subclasses of Railtie so we provide the class method here. @@ -178,6 +179,10 @@ module Rails ActiveSupport::Inflector.underscore(string).tr("/", "_") end + def respond_to_missing?(name, _) + instance.respond_to?(name) || super + end + # If the class method does not have a method, then send the method call # to the Railtie instance. def method_missing(name, *args, &block) diff --git a/railties/lib/rails/railtie/configurable.rb b/railties/lib/rails/railtie/configurable.rb index 2a8295426e..7f42fae10a 100644 --- a/railties/lib/rails/railtie/configurable.rb +++ b/railties/lib/rails/railtie/configurable.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/concern" module Rails diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb index aecc81c491..70274b948c 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/configuration" module Rails @@ -53,7 +55,7 @@ module Rails ActiveSupport.on_load(:before_configuration, yield: true, &block) end - # Third configurable block to run. Does not run if +config.cache_classes+ + # Third configurable block to run. Does not run if +config.eager_load+ # set to false. def before_eager_load(&block) ActiveSupport.on_load(:before_eager_load, yield: true, &block) diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb index b212835df7..76b6b80d28 100644 --- a/railties/lib/rails/ruby_version_check.rb +++ b/railties/lib/rails/ruby_version_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + if RUBY_VERSION < "2.2.2" && RUBY_ENGINE == "ruby" desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})" abort <<-end_message diff --git a/railties/lib/rails/secrets.rb b/railties/lib/rails/secrets.rb new file mode 100644 index 0000000000..30e3478c9b --- /dev/null +++ b/railties/lib/rails/secrets.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require "yaml" +require "active_support/message_encryptor" +require "active_support/core_ext/string/strip" + +module Rails + # Greatly inspired by Ara T. Howard's magnificent sekrets gem. 😘 + class Secrets # :nodoc: + class MissingKeyError < RuntimeError + def initialize + super(<<-end_of_message.squish) + Missing encryption key to decrypt secrets with. + Ask your team for your master key and put it in ENV["RAILS_MASTER_KEY"] + end_of_message + end + end + + @cipher = "aes-128-gcm" + @root = File # Wonky, but ensures `join` uses the current directory. + + class << self + attr_writer :root + + def parse(paths, env:) + paths.each_with_object(Hash.new) do |path, all_secrets| + require "erb" + + secrets = YAML.load(ERB.new(preprocess(path)).result) || {} + all_secrets.merge!(secrets["shared"].deep_symbolize_keys) if secrets["shared"] + all_secrets.merge!(secrets[env].deep_symbolize_keys) if secrets[env] + end + end + + def key + ENV["RAILS_MASTER_KEY"] || read_key_file || handle_missing_key + end + + def encrypt(data) + encryptor.encrypt_and_sign(data) + end + + def decrypt(data) + encryptor.decrypt_and_verify(data) + end + + def read + decrypt(IO.binread(path)) + end + + def write(contents) + IO.binwrite("#{path}.tmp", encrypt(contents)) + FileUtils.mv("#{path}.tmp", path) + end + + def read_for_editing(&block) + writing(read, &block) + end + + private + def handle_missing_key + raise MissingKeyError + end + + def read_key_file + if File.exist?(key_path) + IO.binread(key_path).strip + end + end + + def key_path + @root.join("config", "secrets.yml.key") + end + + def path + @root.join("config", "secrets.yml.enc").to_s + end + + def preprocess(path) + if path.end_with?(".enc") + decrypt(IO.binread(path)) + else + IO.read(path) + end + end + + def writing(contents) + tmp_file = "#{File.basename(path)}.#{Process.pid}" + tmp_path = File.join(Dir.tmpdir, tmp_file) + IO.binwrite(tmp_path, contents) + + yield tmp_path + + updated_contents = IO.binread(tmp_path) + + write(updated_contents) if updated_contents != contents + ensure + FileUtils.rm(tmp_path) if File.exist?(tmp_path) + end + + def encryptor + @encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: @cipher) + end + end + end +end diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb index e9088c44ce..1db6c98537 100644 --- a/railties/lib/rails/source_annotation_extractor.rb +++ b/railties/lib/rails/source_annotation_extractor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Implements the logic behind the rake tasks for annotations like # # rails notes @@ -19,7 +21,7 @@ class SourceAnnotationExtractor end # Registers additional directories to be included - # SourceAnnotationExtractor::Annotation.register_directories("spec","another") + # SourceAnnotationExtractor::Annotation.register_directories("spec", "another") def self.register_directories(*dirs) directories.push(*dirs) end @@ -45,7 +47,7 @@ class SourceAnnotationExtractor # If +options+ has a flag <tt>:tag</tt> the tag is shown as in the example above. # Otherwise the string contains just line and text. def to_s(options = {}) - s = "[#{line.to_s.rjust(options[:indent])}] " + s = "[#{line.to_s.rjust(options[:indent])}] ".dup s << "[#{tag}] " if options[:tag] s << text end diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb index c0a50e5bda..2f644a20c9 100644 --- a/railties/lib/rails/tasks.rb +++ b/railties/lib/rails/tasks.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rake" # Load Rails Rakefile extensions diff --git a/railties/lib/rails/tasks/annotations.rake b/railties/lib/rails/tasks/annotations.rake index 9a69eb9934..93bc66e2db 100644 --- a/railties/lib/rails/tasks/annotations.rake +++ b/railties/lib/rails/tasks/annotations.rake @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/source_annotation_extractor" desc "Enumerate all annotations (use notes:optimize, :fixme, :todo for focus)" diff --git a/railties/lib/rails/tasks/dev.rake b/railties/lib/rails/tasks/dev.rake index 334e968123..5aea6f7dc5 100644 --- a/railties/lib/rails/tasks/dev.rake +++ b/railties/lib/rails/tasks/dev.rake @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/dev_caching" namespace :dev do diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake index c92b42f6c1..8d77904210 100644 --- a/railties/lib/rails/tasks/engine.rake +++ b/railties/lib/rails/tasks/engine.rake @@ -1,6 +1,19 @@ +# frozen_string_literal: true + task "load_app" do namespace :app do load APP_RAKEFILE + + desc "Update some initially generated files" + task update: [ "update:bin" ] + + namespace :update do + require "rails/engine/updater" + # desc "Adds new executables to the engine bin/ directory" + task :bin do + Rails::Engine::Updater.run(:create_bin_files) + end + end end task environment: "app:environment" @@ -40,7 +53,7 @@ namespace :db do desc "Rolls the schema back to the previous version (specify steps w/ STEP=n)." app_task "rollback" - desc "Create a db/schema.rb file that can be portably used against any DB supported by Active Record" + desc "Create a db/schema.rb file that can be portably used against any database supported by Active Record" app_task "schema:dump" desc "Load a schema.rb file into the database" @@ -49,7 +62,7 @@ namespace :db do desc "Load the seed data from db/seeds.rb" app_task "seed" - desc "Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the db first)" + desc "Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the database first)" app_task "setup" desc "Dump the database structure to an SQL file" @@ -57,6 +70,9 @@ namespace :db do desc "Retrieves the current schema version number" app_task "version" + + # desc 'Load the test schema' + app_task "test:prepare" end def find_engine_path(path) diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index f5586b53f0..7dfcd14bd0 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -1,3 +1,5 @@ +# frozen_string_literal: true + namespace :app do desc "Update configs and some other initially generated files (or use just update:configs or update:bin)" task update: [ "update:configs", "update:bin", "update:upgrade_guide_info" ] @@ -9,14 +11,14 @@ namespace :app do template = File.expand_path(template) if template !~ %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} require "rails/generators" require "rails/generators/rails/app/app_generator" - generator = Rails::Generators::AppGenerator.new [Rails.root], {}, destination_root: Rails.root + generator = Rails::Generators::AppGenerator.new [Rails.root], {}, { destination_root: Rails.root } generator.apply template, verbose: false end namespace :templates do # desc "Copy all the templates from rails to the application directory for customization. Already existing local copies will be overwritten" task :copy do - generators_lib = File.expand_path("../../generators", __FILE__) + generators_lib = File.expand_path("../generators", __dir__) project_templates = "#{Rails.root}/lib/templates" default_templates = { "erb" => %w{controller mailer scaffold}, @@ -36,38 +38,21 @@ namespace :app do end namespace :update do - class RailsUpdate - def self.invoke_from_app_generator(method) - app_generator.send(method) - end - - def self.app_generator - @app_generator ||= begin - require "rails/generators" - require "rails/generators/rails/app/app_generator" - gen = Rails::Generators::AppGenerator.new ["rails"], - { api: !!Rails.application.config.api_only, update: true }, - destination_root: Rails.root - File.exist?(Rails.root.join("config", "application.rb")) ? - gen.send(:app_const) : gen.send(:valid_const?) - gen - end - end - end + require "rails/app_updater" # desc "Update config/boot.rb from your current rails install" task :configs do - RailsUpdate.invoke_from_app_generator :create_boot_file - RailsUpdate.invoke_from_app_generator :update_config_files + Rails::AppUpdater.invoke_from_app_generator :create_boot_file + Rails::AppUpdater.invoke_from_app_generator :update_config_files end # desc "Adds new executables to the application bin/ directory" task :bin do - RailsUpdate.invoke_from_app_generator :create_bin_files + Rails::AppUpdater.invoke_from_app_generator :update_bin_files end task :upgrade_guide_info do - RailsUpdate.invoke_from_app_generator :display_upgrade_guide_info + Rails::AppUpdater.invoke_from_app_generator :display_upgrade_guide_info end end end diff --git a/railties/lib/rails/tasks/initializers.rake b/railties/lib/rails/tasks/initializers.rake index 6522f2ae5a..ae85cb0f86 100644 --- a/railties/lib/rails/tasks/initializers.rake +++ b/railties/lib/rails/tasks/initializers.rake @@ -1,3 +1,5 @@ +# frozen_string_literal: true + desc "Print out all defined initializers in the order they are invoked by Rails." task initializers: :environment do Rails.application.initializers.tsort_each do |initializer| diff --git a/railties/lib/rails/tasks/log.rake b/railties/lib/rails/tasks/log.rake index ba796845d7..e219277d23 100644 --- a/railties/lib/rails/tasks/log.rake +++ b/railties/lib/rails/tasks/log.rake @@ -1,3 +1,5 @@ +# frozen_string_literal: true + namespace :log do ## diff --git a/railties/lib/rails/tasks/middleware.rake b/railties/lib/rails/tasks/middleware.rake index fd98be1ea9..3a7f86fdcb 100644 --- a/railties/lib/rails/tasks/middleware.rake +++ b/railties/lib/rails/tasks/middleware.rake @@ -1,3 +1,5 @@ +# frozen_string_literal: true + desc "Prints out your Rack middleware stack" task middleware: :environment do Rails.configuration.middleware.each do |middleware| diff --git a/railties/lib/rails/tasks/misc.rake b/railties/lib/rails/tasks/misc.rake index 29ea0ff804..e7786aa622 100644 --- a/railties/lib/rails/tasks/misc.rake +++ b/railties/lib/rails/tasks/misc.rake @@ -1,3 +1,5 @@ +# frozen_string_literal: true + desc "Generate a cryptographically secure secret key (this is typically used to generate a secret for cookie sessions)." task :secret do require "securerandom" diff --git a/railties/lib/rails/tasks/restart.rake b/railties/lib/rails/tasks/restart.rake index 03177d9954..074e3e89a1 100644 --- a/railties/lib/rails/tasks/restart.rake +++ b/railties/lib/rails/tasks/restart.rake @@ -1,8 +1,9 @@ +# frozen_string_literal: true + desc "Restart app by touching tmp/restart.txt" task :restart do verbose(false) do mkdir_p "tmp" touch "tmp/restart.txt" - rm_f "tmp/pids/server.pid" end end diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake index 215fb2ceb5..403286d280 100644 --- a/railties/lib/rails/tasks/routes.rake +++ b/railties/lib/rails/tasks/routes.rake @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "optparse" desc "Print out all defined routes in match order, with names. Target specific controller with -c option, or grep routes using -g option" diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake index ba1697186e..594db91eec 100644 --- a/railties/lib/rails/tasks/statistics.rake +++ b/railties/lib/rails/tasks/statistics.rake @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # While global constants are bad, many 3rd party tools depend on this one (e.g # rspec-rails & cucumber-rails). So a deprecation warning is needed if we want # to remove it. @@ -17,6 +19,7 @@ STATS_DIRECTORIES = [ %w(Mailer\ tests test/mailers), %w(Job\ tests test/jobs), %w(Integration\ tests test/integration), + %w(System\ tests test/system), ].collect do |name, dir| [ name, "#{File.dirname(Rake.application.rakefile_location)}/#{dir}" ] end.select { |name, dir| File.directory?(dir) } diff --git a/railties/lib/rails/tasks/tmp.rake b/railties/lib/rails/tasks/tmp.rake index d42a890cb6..7340b41ee4 100644 --- a/railties/lib/rails/tasks/tmp.rake +++ b/railties/lib/rails/tasks/tmp.rake @@ -1,6 +1,8 @@ +# frozen_string_literal: true + namespace :tmp do - desc "Clear cache and socket files from tmp/ (narrow w/ tmp:cache:clear, tmp:sockets:clear)" - task clear: ["tmp:cache:clear", "tmp:sockets:clear"] + desc "Clear cache, socket and screenshot files from tmp/ (narrow w/ tmp:cache:clear, tmp:sockets:clear, tmp:screenshots:clear)" + task clear: ["tmp:cache:clear", "tmp:sockets:clear", "tmp:screenshots:clear"] tmp_dirs = [ "tmp/cache", "tmp/sockets", @@ -32,4 +34,11 @@ namespace :tmp do rm Dir["tmp/pids/[^.]*"], verbose: false end end + + namespace :screenshots do + # desc "Clears all files in tmp/screenshots" + task :clear do + rm Dir["tmp/screenshots/[^.]*"], verbose: false + end + end end diff --git a/railties/lib/rails/tasks/yarn.rake b/railties/lib/rails/tasks/yarn.rake index 2097b7ffef..48da7ffc51 100644 --- a/railties/lib/rails/tasks/yarn.rake +++ b/railties/lib/rails/tasks/yarn.rake @@ -1,7 +1,9 @@ +# frozen_string_literal: true + namespace :yarn do desc "Install all JavaScript dependencies as specified via Yarn" task :install do - system('./bin/yarn install --no-progress') + system("./bin/yarn install --no-progress --production") end end diff --git a/railties/lib/rails/templates/rails/mailers/email.html.erb b/railties/lib/rails/templates/rails/mailers/email.html.erb index c63781ed0c..89c1129f90 100644 --- a/railties/lib/rails/templates/rails/mailers/email.html.erb +++ b/railties/lib/rails/templates/rails/mailers/email.html.erb @@ -98,8 +98,8 @@ <% if @email.multipart? %> <dd> <select onchange="formatChanged(this);"> - <option <%= request.format == Mime[:html] ? 'selected' : '' %> value="?part=text%2Fhtml">View as HTML email</option> - <option <%= request.format == Mime[:text] ? 'selected' : '' %> value="?part=text%2Fplain">View as plain-text email</option> + <option <%= request.format == Mime[:html] ? 'selected' : '' %> value="?<%= part_query('text/html') %>">View as HTML email</option> + <option <%= request.format == Mime[:text] ? 'selected' : '' %> value="?<%= part_query('text/plain') %>">View as plain-text email</option> </select> </dd> <% end %> @@ -107,7 +107,7 @@ </header> <% if @part && @part.mime_type %> - <iframe seamless name="messageBody" src="?part=<%= Rack::Utils.escape(@part.mime_type) %>"></iframe> + <iframe seamless name="messageBody" src="?<%= part_query(@part.mime_type) %>"></iframe> <% else %> <p> You are trying to preview an email that does not have any content. diff --git a/railties/lib/rails/templates/rails/welcome/index.html.erb b/railties/lib/rails/templates/rails/welcome/index.html.erb index 5cdb7e6a20..5a82bf913c 100644 --- a/railties/lib/rails/templates/rails/welcome/index.html.erb +++ b/railties/lib/rails/templates/rails/welcome/index.html.erb @@ -26,18 +26,28 @@ p { font-family: monospace; } .container { - width: 960px; + max-width: 960px; margin: 0 auto 40px; overflow: hidden; } - section { margin: 0 auto 2rem; padding: 1rem 0 0; - width: 700px; text-align: center; } + + @media only screen and (max-width: 500px) { + h1 { font-size: 2rem; } + + .version { font-size: 1.1rem; } + } + + .welcome { + width: 600px; + max-width: 100%; + height: auto; + } </style> </head> @@ -52,7 +62,7 @@ <h1>Yay! You’re on Rails!</h1> - <img alt="Welcome" width="600" height="350" src="data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEABALCwsMCxAMDBAXDw0PFxsUEBAUGx8XFxcXFx8eFxoaGhoXHh4jJSclIx4vLzMzLy9AQEBAQEBAQEBAQEBAQEABEQ8PERMRFRISFRQRFBEUGhQWFhQaJhoaHBoaJjAjHh4eHiMwKy4nJycuKzU1MDA1NUBAP0BAQEBAQEBAQEBAQP/CABEIArwEsAMBIgACEQEDEQH/xAAbAAEAAgMBAQAAAAAAAAAAAAAAAwQBAgUGB//aAAgBAQAAAAD6AAAAAAAAAAAMZAAAGMgMZABjIAAAAAAAAAAAAAAAAAAYyDGQAAADBkAxljIAADGQAAAAAAAAAAAANMSAAAAAAABjLGQAAAAAAAAAAAAADEUwDSGyGGQAAxkAAAAAAAAAxkGMjGQAAAAAAFS2Yy1xtVthSt7AAAABjIAAAAAAAAAAAAAAAAMZBDtJVtFS3TuYxtjSQAAAAAADGQAAAADGQAAAAAAACrY2BVtEM1S3BmbSvbAAMZAxkAAAAAAMMgAAMZAGM4yAAAAK02+DKragnKlqNLHHYi23DGQYyBjIAAAAAAAAAAAAAAA1znDEE0FnMcdiOJLJVtU7hWn2Yj1nq2NgwyGGQAAGMgAAYyAAABjIAAAAAhkVLdW1mtPvipPvBttLVtQywz1pt9a0qapayFWaSpbKlrIAAAAAAxljIAAMZAAAABptkxkxmncp3EWs6pmfeLWeraK9ivLvjEO28FipdVrEE+adwhmFS2a6SmMgAAAAAAAYyAAAACHWxhTuhUtVbO2KttTs521gs1bQV5JCrYzBJBbYya17QK89W2VbOYdpAAAAAYyAADGTGQAAADFS5rVW8jSCZJirbRyK1itaq2kciDeSvYqWs184sMY2Q5l1inKe82zRjercYyMZAYZxkAYyAAMZAAAAAabqtjaOGxtrtjNO4hkr2gp3KlupbQzVd95Kdynb1gTa7R7TKu+2c7otN5YpMw676zgGuwMZAAAAAYyAAACPbY1jk21r2mta1SvVLdO2hzHaMZq2qkmlmtaRxyb1bdOxAzY13qW8qk0mVaxUt5Yyp3KV2HMpXm2ijsgAAAAAGMgACPfIMV7JWlkilqZn3p3KlrKvvtmstwT0rla1U3zvBbK8mYLVfZCt5U7jFW2rT87pVLhXsQZmxmlZ22xTu4p3MgAAAAAAAAI4bQDGWKtuGWLXM9WxiFZ0gmhngs1Leu9WzVtw5itQT15MwWNoNoLgp28wpYYrdWTaU1zUuEUebCpZ2rSyAxkDGQAAAAAGMitYyBDNjNeSRirJNivaqW2KdyndryxSSK09a3HpOp3INsxR3a8le5VtYq63KtqHfePWLeaJvFY212o3dYpIbUUdkAAAAAAwyABhkBjIQS5qXIJ6k+d6V2vLtSzdo3oW0djEWYbete0qW4NWNblWetco3ocRXKdypbVLVSxvVsYiTV5tLFK5FLrWuAAAAAGMgAAMZAVbVWWXEFgQbRWNNZ49ZsVbeteard1hsVLlO1BpbQTbVLdffMOLVWzUu0rtaatcqW6dxUk2lqWdqdqCTend0jsGCtaAAAAADGQBjIMZIZiDaSpcU7bMMtS1XtU7lK5lTt51q2N1W3TuUrlWbbWWraqW6+6PW1Ut1LdO7UtVLdW3TuaQx3dK9uOPM2dNJqdwFSxuAAAAAADGcZCGZSt7K0shUtxxz6wZkxiOzXs1bUe2yHMdmrbKdyGancp3MVLlexUt1pcR626dynZisU7jTWSrbqWYbFO3mlcyFfeWtYgnqz53rWQAAAAAAAV584q2ypaj2QsSa62Kl2nagnrzRWMxwWqdjbdUtVbeNa1zRtruVLUeuUVuCWvaKdwR6zU7jFO7HFYrWjWtbr7RWa82mLDGQAADGQYzjIAADTdriGxrvWkkqXMVJNtorKpbpXMqlujdZVpYLUcelrancYyqWcokNnbJFLTuBpiCztX2mp26tjSXNOSSC2hxPrs0p3sgAAAAYywyACpayVJ9NpY0FuLSWLeC3VtVt8xzRzVbbOuc6Q62al2DO+spjNSxiQjxJko3qduKarag0l0sQzYhkgs07sDMNvMcNoR17cUwDGQAAAMMgVbTBVtRTYq2qtnNSTeWCWqtRS151a3ghzPjwfuN9kGYbmtVbKnL9BrmpMmr7ya74q2qlmKendabsZFS0xmpcrTb61reKtuOC1ppOAAGMgAGGQGM1LNW5FpYqzop6dyLOlgr42mzUtKtjcxjaPyElX3I03UZJ9o/M6+tId9s+W1irei6ke0dlpFYw12rWNJcVbG2aljEaWvazHnEFsY13AGGQMZAAAYyhxtvtVn2jlhbb5qW68kmIoLlO3X1s1bjME7m+Z6G3po5AxXlr+F7kPsAYpeUit2vUqk8irY2qz1reDarPWuaQWalurmaSOSKKzStbqVvaKYAAwyBgyMZBSuZVp9mKlxjFefeDbWapc1zpCmr2cbax2KVytP5T1fn/RVrQIWPJel53oI+VXudTLlxcH0/QQy17NO5Tt51zkU7mM07debeDM0GskVmnaq3KdvYGMgAAAADFW3Xnq2tYJorOKN6pb1g1ts15JKlpipdKtqtJDb8/wBjjehxFwqtnt2TTiUvU7eWvVNofU55knG9RhlptUtVrbys/a3m00lxHjeKzHFZigsQWqdvKnb1SaaTAAAAxkAxkj03k0hsRTVZpNa1mrca1rdS1kRS4yYzileR+fn6u3n7/H7XN6vScbm+kn18zNnGvWvcXpcT0TI1i0mSeZcn1fQyqWK9qncU7ulW1XtU7W+tO5HHZ1rzw2gAAAwyDDKCdXkkQpmKdzOkEmu8tSxiNPlDM08/0rkmWK1rzfb5db0m3Bp9nznq7PmsemU+Smq3te157r8zuM4ZIZalzzfd4fo4pWMo874Yp3alupYkxSu19pytrazHJpHOGMgGGQGMtdo5NNq1mCavPvGgtotJ68u9O7UsaTMM8fg35IOh2lO3ByaPQlq5nn5sXa836i287f6NLkZ5/puZYn6dHh707vpcsa78O1D2KdwjkQTsUrke9efbGyrNrmSpYkhnxHXuZAAAArSyVZZcRxWatqpbp3KlnSbXNabfKGbWtbcbqyV/OenkxV872OrptngVPUUbele55npcS96XGvmsda7wNc3+Tz/X2PLX99uXa9KHGi608UxBO03Vpt9NcS0rmallHjS1rWtbYrWgAAADFO5mpZaa52imgsVLlO41r2qN2rbyVrFbzno7Xm/Qbx8rl759LK0853IuR6WnQscyxS9HZQ+XsXJehxa+lX1NvyEG/Smg36lscWv6DavriSXbGTSDzXpLZjFW3kjkrLEGZ2mdgAAAGGcVLcFitLoZzDYrbTSMNW4cLbrcN6DyTs+W9Rb5PoXO4Hd6PjvVcbbXp2fN+kly85yt+ra48MnZ63Bodjl197fofP8Aoxxa/ek8tRvTKvfu5eS53Y9LmPXevazBNnWtPIwIJ8gAAABjWC0xVswWadrOa+8mcRTYy05Haqcx2/MWu/xK/Ugkl8/65S4/at8vgdOpU9TJ53me2n14F7zUt+POvqeRzYut5W736/f816XGXMpdDoeT7EcMfJ9jO087X9HaQb17eYU0GlmKZrUt7AAADGTDLTbWvvvKxUt51qXYJs6xTkMuvA3v1Ox570fO4u/o4+Dnqcij7CxyqUPd81Yt1NqnoOL6nOfP9WLm1p6lf2UulLyFn08cM3T836PjV7PQ5vHvcjq75npem3eektdMxBYYzitLJSt7Z1zW2sGMgAABVsYgtQZguawTwbya6TsqNzmzXsee7Xk7druee6OPPZ6XT0pw1JJ/SPM7VbGKtiWDp8rtdPHhree3yJoej2UXl6vVwg9Pv5rq17VK3y9+1xqOIqtmX2HJpW+xQ2u4zpvChs74R17mlezvjIAABjIxXswpqs1S+xFpLIVbSOPyvYr9HXp+e9BS5fRqwc6Wt0sNeP170vWxnzmApevzlpwoaN67V1k9C8zZ7Pm9ZIexp5z0XLvT9Tz/AEOT3KvX4vY4Xc53Ps9vTxdf1HXV7Gla3litmxV3nzrFOxkAAGMtNzCDeSpbawz1pt9mMipzrfn/AE9rzPf5/Q34EG/V04Vd1afruFF6U8xtXxNNi32ChDV5F7fq8uL1PM5FP21TSz41Y9Bf5Fvhepo1uF6rmT25OQin7mODxNez6MihtIJa1nSG3lhnXOQGMgDGNq6fMUFytNmtapXsNY56Xm9/Q7xX/PegzjPLxF1nkLHoOX32vE7vF6HH37MzyNmTMEWJfT5ef7XB5PX5fep1fXeUkk9JxuxTqcC/6fPl7fSt48v3uNiSKWG1nt8CHz8dr03aVllpDizVklq24Y7VbFrIDGQMZYpXc1pJa0kdmnb0is607w8bep1LHsduRdn5lzgpO9L4633uJ6Q4fcrcmGWXt48NdsZFD2G7zXb4GLuLnQ4fS8V7Pm+g4/YaePt+oo8Gb02MeX9TyOZtmzH0uj53q3NOZ5T3kyOpYnrt4bVdJDNHpa2AAAxlq2VZsSRxzZ2rbTwbyHk4ou7JT9Dy6XUn248Pf4fV810Y5O1I4utDm9KDo+hg8peBBf7mnmqXY07dHTpczbzvobVvj9lU8ta9V57Tq33C60/FqekeY9NtX5V68z5v0hp4n03TxW22is4gkhs7Y1xKYyMZADGRVmkjxHZpW9ZAcXgdvu8ypY6nA9Dlwt+15retvy/XcaejWsWqPsuff24nB6exXo5u+ml816Lbz/oePZ6FHWfz8fTqd+Wh5uf1nm5PQZ14fecPoXOPbuvN+g811LGvnvR3Xmoef7WGzBJMNc5rYs5GMgADGNcSa1569qPErYR8HqvH+txyr3C6nM9hjHl/UcL0fA1oWfT8XkXa2noed38nM8zWso6/fvdgK3O7XnupdrU+rxuJR3n2xcr+y83L25aPK723L62fO+iPMbdCTocCD0m3nOhxPUZzFZzpFY01la7AAAYyMa7qk0M+UWLGXLo1+vxL1LtNed1NvKXcT9u7xep52z24bWPNd2rzvScu1aEPkJYOvvv19zDi37nlet09OV2edSiqaYr9J1KPV8z6DpeZdahX9Dyu5H56r6Wtc4tzq485I7yrLpNIDFWxrLjIAADXNexFtDNBNHZxnkVre3Q85yvc7eZ03u9XzWIrtrsczo8Tu8PrTHF7Tz/oK9HrDHA73E7vF17eRjzvo8edn7mvC79WGeGfTh3fP9q/c4a3HJtWs628+Z335HQzD62zxKnQ6yPfOm+PI+hvVt5Y4J5QAxkAQboLUM9O1DOcSj2ub2LXmu7Ycbfrcipf5nc5NyzzO7xO5Th6RzbFrgd7HC74ee73I7XD17zGXO8n0bNqPu48/wCh05Nri+ozxO3pVjnzP4/q3azXbr8Dm3s70bGa3rvH9TuhUn4/Hk9bvpmtJOAAAARwyyKlvG1Cp0uJ3NYJuNLYj4fqYafX8707/Pi6EK1Jy+yY4nc5kt7g94OD3PNdGjF6Sral87zrMNP1vnrG8XqccbtcXtuF3XmvQcL0PP4tb09Pz/d7lLnU58FePHp/H+jrYtdbOM+bsbdrFbNjMO0gAABiGevneGzWnrx3vMbzc27c7EPkN56PSz6CvzOxy+7wadmH0Pj+9au5OL1ZOB2/M0rmNdKfpOPrPzZ9MYxSs9GD1fmMSQ49dQ7HH7OOF3see3p9nWWbh0fWUulw6VrQc3eXow8/afs+b9hLW5Nf02YJdmsctaaQAABFHZqzY0lrTaWKsFOv2daPQ81n2HIq9LzvW6nKtWKcHSp2PJWPYyI6VXjWqN3nz7xz61ptodJ5ttIZNsY3owyIut3OV3+V2s+b9JTpc2Kv3LXSh5Pc4VWG1toY5ncs1eNj0snI5fQ9T5uP0u2u+K+01bFnYAACKTMMmatiNLtW2nOJz5L/ABLkV/pec9RJwrVP0HEuc+D03k47umscMma221mKKSZrBJsxrrGuIM6a5zvHnaBfc6WTrUOrztavqvLWZu/pxYOt5XrZKl/fuefmmg2oXKHX4fpLlOxIjizPsAABjMek+Mwa62IJI7OUVSx5Ordq9dz/AFvjfSdXmz83ucDn9Pi3oLUtKLONpNwAAwyabVdsrEcWMZu9arzqdi3y7E9zrcHf0fPns86lQWdLR1eNz+/zdqtzfndfrqqxXzYyjglmAAAK208e9O3Xsmeb5vtwc67z7VjTe/0t3B6lJyOh1vJTSgMZAAAABWm71rhY2l59XSSK/wCq8vD6bm9rPC7EnN4dH1XM7nH5npPMXZ96M1vt7kGk7fGcMgDGSOQBBixptRvqdOzL5PtR6x05LPL9VfHP810upVzbi87aAAABHpFvZACh7XTn9anzezx7OeRTu9ejX9HzuvHyrXn/AFUfH4/e6XL6VfyXraXR81a9LrPFDNFtLXzajSAAAAgZ23y4nHvw0O1yPT8C1zJrPR66DlpIpenQ6fF1pU20cyKPoDSlJjbXaTMCzds1ubve5+qaRDr1e6MQWOdyZZJMw7aS9aTkdOhJf4Xcgzxe9nycfruFNFW9TnSFievtYzivYyYyAACCXXTE9bndvgXotfL+t5vofB29Pbcql2+ZLrB08XqVrcAOBBTiz0+xayxXqRdOTYOdx796Plx7dTo5AIORHvnpz8qbh9Cj6nid+jp0VWh2ccC/f4O3d1hklr7RZs5AAAAaQ2EcE0UHletJyrUGvrLWIJ/LdvfzvTxtfoOjz+nKyYyAxFtIYyAANcbZAAY51Pe9xZpelyYYu1T7p5n0ufN8T32YKnR10mgjllgxZikV7GQAACDEkM0NnSvzIYedavcn2kxwKfSitXeVPd5+nXk51/YAYyAAAADGQAItqfM6kVWv0dafUvPJegu8LHdzwu5nWKKeVjTEElgxkAABFtrHZRwWK9rEevkLF6btcyC30OdZ5st7mTdOtZyAAAAAABjIAAxUuVtqWvWh48fal8Xc79w876JDieHSzHXksGKsk4AADGu2ta1SuVrelZai4O+ZOnSnm58213n9XIYyYZBjIAAAAAMaSABVtYzjiaZxRm9HyOTa2t9oNNYd58xwT767gAAEE9ZYp2JdalyPeGxU8/2ZeffoXegj3KNCPGlWf0koAAAAADGQpcnn1/dZAAUZFnlZrTX+dD3tmsFmOvJvpJX3mrrGkdkyDGQMVLeNoNbGkFnDc51Lt7xa8uOt0Y9dqlPo3NKcLSlYsSTSIYrvVkyDGQAAGMjytWzmj67j3rc2zIxkBjWhwO50tsoN4N586Zgmrb2K2JZgBjIxk0rbWdMQW8NiPh36XYmxngc+WrvLrIADGmu+6re9MAAAAYcbmR40tkdbpUcxeylDHL5s8VbXTGmc7Y7vXKubGdY5tIMzb4xmBYAAAxVlkilp29ynzO7nG2M1PGWo9bu4AABR9N0gNdsZGMsZCPlU4qu1gAGs/pVDkwx1OhjGcgK9n026tYzFrHujxcxVsazAAAa1Llebdhlx9MdlzOZSlsdy1ny8E4AABh6sAABilyedZ2AACp1aeZ4QABrB6ufKGGbXSeSOBvHNMAAMZNK1ratPs08/1nCpYmnyq9/q48NfyAAAVuz2gAAHnqO24AACGTYAADFT096GavrPvFFvtDNMAAMZGKlurLLDPW4vfkcfzu9g3p9vu8PiXQAAGtG96rYDlcDo1s3OjeB53nzRV8W5QAAAAACp6Hpx5kginxFJNjOkgAADXWSvtNzK01W51EEuylDPcx4+XYAADTn+o6mWMhx+BcyitelBryq8vQ8xtKAAAAAAKnoeohzLSmnVJW0gAAAHNrxc2xzvV3xjI8ikAAAUfTdIAOPxNdLfa6YDXzdCeQAAAAAAFb1dhFlWt5q77TV7AAAAc/jcy9IxQ7m1jpbB5LcAAAq+zyAAAByuDZAAAAAAAYoesvR74VlnSTIAAAK3J7XjLw1xvFn0N1jPm624AANaPo+uxkAAAa+OsgAAAAAABB2uvWlgsw6WsgAABjHnPSReJ6QBXm63VzzfMXwAGsOk0l7t7AMZABFX3s+QsaZAAAAAAADTs9RVxPIAADDIOB1qPAs7gBV2s1JZQGlVYtSzXbmTGQwyAY4/PqTV5I9JLYAAAADFHE1oABZ6HR12AAAANeZQhI98gCPfIBE7t2UDGQAAr+bxuxkAMRTAAAArySUob0oAJJO/uAAAAObxdgAADGQYu9zYIOPa6iCbYACj5uwABVgztHvLaAAAAxruAYyZ6vQkAAAAOFTwAAAAFfW126Wca3+TU3odv0IAFPyl0I4Yy7BiWQY13BjIAAAABjPobIAAACDndLgwgAAAAMyR4GvO9b0AAKHmLgp9fq2HMp3YrllR50AAM7Sbb7VYwAAALHoNgAAAMRTacOoAAAAAAw6PaAAg8Zne5V9NeABitQ1333232k23zkMU+dTAAABd7mQAAABjjc8AAAAAAqd3sAAY15PN7XRYZABjIAAIufz5r80FevXwABnfrdAAAAAMczkgAAMZAADfo9TYAAAAAxkAABjIMQ1q9evqT9WWTbbIABjIGMgFDi4AAAAAG/Q6m5jIAYyAAAGMgAAADEVePobAAAMZAABV5BFGAAAAN+n0dxjIAMZAAAAwyAADGQAMZAMZDGQAADGTWKGGGKGLACa3iCHQN+n0dwBjIAAAMZAAAAAAAAAYyAAAAAGI4YYYs27coxpBBBL0dxpuANdgxkAAwyDGTDIBjJjIAAAAAAAAABjIYZAAAABjIAAAAxkAAAAAAAAAAMZAAxkYyDTcAAAAADGQABhkAAAAAAAAB//EABkBAQADAQEAAAAAAAAAAAAAAAABAgMEBf/aAAgBAhAAAAAAAAAAAAAAAAAAAAAAFbDn6AAAAAAAAAAADLUxp05ZdM1sAAAAAAAAAAOfazn1nDScOmZAAAAAAAAAM6xOfRzk20z1zo6JYzrlqAAAAAAAGG+OzldPP08Hbg6aZ6zzTTsznHppfPLpw1WAAAAAAI5+jg7zm2x6o5zpzx3xiadFN+PoW4+ucK9YiQAAABWxhvkty9fPG2WvJ20ozt18+mVMt+jm6+Tfmv1ZznOXVz9HL1gAAADm6RS/Nvz6ZzrgtG+Vdsps5+jDp04N75xn0xn04L5x0WAAAABnXakZxaaa15NOrI5+/CLZ4dNJ159HVx3rfO9Lb2znQAAAFbY7c/RzWvjpWlono5tcevltSLzFnLuNK10w3nLe2UXnn6a6AAABS+O2K3NbTn3Vjr5HRflXzvvPNtlh1xfn6mWPVy6Vno4dbU1tnuAAAM9Mlueejj6s+fcptTa05YzF+hlS91uSb03z0MLxF4vFJtqAAAOW0aTnNbbc0wr2UshaMsuqLVTCuHXy7UtLn6+ZTe+NenPRYAAGM1tGXXya52vGemPVgr1KaROWiYTCaVxnqObo5p2xUtfK/RTQAAFMtcrzScd8rt89Bja4TEwTCTmndFsUTXO1po6eLqtXQAGd8dsKzXSnTza3xp02QTjqBMTWYtETMGHRnpmzms2rnNtqNgAMZisVmrW9G+ekCJmJgSrMVtMJlAJVtnSbsNq7ZagAQ4+umekRoLwIlTSFL0umuddb1SCtLXmE897Z1dOFekADKKMuisaTpzdAIpFdLzGC2sxRnvMQtARFgnPDTflOiLgCKLYzne9NLphNZEJTGabzFK2vEkWgApN+bTOp0Vy6gBhtzXiu+DpnG15ictBMQmuRreCYi0TW1LwCYTW3JF6003mwAq5rw2uVkTndE5xOqma9rITDObJTCYmBNac+171hoAIztzNJ3hEomEkqzIqmSExNYi0xImiyYnCN1J5enQAUc+2O+kJgimgkhMSglWly0ExETJnSbaE46s89NJAGVaIx9AQSCLIjPLlr18u/NpovpbWSYrYRlFtCYtCuau9gFDnlbW8Uq1mCKYZ45KTWL1m0VtrWL9PZImLRGUa2IlPn9eO9b3AHLrXPqmGUNpQcPnAAtbMN/VuQmJmFbIi0Vtz02ta4AUwtuRNa3kSjPKzDl06tKZ30tTKcrdeggCYmISyrhrtpnoAOe/PPTaM9ZgEgAAABAJictYjnnbnnppoAM6Tha8bWBIAAAAAEAqxi1tMOkAynFZouImMq89e202SAAGHH3aSAQlzRac6xptoAji6crbzCXFyYxALRPsbAAjLkxwro29Wa5ctKUifR6ePs545+qY0uAjl2aTGfPzW9OniQAPR7wBlwckAOyOWAHo9+XJtNKb9AA566apyy0y6uHzgCev0rFOPHp7DDl28yoAAHd6FcabJ2ABHJ5/Z3WK+FAB0etYM89bjDy8gAADu9LLOI6FwAjyuVO/f0PEzBOmvX1gAjyOcAAAOz0MWuoAOXi54DbTmg26+nWQA5+OMmJMAA1vTIHq9UgAZ+JAAE+n0XjNOoHi4r6ZrpzoACYA9bqAAc/k1AAJg7/RA8rm7PRmEV4+W1rWtazj5wAt7GwADLyMwAAaevqBWwAAMufopjjjlG+19uoAAp5WdIAAnr79QAAAABGWwAAAZ555Z5z0655ZaehoAAAAAAAAAAAAAAAAAAAAP/xAAZAQEBAQEBAQAAAAAAAAAAAAAAAQIDBAX/2gAIAQMQAAAAAAAAAAAAAAAAAAAAABYN4AAAAAAAAAAANZNXGtc1gAAAAAAAAAA3mN5by1iAAAAAAAAADSzWNkZ1m6mDTOsgAAAAAABvGsujn059uWmLrM6TXKt87LvnvKAAAAAADpjrxXeN87oxd41ZrFx0ynTm25gAAAAFhrOk6c9s6z05W2znuXVzjfPpjpOdamue8dOYAAAA6cxZ0xvOmNpcaudSN43jPbE0uGue00xAAAAAaZrRNYvSc9G+O7nW+epncc+ubNZrE0yAAACzWd46TO82yzHTOufSVA3kZtzvDWJq5bwgAAAWazpOkm8K59GJ0SzDpjW+ab5ta59IY6yXM1gAAAazpNsdMXeLNYuY1omGrInRLjWTealipkAAAdJcqszuVeVlI1rnZUq659M2S659FxNXFiAAA0sa59M6kazvnpebWTWSost05m8dJnYlmUAABdZ1Fm8akzrI0kVLFQHSYqaWXUk059cRAAVrOqS8+mZq4iyzWbFJZWsUljeNZtolqZrIAGi0skrFiyksoiunTjkQLBZqyN4udZAAOvO2LkQXeL289nq83o88b9fo8HCkF6evl5ZY3Jpee2AANK1iozvMWV7d9fn8V+tOfzWu+vZ8hSVLNb5xpnWs56GEADUaWS5sg6c1m2LNeqTzR39HPyoqWKljq5blpi65gDeOkXG2J7ufmh7vERdZnb6NfP8APULCztxKhpmzpYswgArcXEN4qPX5KevpjxOntvm44A9eMc4AqWW7zM2sgBqdGWFS6yrNjeA0kAb6dPNOvOI7zklm2ZXTGQBW86zmwXt38UAAWOvbhGAddcpHr9mOHjhqTVkgA1ab4VNXNkWWBMzeNZt3OvTzwXpnMX6fTz+PAll0uIApsmZe3pnhhUyzEsWKlqXWwI19Dt8zlQnbnrKQAdM281e7W/llTPMACwNdFBB0xOt5LN3MkAC7mFO3Xy5WCCZtqKRLoKSoHXOZq7znNgA3NzB7fHksAAAAAFlQ9njXbG3PWQBqzclvOAAAAAAAssK1ZJrABqbRm5LCMtUAAAmdgAsdEWpnIB1xqYBnOQA60ACZmVXoTKQ6Xpz23iXMAOmWRnN24gB00AJnABuZAOmtdMzVxgAbuciJrPMAb2Gc60TOuQAADfRu5TIAE570HEAa6AShOcAAAN71awgAMYNbrlALdaAA55AAADfXTOQAZzkFuRd2gAmWUAAFsgOmgAE5AAHTRFAc8rYssgAADpoABnmAAA3sDGN7CZyqlYgAOtAATnAAAXpQAAAElkki23QABOaAAGt0AAAAAJQAAAkSRqyS7oAAAAAAAAAAAAAAAAAAAA//xAA/EAACAgEDAwMCBAUDAwIGAgMCAwEEBQAREgYTIRAUMSIyICMzQBUkMDRBQkNQFlFgUmElJjVTcHFigDZEY//aAAgBAQABDAD/APoHO+3j5/8AMmnK1kcRylDe8oWbbf8A4Mrvh4yURx/otjdZxqht7YfxTMR8/wD4GrRK7Dlf4/EZiAyZfC2C0IMPt1Q+kWL/ABZCZLtKjQjAjAx8f/gu3vNc4GN5QHbSAf51W8WLEfgVJTfb5nbVv+5r+hEIjJFO0CQmMEM7jqZiImZ8Qpy2xMrneP8A8A3mtUAEsttDPIYL8avF10fgBHF5u39Lvg0F6XP7ZmqsbV1+jv0j1jh2RM//AICvjyrTOklyUBfjD++Z6V7BOY0ZjYfXIR+UM+lgZNBiMbzXGRQEF4nT/wBFmqX9sHpYdKVSyI3lR9xYntt/5obAWPI52H58x6zMRG8+IEhMeQzExemYrFtpW/aDed5vMaoBJZcdDO4xPo8eaTHVIoKsHo+JlDIj5oFM1o386vxM1i20iZlC5md51HjIT6U/DrA/gvx/LToJ3AZ9RGB+PGrH6DNU/wC2X6ZCf5fSx4rEfxEQjHIpiIiYmN48x+EiEY3KYiP/ACa0qWpkR+5cSICM/JjyAhidtY6ZgWBPyY8wIZ1jp/LMJ+TATGQKNxiIGIGPEZCN6++lTusJ9aH0y1WnuhK5OY31EwYRP+KSjUmRONpu/wBseq/6C/Rni+qfRaODmN339K9nvycQMj6X/wC2LSf0g9D8gUb7ax0lKZ5bzFqdq7NVY2rr9Mj+hGo+I/FkC2r7aQHbSAejWMm4tSymI9bMwywpE+R/8eYwFjyOdhEhIYIZ3j+j+jf/AOw6X+VeMZ8R6WA5oMdUy5Vg9a/03HjrIRvXnStpUG3xqzHJDI1UnlWXPpY/u6/4KijX3JKNvS9/bFpX6QekxExtPwIiAwIxtF2dqx6k4RWgtt9JMmLEyHjOQ/t9Rvwjb5x5sOWEZSXrZa4WqWr0tpNwgI/HpVjnZe38EIn3MvmfH4GGxlwFAUwPqZisJMviuw2hzMeMf+KWl9xBx/miXKsPpMxEbz4jfeNxnVCSE2qP5/DfHYAbHyJQQwUfF6JHtviPIlBjBD8elCdhYv1idsjOmrFoSBfADAAIR8aONwKNUP7YfSz/AHVf8V2P5Y9J/RX+DIf2+h+2PS9/bFoJ/JEtY4dkkX4yniMlqhGyOU/P4rb2K4CuIkpnaN51SHlzfPz6u/PbCI+yIiI2j41ad2VSUfcgWCoYZPI//Eq35NhqPiNMCGAQT8Y8pgTSXyX5V8S/x+FoQxZBOqBySOE/Ll91RL320hXaUK9+Xqr6LzR/x6dlcM7vH6/RVkjsmmRiI1Q/SIfSz4s1/WXLFgqmfr9Ln9szSP0V+gOMrZq8cNZGfywH/Ppe/ti0wuNOZ1RHasHqRQIyRTtAGJjyCdx9LZca5zqsPBCx9DKAAin4qONy5M4iPwR+femf9F45FEjHysIBYhGpmIjefEacyVrmRjckq7QbT5IbAE6UjEzJFxGSnedFPubYjH6f/iDGgqIk52j0uDISFkI+oSgxgo+NCoBYTI+6+EyqGD9yzgwE49BYB78CgvSZiJiJnadL2XeYH+PR74QEHIzMAUGMHHwfi+H4hHjkS9KMzyePpcnixBepVxl4v3+rQMYVxgb/AJd3+2PSP0V+lOJJ7zn0u/U1Aamdo3n4W1bY5BPKMhP8vMau/TUmNIjZC41abKUyY/cgiNIEf3XS41j1UDhXCPW99XbTGrRyuuUjPGa8nKAk53Jy5aolxO0qCFrEI+PSy3spI/8ANNXbTEz9zEwxgHM+NOTDhgSmYj0tO7KZKPuqJ7St5++y7sqk/wDVVT2lfV9/9EWAUyIlEz/4A9woDmUTMAYsCDGdx9SYATEEURNlXdSQR9y5KQHlGxaIYIZEvimUxBoL7vQxgwIZ+KBT2iXPzqsPZuMVHgdXFGTUkMTPpdjgxLo9bg8qxxqoXKuE6seLaJ/Ez6b65/xqr9NmwGnWIUaw48pyEfkQX+YneInTWsG4tcT9HoCRBhsifN3+2PSP0F6mdo31jvKjL0P8zIDH+HTsk51jh2RvrITMisdZGf5fbQRxAY05IuDgW8REREREfGQmS7aY+YiBiIj49P1L8/8AZ6IeIjMzEfHiPW1YNRAAREzq3+a9SI+PWZiNt529f7i3/wB1amfc24j/AGtWm9pJFHgq/c7Idydz9bdiUjEB5YHPhHPaT1YdCVSc/NNMrXJn9/8AzDXLTEEydoiYmImPj8RCJRIlG8VwYkzVtur1vK7ieUfchkNUJx6FYWLRTO/LT/ybS3R9uQEpRyGZjSi5KAvSPyr8x/p1cGQNdmNRMTETHx6Xg5Vi/wC9c+aAKflrRUuWFvMRIsDf5GhOwmmfuuzxJB+jrPaaC+O/pfGSYmImY9LfhqD9A8ZA41YQTTUQzGrkb1j0md0hOm/36fwXv7Y9I/RXps7KOdY+I9vHoquYWGOIonVqdq7NVI2rL1c8urjq+LDlYCMyPqz68gsf8ekztG86oxJQx0/PpZYcGtK52I5kQIojeaQky1zPzOqv5r22P8eppYyyJlP5WrTu0qZj76yu0mBn7tQMRvtER6P3sWhRH2OaKVyZaApIIKY4zqZiImZnaK8TYeVkvt9B/m7HL/Z/5lyhcuQLVI54Sk/v/qI/IsGifs1YRDojzxLVpfcQQx5lJRYq7T81FsUmAZ86vjPbFo/cBwYCcfDAhiyCfikcyuVl9z4f31EveQ0yOSzHWPPkjj/mwHNJjqgfKvEf5FSxMjGNiyH6ETqJ3jfWQjYVs/z86YoGSMl86yEfkiXoXjIB6Wv7dmq07116atk3FMEdx0yz23gnjM+l3+2PVf8AQXpv6Z7/ABj/AO31JjBQMzHLVz+2PVf9BerU/wA3Xj8NeOdxzPW2zt1zn/NUOCAH/MztG86qmbBNhT9Koll5hl6UY5S1s6uM7aC2+ay+0gAnxPqJCW/GYnUztG8/Comy/vTH5XrYdCVSf+aSZBfcL73KNrlf/a9LZkZDVX9ywFYQA/GrjpEYUvyxCoSqAj5/5p6D7kPROzB3kYko2n8fZKLPeGfp1MxHzPpcXJLhgfqJbDViyPwV/wAmyxE/FtxJVzDbcZ3GJ0YwYEE/FA5lUrL50X5N0Sj7fWrELtuVHx86x/0k5f8AnV6N6xaVO6gnV0eVY9JLkkJ9b0b1i0E7gM6dPG8mfSxG6GRqnO9YPV8b3k+lz+2Zqv8AoL0/ylmsfP8ALRp8fz6Zj0vf2xaRGyVxpv1X1R6XHGs1CE7TopgYmZ+MfG6zOfnIScypYTqN4iIn5vTy7So+dXD4Vy/7pDtqAPSwztpM/wDNMOFcIn5fu22tMfbdaQLgAmYNQECxEp5FZcYSta/vtulSth++uqEqgP8ANphMKKyvuWArCAHxGpMYKBmYgtMn3VqFR5VprIUsjnVQnGrm2d9OaKVyc6pqKIJ7P1NPeCA5T5msk+Uvd5b/AOAcoidpmN/w2kS0IkZ2YElIDJRsWl/y1mVT4VqbG1mEFG0aubrNViNNULlyBfCglaxAp5TpZdm8YF4jV0OSJKPuUfcWJ6mN421RkgcxJz5d+XeWf+NL3XkDH/GrUb12RqpO9dc6dG6WRqiXKsPrajeuyNVp3QudXY2Yg/Q45AQ6x8719vV23vk/99XZ2rHrz7DxO0omSpRv5nH/ANvp/wDeon0yH9tOl+FhGl/XkDn0bEsyAD/jVouNdk6pDxrBqfzMh/7ad9V5I+luebkK9bk9xi60a+I/9qe7GtfPxKVk2GzG5TMREzPiK273FZKPpNQGQkUby5opXJzqoohGWs/UtNIeKVfqjEKVEEW8VhJzCtM1Zb2kkcfNJXbTEl9+nzNiwNePsiIiNo8QxHdaJHO69NZC1kc/FdRtP3L/AJ/58HNG1Km/bq6iWB3Q35obDlCcfOp32nbzNWxLoKDjiz8FtUsVMj99dveUJ/5ujMQDx+4Sghgh8xYX3EmH+ajO4gZn513l93s7/WxEMatu+06mImJifilMgTK8+nAOfPjHLIBJJg4+Un3FCerk9uylvo2N1nGqE71h1PmNtY+dhYufRMlF9ozO8HHICHVCd68RrI+FhOoneN9T8aqINAmJzE6IxGYgp2nVj6baC9L39seq+0117+Yb9KT2jWP/ALeNWY/mq86IhGNynaMjP5ERoY2GI1Wje6+fRf1ZE59L87Vp0oeKgHVTcrDz9F/XfZP+ND+ZkJn/AB6J/NuMb/pts7aCmPmsvtIAJ+dXDk5GsH3LAVhAD8ab/MWhV8rYYrCTLxFUJKZssj67CicHCC4wIwIwI+IZPu7MLH9LT2wlUnPzTTILkz/U/D8eZ0mwxzy4RHY/4OZmNto3/ZbxvMb+fUrAC6EzvBetpMtXuP6ldsOUJ/50P8rYkfhPo+Pb2BsD9kefMamYiYiZ9HWBSQCUTtpH5No0/AkMEMiXmKhSEnWKfOq26rLET8atbqtKf/p9bH5Npbv9PowIMCCfjHlMCaS+XIW4YE41EbRER8TG8TGsdP5Mj/nVPaH2I39I+nJT6UfHdXqwmHrkN9tKGVqESneUWAfBSMTEauzsSJj51d8EgvS7t7Y99V/0F6s/27NUI/lh1c8NrzqYgo2mN4yHmFD6UfLXlqZ2jfVCJM2un51kPKgHUzsMzEb6x4nEMIxkZ1QjlDHT86pfU17Pn0acLWRzqiEiiCn5b/MWxVH2aawVLky+KYSXKyf3ae2EqI51TVK1ci++yiXwI8th9HCRqIQnYqyIQqA+S1P81a2+U/isNJx+2T8qWKggB+P+SCyBOJPmC9LIElsWl7zAGJjBjO8elxMmuDD9RDYcoTj8Bfy1jl/taemHLkJ8aqukxlbPDZmBjeZ2hgCwJAvioZDJV2fddWRLhgffXdDlQcfNlPeVI/6qje6mN/vvDIwDx+RKDGCH4txKyCyPzEwUQUeYuR2zXZj5iYmN4+LCYeuQnxKgIFiBTymZiI3mdoiYKNxneLa+4goj5qt7qRL/ADeYxahIJmNRO8RMfHZYu73Fx+X61PosPV6LritxtiZ3076b6i9EzwvNCfSfMTrG/olHpYSxpq47cNX/AAC59L07Vi0iNkrjViYhDJnVD+2HV75T6WpgrSF+lDz3Z0zlKygfJUlmtHE42nV36nID1YJEBCM7TXV2VQvfeSmBGSn4x8T2SKfS+c8BUP3CMCMDHwiuS2tYUxPpennKkROhiBiBjxGmfzNuF/KvxW3SsIAPLK6YSqAj50y2IuFIRzKZiImZ8RWcbpYW35emvNx9ivpKQSHENS1cMhW/16X3mWCMtwX/AMhZQRyLVeG//v0mIKJGY3irMpYdYp3/AATHtX8o8J1YYalSYDylLIasWRpqxauQL4qtKYJDPDajjOCW2d22gICiyr7ihdpG0T9NSXQMraO02wKONhf3gYsCDH4VPt7ZKn7NM/lrMN/2mBDAIJ+KYOWEg2NoMIMCCfikc8SSf3OXDVkE6oskk8C+70eHNJhHzji3Rt6VvybTU/6bKpckgj5SJCoBL7vS1Yclocf09R9OQn0IoGJIp2hbAYMGE7jd+lyD9O0vud3j9ZtWExBlEToFguJgI4/gyH6QelhIuDiUzEDZVzhQbnNvxWZqpG1Zerv3oj0OtBWBfymJZJCBSMcioqNapk42n1b9WQVH4XiRJMQ8lWCQQAlG06j8+9//AA9VTFi4TY+zTmwpRMnVJcgrkX3zMDEzPiKzWvaTN5hPqRQIyRTtFYZc0rRx41afKxgF+W164pD/ALnc7xyKVxPFYCsIAfiy05KK6f1EpBIQA6tP7K948nVRK4ljPLfSSiPmYj/lJUBMFkx9XpyGCgZmOTFiwJAviqwhma7fvmImNp8wtQKHiEbRq2so42F/euFM2eMRy0H8rY7X+zqY38T8JmUWCRP6dxEtXuEfmVnQ5UF/qcoWrkC1TaRDKT/U9Hfk2wb8BqdkXt/gPWt+XZcn0uiQvU4YmZ/BeDlXLSD5pAtWZ4W0M/xMxETM+Igl2UlwncaS2LVIMjackM9oSjQTuMT6X4iTT+LIx+RE6GdxidEIlGxRvERERtEbRaAjQYjG5VAMK4Cfib0/mojTWgoeZztETvG8fgh4y+URHnQblkTn8TGCsJMvthwkmXD9uPiZWbJ+SIR25Tt6XH9pUxH31U9lMD/q0/8AmLIoj7NWzJhDVX9y1ioIAfiw+EjG0cjHeRiSjYtPKbDYrBP02HxXWILiJOWdtPcbtE1VkZTZb93pYfCQ/wC51kSuJM/LdEAFtyiJ9WMBYyZztArO4zus+lX/ACNiWIdFiJmVDMFEFE7x63EEWz1+GJbDlwcaspI4hi/DUPhwb/BacwlqIxHlKmC5cHHwn8h5In7NWEw5Uh/mq6TGVs8NYwVhJnOwvCLCYNU/VXdDlwXwTYmq7vj+lEwURMTvFkZS0bI/ETExEx5i93BAWgUxpwRZrfT81W91IlP3Xw3Rzj5Q3uqE/wDNxppVyD5AoIYKPiz+VZU//T68ogoH/Po0eajCPmkLATwZHGchGywPXgw/9sd4Bgz86uDBVjjVQuVcJ0bVrkYMtpciGyEzO34sj/b6V+kGgcBmSxn6vViVsISON5sIF4cJnbQxtER8/gqRytuZ6VfqtPOPjVd/fgp22jRub7wUhMcNWlG5cAExEXCFNXgPjVZfbQAz8iphWpaf2amssn98vM6nfaePzVrykZk/LGMFYEZfFMCmCsH9/wAedVv5h52C+3Vp/ZX9P6lVHZX58n7cfcd+Z3mf5t23+x6OcCQkz1XUZn7l33fga0FDyOdoBbLRw10bK+PEaKyAtFMbkX/FsY1VkZKd0+nx8+hDBDIlG8JKa7fbnP0arg1fMD8j6R/Ku2/2NWAJLPcq0BiwYMZ3GY38T8Kj21iU/wC3bTLF8g/UrOhyoP4nU1o9zD4LbRCJjIlG8VZlLSrHqf5a3v8A7ZgJjIFG41kEgSGS5CYQYSE/FMiHlXP7mBDAIC+FhCwEI8wn8m4avgSESGRKN4ERCOIxEQwBYEgcbiIiAwIxtF0YKse+qjYagZ/z6Obee9982fnV3C9C3DO4wYSXGCiS9LgSdc4GN5qyU1wk/mrsFp6/Qx5gQ6xxflEP+cjGwLLUTvG/rk8gvHVCsH9RdO5i7fsuVZISHRgDBkTjcYiBiBjxFTzZsTq2UjXOYnaaszNdcz86tXalMYOy2FxXsotLhtdkMDQMWyN1lBxqZ2iZ1jx/KJk/MztG+sdG4sZpvPtFwjcqSiWjYo2LVOJY5r59TWDNuY8v6FuZaxdYdRERERHxoFguNgiBiZiImZ8QjezYl8/Zq00t4Qr9RShUuAH40RCAyRTtCQKyz3DY+jUzAxMzO0A1tlsSqeCdOcCQky0pJvOH2PjToZKyhU7HWrwkfPln/Gw1i7crZO4GAmMgXwgirN9uydw09IuDjPiaziLdLfDdXO6HF65nZZwwBMfhqxaEgXxWYUTKG/qTETG0/AT7R8qKdk6tK7qp4/fXbDlQX+VVpU82CX0etpUkMMDwyeNyt4+6o6Wq+r7/AEtRKmBZGNCUEMFHmJIYmImYibwFEC8PuWwWAJj8esxExtPwICEcQiBjT3BXSx7PAYlyr+RtqfELitmH46pbxrZ2dVr2qmQx7ef5v4IUuGS2I+v0ozIvcufnIRvXmdK/SD/OpmIjefhNiu+JlDQbFpsZjJtM95x/SbE+9fzLZ/qmsCTMxmZlqgaEgfwIwIwMeI11bA+8qSzft2sfYxEjk8U2SqsyOXzcsXX2RXrMdTugOHM7LMBlbGQBwWRgXOiZUcR80x41wjVguKGTqiPGsPq4+2oz1RHjWHf518encDn2+Uc9U5kya6dEUCMkXiK7pcHOR4xqZgYmZnaKkSxjLJaY0FDyOdo9LbCMoqr+5axWEAPw5opXJlqsoo3c39W08lxAL8tUErXAkUlLJ903tDP5MRERtHiNOULlyspmIWsVhABG0OcCQ5nOkKNx+4fH4ZmIjefELatkTIFyj9nH7WCGd9pifWyiHL2+DrOlgyJ+GvSLg4l4lYkIQJlyLVtcxxsL+9TBasTH4mImNp8whEIghGZkdWESyIIJ4tQ+GjO8cTchbhgTjURERER8an+Ws7/CdXYZKeS5mJSyGKE/VVeFMMxmeLf5a1Dv9ufMeNVDaDjrumZIxgxkC8xTKR51zn6r4FEA8fkZFq4n5GmUqaysXnUzERvPiPRFnusNZDxL06gW1mJsQrfdKq9VeMyI766ixD32gt01k2GKAup6leI+nTHCswAvn8O0b77eb8TNYttV53QudZciHGWiGdprWn0z7tc+JVadn/pvjUCWO6fqkjPEmZgp/Hl8aGSqSqfDbV+0rGxiLAEtjW3gxwBCPb1Kl0krGhhVydnp2w2peLHvr7P9MiWyIHSRgVAMeuQPZMLj7gGBARj41fkpWKhjeVB21iHzpKiK2xxDIxoAEI4hG0GAmMgUbiACAwAxsOrplwFIfesIWAgPwcd+7AT5DTGQtZGXxSXMxNhnk9R/NWOXynVdZscdhsbTZaRFFZX6iVClcAPpLVwyFTP16fYBI7z5JVczPvWfJEUDEkU7REsuMnaZCuxoJDkc+BmCGCj4+PM6YZ3DlSp2StYLCACNo0gGjyJxbl+/87/+34mAVR3fCN1RMFETE7x6WQICiyqPqWYsCDHzHp8+J0veq+Vl+j6iYlvxmJ1ZWQFFlUfUtgsCDGd41ZsdiBmRkoasbCeOqjZYEif6mkbosmj/AEakwEoEpiC05QuXIFqk0pgkM++6ueMOD70s7qhZttq0MqaFoY319DV+PIoV2VQvflriPLltHJ6+6og1TZJp2L79HEJvCf8Ap1ScxsM5zvLAhizXPiKlVKL7cHf3ZXUoEqBS42CkgGdUWzdM93V6J4rkY3n8VoTKuYhG5VBIa4CcbFcTL6j0D92GxSskt1dpSpjWV8NjPH6fStUu27IN3lmnPShctccLW7qrFrmYDuN0fWFWJnhXYUU+pMbaMV8pSz1z6qnsDsWK8Pms+bfT0lWrxJY21brz7CouE3cXiFY+CYRS616WK4vgYkpjURtG0atHPdQsZ2nTvzbq17/ToGm29MAU9v0IwCORlAxEwUQQzvH4Ffn3CZ/p1AjEyURG+nz7h41x+yIiI2jxFtpREJX+olQqXAD6WX9oYgY5Mro7QzJTyZqzYhAePJ1UEO7m+WvcKVyRfNdBSXff5ZMwMSUztH13T2jca88EqnaNgQs7B99/2+Ij/wBjMrZ9pXhS1isYAI2H/iSGCiRKN4TM12+3Ofy9DYmLBJZHH0j+Vdt/sabDJWUL8HTsk2JWz9R6RcuQn5qOIolLP1NfPjUjNJ0GO8piYmN48wUTTbzj9CJiYiYneGLFgSBfFPurkkHE7WImu4bIx9MTExEx5jaN9/8AOriYYvnHg0GTEiRRsWnqaNkHpHf1MYMZAvMUmEBnWLz+Bcdu4wf8avL5oko+6u2GqE/8oWarbYgZ7eup6ZQKsnXjZtC2N2mqyPjV+RpdSVbO/EPSzar1Vy2wyFha6vSO41Ekcq6izbpJykixVbq4eXG7XleqmQp3R5Vmif4GsBKjac7B05K1UbOQeUBEBb6ku85mV49awWArWMCGrQzmM+VN7JXXp/w077MdWogSrIRl7x0KC1Jq38Lhjcuus5rXMblbmJsxj8nvCYmJjePMa6hZ28RYnfacGvt4mqO204oBu9RWrRTyH8MrAjhkx9eqMcza+flhcFkWseviqWT8+nUK22s1XpmyQVhnPxuSZh7BSYXMhUohB2WQGqtpFtMPrnzXp0GSihfg6yYSqB/1TMDEzM7QJCUQQzExZdCVSX+qomVK3L9RzRUuTL4qqKd7DfLNOcKVyZarJKSmw79TT3gkORfNZJsObL4+pzgSHM50hRuP3D49DIrjO0vwkREBgRjYdTMDEzM7RJMulIh9FdawWMAEbQ5y0jyOdJZ3Vwe0j6NaChkjnaK7jdEmQcQ/4Z6RcuQnxNZxFupnhrFLbx5xv6NWLQkC+KzCEprt+/TKwG0WxuJ6tqIZiyr70tFy4MfQhEhkSjeFkVVkJOd0kImMiUbikyrN9uydw9DAWBIFG41DJZFWP7tNI67xZJTKvwpeDoKR9No+fxTETG0/FOWLexHGe36PSD0sSf2dOmdLIWsU2d9dWII6K3jHmhZG3TTYHVmymognvLiunVs563767EjSy+DVbVDK0Qm1XB7YK9RHs5PH2aObrwb0gbsj0+6kRX8WwhnC5YMnXkpiAf6dUWpTj4QE/Xl+4unQwqI3bSqhTqqrB8aMpECKPM4CJdFzKt/MtdKzvStnvPd6SExr2jMZ36ZkLVi5cbG9nq+rutNsY84e17vGodP3a6rKBxcRPzU+iknfxHSUCRXHbefxFEyMxHzTUak8TjYr58USP+UB20gGu8HehPnlrqqP52jI7CfU0WKt6rkUxrILaLJ9+RtyGDolRxwKZ4b63ZNkhXCNLAVhAD8Ss32tziYVMxEbz4gd7j+c/oamYiJmfEJGbTZez9PTGCsJMp2hAFaZ7hsfQ1oKCTP4Uo7Bw98fTpjDtMlKZ2UtYLCACNo0RCAyRTsP13T/AMjXEREYEY2ixYFMbfcaa5GXesfUemkYrkgHkSqpEXdszzNrlpHkc7arssNZLCjin/g0WIaRhMcS9LCSmYcr9VLRcEGPrYT3B5B4bXfDh8+Gesfylnb4T6NULQkC+ENNZ+2d82Uw5Uj/AKqZOJcw4ZiSsALxTPidXVzHGwH3rMWAJj8OVDVkE6qMk18C8Hac1MgQxEriYKIKPMei661MJg77+jWglRuZPEF5BpDOcuMMK+Pt5BolZvgutXGzWPwDgKfwVZJbWIOeU66kAqd2rlExMSyE5CgUD9Suk3TNNtY/vyhzmMurHIKZQAAsBWEcQ1aGKvUtZq5gIa+pj+oQsIcPtpz+Hidpsjq5fRTy03MRIkNC6q/VCyrxGnMjIdQz3J/lMIs8nlHZZ07D6ZW7NCiyzAdyelqTorusmXFKukhHfnbPYenGqEuxkHgWBstqZKxjrccrHVA74g53210ocli9p9Or95rVgjV0uzjnlvxnpEIjHsP/AD+D5+PwOrrdxkt4mZiImZ+Kc91rbE+nVcmp9KzAcgquG5UTYINoxagu569aaEGOnOFXHeJmfwvVLVyvlx0tYqCAH49IiIiIiNoMhAZIp2EIO6fM9xRERERERtEJY90m+Nl6e0nFNdGlKBIQAR49IW62yZbEgqIgYgRjaH2ZguymObUVoXMsOebfV1yBLtpjuNVUIi7tmeZ+kOGXSmImZ0xgLGTOdorPJ/IpHiP7zkMFAzMctdlcN7sR9ejate3MoH0aM12d9cfQJQQwQzvFhjUlDY+pIkJjBDO8WVksosq+5bBaEGHxqHrlsp3+t6RcuQLVRxxM12+GelhEODj8FWfJxK2eG6uI7i+Y/qIOWJEyjaZiJiYnzFaZQ86xT9OrESh0WRj6CgWrmPkay2KVAMmJn8PVDpViiGJ2k5vZga9ekgoQvpa2/id63MlPRyNp2snuzDZvG/nUrEuHD9QrulFa1EJteluYVYS2PHpeqLu1WVmfHTNiVi7GPni8rg4XL344fT0vR7NQrjInvW8vbJhV8ZVN5ljM/agjuXorgnpei4YadplgTwOCppl1gJ7asJhSEWrriQ9PCorGRqGsYLAWGY7IuxdkoEcxkBx9I277NeptHHLqjEzcxtIaNJVeNuXp1ZYkhr0F7yxrnY1FGnXV3CMwWBMOeIYvKDkgawFEtd/uL6qqkERM+9s5PI3a/Du1Rxmcq0GPgzQnDZCcjSF5DxZ1dMQqp52nM5Gq7DPKs8WT08mU4lETEROsjmKeOkRfMyxeWzmWaa6EClf/AE7mHD/MXvOSxl/EJWxdozXieqYnZGR+YmJjePMekxExtPwpS1RxXG0a6sYQ40Qgd4Q8KeCW+fEdMVu1jYcX36ifcXN4ndfqbVgQgU7T6FZjvikB5F6fHmdGRXG9sJ2QACAwIxsNh/aGIGOTFQyFxDZ5G5xuP26J0pIJDgEaIhAZIp2gGAweQTyj0ZYNpdmt8oQCR2HyVmzCo4D9TawuFf5xbkZisZI52iTfb+lX5aU11pjYI8kQjEkU7Qm0DjIQidtbRp7wSHIvlaG2C7tnwERERtEbR+5sgYzFhU/WpotXBj8atoOSh6p/Mrvhy+UeJ9HoB4cS+ajCGZrN8HMRMTExvCymq3sn+lMRMbTG8RM02cZ819FE02cxjdAlBRBDO8XFkBjZXG5LYLQgx+CrATxfvMFozEBkjnYYmJiJid4tJKJiwrwxDhcuDH8DqwtMDmZGdGImMgXmKhysyqs+70zPUAY5goUENclsOStseI1duJo1jsun6atS11Db97b/AC6aUJrrhSQha/S9kqdBcMsHtrJroZQTuYuZ91gMr7+twbMRZ0a1s25jBeuaXFDI1cuEbDaIeoMuCExtUERAYAY2HG5UL9i0sIiAsJixXYgp4xRqDSqLqiUlDXY3Jk3GsLkaSXIyC4kQYS8V1PLDLgjK4qrl0+4rGHuF25/iCBz3OIornMZtmR3mavoxgqWbC+3Fc8vmGZJsTCbnVDbE+2qDFbWJQj2JV5tReirVr00wiuHBfUHukZercUmWxXq5lIy5FcVXLxZXH1WzetA9fTYdjDgbPojL2v4zYFVaNq0UZv24r45W8Ymg6hVhLXy6bVlNRB2HlxXWGeocyTmjMVbGCxhO2oWYq24/6npztMLvLPI/xGu7HvrMrWcRi6+VxjQmIC109fsV7Z4m6U/i6qnbEzG0TrKskOmV8dUFdijXVPyUTIzETtNZHYXx33L1Gv8Any8y5T6CsBMjiNi9LYvZspcfQpYqCAGPGu0Hc7u312AYapFZcSrIhC+P+rXFt0vqiVoABAYEI2EigYkinaCNtuZFf0IWoFDxCNoZz4T29pNFaFzJnPNrrYLLthHcYFU2FDLU8piIiNo8Q1q1DyOdoFbbc83bgkREI4jERHomqUsl1ieR/vGQVRndCN0xMFETHxpwlXb7hcbgJCYwQzvBlIhJDHKa7xeHKPE2Ud2IIJ2bXf3RmCjZjlC5cgWqziifbu8MYsWhIHG8V2Gs/bOnyQiYyJRvCSKq327P05jfxOv7N3j9DW8fH+WLFoSBxvFcXIZ2CiSVpm9J/MfKRKCGCj4cxlezBlMyn5/BZrmwwYqeJ6yNwaVNtgp83cbNfBnbtbnexq+1j6y5+dZ/e7lamM5SK0pWhQpUMCuZiI3mdoizWk5XDQljuqscs+ChY7WAVTu5K0dsBI7wV8d1BWKltGszXbiMgGTpxsurYXarrsL34ejXKSEsccLC5btZ9vsqAyNPH49GPrwhMayVW3aIVi6E0ujw82mxH0zMRG8ztGTDIkoDxxiLatWMe1mXy7xizhshayHuLDBgKtZQZjqB1iRg6mZsWcKpI44FqrLx+XzoLsWngNehRVQqjWVvI+lpROquUP3YPIOp1bdRa5O3SyWGyEEWTSlVpJdN445tpYsSqZuL9kV065mixZTVSTnnALNubyZTZoTNRCsFbafvM5Y/Ku3jype2qxKsc6DaxeLoR9eOxyMdXhKY+rXU2WG0yKdct0pjJY7EzaW0FouYhVTEV8gDC9wfVKV0FGMQ25iOoYyD/auV229KjwbfXrqejsscmjcX4y8F+muwP3fg6qmIxW066gKYwdQNttDGwxHpavVKfD3LYVosnjhjebStoy2MmdotK0F6mY8geshEwL7SifxMatQ8mmIDbyNSnW9y04leQz+WaAuUM1KuLOyePQdvy/1//WjMVjJnO0QLLkwRxIIEYGIEY2j1WhSpmQHadNtfV2kR3GLq/V3Xz3GasWe1MLAeba63xMm49y9GMBY8jKBgDhgQY77fvZiJiYnzATNNnAv0NRWkLPeXPEdWElBRYT+olwOCDHVlHdGCDw2tYhw7F4ZaR3g+nwys/vB58MekXrkJ+abjmSQ2J5sWLAkC+KzSUc1nT5tpOZh6pnmlsOULI9SESjYoiY01QNCQON4WsVhADvt+Gwz+K5tdQZiavVZ7Y0VxG5LgUVxgvpFnVJkTJp1Ccnp2PfWbWUeO7dQl+dy9lD7BArMYCvjqfuUOOTw1ZFfFpIYBTiqPZlvaudEuxfT9egcuYXuH5SsNqg9Jba6SuSaGUiid/TMYgMosBlkqPI4dmLpSZ3SmOn1Wl40JtGRmUchkfjVG1YwGRZXeO6rNetkakqOYNP8ABs9XjtVL26VdNOe4XZS1Nic5k1gqMRjo3dh8dGOpCmfLbVVNtB13RyWluR6cbCrA96hWt17aobXOGB65rC2gtfxPG/qTkqNiZ/iNKJcmz02ncprPaVXJX2wBgpWNx9jK0rQSCBh+mfxgSkk3CKGBlLhgu6czXt2QqKhSYjudP4oqSJe/za1mso5rYxWNnm/N0E4zGV6wbS1lSg3CITZOFJX09k2yutZsxNGuuhj+oDCxErQy461npu4xM2I6Snmy6yRgZcoXKNRxuPSr+2VnHH934OqwksVvGs1Pe6crNjVdkNQtkfGs7WPIZ1NMC20GORQbK8wlsBFLpNsDA2ZCR6dwbo3TbKYZ0nVmYmtaMJtLy+GmHhdh4YnJRkqcPiIBkb7efn0vSebyFkROYRj4W6RK8c+xx1BuYs/xO8PGr6NctI8jnVc3M3NgwAMYChkznaAWdo4a6OKvRltYHCxiTP0NgLHkc8YknWvAbrQpK0jxCNvXaN9/86iYmN4neNBVjl3HT3Gfv2LFgSBxvFcyWc1mzvPq4Crn7hX2iQmMEM7wysUvFyy4TqwMobFkPgSgxgh8xtG+/wDnVuv3Q5DH5leWkqJbGxwIjGwxERot+M7fNVptVyZGxamYj5nb1a1aVk1pQC6fU1S1cirCyCNZS0dOg6yERJ9JqkabXGEwedmHZLG1N99NDuKNe+2sS5tH3mIORXZxWVtotDi8mvg2JiZmInzkunzba99j2+3sDg8tfYM5azunIYapfQtJ7r1S6dx9FvuJkmHjstN+9aSuBmvlH8FQoZ+rp4+xnGqLx65DK08cG7z3ZVrPy1kMnk5FdRGToWLE1UOFjdZ2pj7FeJuMhJ1r2YxU8BjvVk9S23R9GOYU5S71ENQWWYGqllRlAfdVWF38VkQyNQXxsLMg5qKL3JjdkdVE5Eqt04bCFXm2GNxaWqGlFmKiotzE2NMYCgJjCgQ97ks2+V0imrRv4tZV4kY7hvrW6yycLRmKaItLGzaI3MiIiNo8Q+wquME2doh6pV3oKJXgKRXrx5J4z2tdQZhuOWC0D+b0/fw9NUk9kjb6kv0L8KOqyTYC0ZzCAoSiGPGxWMkWpYD62VB1kZzHCymnarPxlo8fXmuHSC9qLmbej4ih1SowiAX+DqEOeHsakfedJxA/OCfD8VXKPnSrXPP2ridpEcoJjIvVBDNTp+zP1oAJLpbFumZSbAj/AKQRE+LRxrI4cK15FBDCc+nTr0kQiuPEPRzVoWTWlAL6dUwslauIEoo51tZrhxWOUHLHU4o01VYLlPpNUCd3imSlzwSHI50pTLBQ6x4H0sd+YgExEaRXBA/T5LTrIqmAGObQrEwu7ZnkXiI/7QuwtjJANy9TMVjJnOw/n3J/ytC1isIAI2H98bADbmUD6NCWLIBLjNRrBIq7t+dpMmEGHhtd4vXBx4n0mImNp8wEzUb2yn8j0IYIZGY3hJFWd7c5mV6tNakRMIiRid4iY9DmRAiGN5q2IeEztsXrZT3l8YnYo32jf5Yxalk1hQIcH9Qu7jJlOKzGDrnTF+OXAOwuRjIUhZM/ndVO40ARG+6lipQKCNgO0hnVPNzBBcTExvHxl8OnJBBb9uweTzOLP2rxXZZhLTEwy2c95le0qwO4T5yIXDpsGicBYXmeoVBCDpkxsUuosrExbZ7ZFRePxFfsKLmebsvYBPVMjqh79TgzRJlyal2vcQFhEyQayXT9XI2IsGZrOelsdXEnWbBynpmp3brsgA9uvkMjWx6Za8vqWL7z/f3Z3J/6DNdP5GorELF7wXPUuToW6gJruhjAZDghm20YWuUZR7VkQRSy1DJqlfKAOsbenciSHxJURISGCGYkfTPPddtqw1WfPBeNx5ykImMNk5yVaXEELPqKp2arGB4XS/tE+mTjekzxvqlisrZqrFO0069dVVAITHFemKU0eLQExuVsPVSdmzXVAWLDslb7VVfbXVO109koiyO4FGJzCoiZW+F9OYdc79jlrM5KlXx76iGBDunkynEoifnXVMSu1QeH6msln+22KeOGLFvKzm4xXuHthDV5vtTj67FEc5FB2KD0L+/pdgOxRVy8zToXHm/GLsykyudSUFyuyj3IYu0hEGLS4ytqmRus4KNCRDO4zMSu/aD/AF8oxdtLcpZyt0xWtbFtCDUUGHpeNmaycY5MzFLLZP23DE4wP5rDYZeOVyPY7X4PawT5cyef4jexxyqtpNcEx4+o5mIiZn4k2XTkA3BC1gsYAI2iZiPmdvQhEo2KImPj/wDTLkyXbrj3DrqeJybmcp/due5DuRRyQJCYwQzvDVA0JA48V2EBzWb92rKO6MEE8W1390fMcWOGare+uN1iUEMEM7w0O4sg321UJoFNdozOmqFoSBfCGko/bOn6rTTUmTDbdTIYsTj4sIF65Gfupk6VyLomC/A0faWIcMflEYAEsIogG9R4hX+/zmrmsZbPtpfEn6WmHncjFFBT7B9Ku+pNKR4oOxkMBdGv3panpebDMhZeIcK3VgTFetYjfZTVuWLVlBBkem6y0XLncMz6eaTcRXkp3nPZP2FTiuf5nIYaKWMC1YMpt4holXlW/wBa2Go4MJ2IMqjjHMSgm5b/AAkNNt2G/ec7aZfpzLEsLx0pfASZQbMca5Dhc4dadxp+nVdm5yCqAFFapkb7q4U8TSlAZ7FRVrhZfYZYtAMAAiMbQ1cNUSymYhWJqr8nuyQrV1/YsY9McyKvUAx/pyfTlW6cvUXt3t6dzhh2CeLE4PH2sfWJVlsMn0XZmlknMczuFdydvIgOPqn3IwLzQiDD5y5Ls4WwY+YpgQ00SUbRq4EnVaI+Z6VPlioH1s2U1UE95cVui3nifc4l7LD5QMd3dqsus1rlTPmdO9W7T29JLEoKraJch03kYmYnIlAZrHVqaKuPqBu5SxUoFD9uupik8jQr/Gs3f9jj2MGdm9N0RRQGwY/zCb9e3bs441zyy1WrTcGVNnHWFdefRF17bng5mtnL1Q421fj+HdQV7v8As3bHtajrHzqgsMjiTuZCtFhlbpmxY5NZPshjB56oM9mRYLH5evE9+oURXyblc4fyZrGZLGIoHVvVyfrp5r/4qQ0YL2WsxkmAQ4+j9d57wwVMaFP83I4TDTT3t2p53dZLM08dHFxSTaeXoXIXCmj3fUmLEoEiiC1ZJ0BEJHclBCFzzOSkjZcmQXuCVrBQwARtHpEQMbRERDnrSO5z5gG2zg2xwVpr1Jjcy21+dbnzupC1LUPEI2jW8b7b+f3RDBRIlG8KSxDeIfUjVhPdGJGdm1390Zgo4s1YWQF7lP3gS3q3+RXLKtiEzuSvTkO8DvHKyiHBtHg67oeBJbH5iUikOA7zHrXW1T2DO8q9HLhqyXOiqwymVVk8hpVk4zJTVyqANV/pmjZCTqx7d2NzL6bv4dmNwZnbntMY1gT9fT9QKuMVtH16zEArPY95xHGBEY2GIiM0tDMZYh/2dK1boLmwxkxVzzxRirEz89LWUMx8Vgme7Yj3/U6k/crOG2++1ZgvyMliypgvK0ZiExmIJoRAbLmYiN5+AMGRyAoKDMFhJnPEUvU8ZJRcosjUXkNz3NFnHsbhE3lpBFh6hz2EW0fFjp7Ie8owthSVj16skibRXHx+G/MpdWuDoSExgxncfVpwCjOZ2jpaupuNbLgFg9P1UOZeuwEDrDT/ACxxp7zXSshH2YpA28HXiJ+shkZkSjaZjeJjXSDN61hPrkmnmsqGMRP8rl7Y1VJxONIV2FKhYxHyeaKxjs2vIIVyhGByWS5Wsg8kmnD9RpX7ULYgjG0JLqCV9wnrx+dRfuNqAsgnqLIjVpElbJC1ivc5DKVQayT11JQbcowSfJ1OqWV6q6xVubMBTuFasZS4HaPqB9CupTrSYsOWUmsCkeE56CoZSplA3gc9T97jD4eTvZT3XTQzJbuw6uzi6oay2Mdfu1CW6ADV9mUbmq1avzXWkAn5GJ106uu4bYmkCkFrXEwsYCMrl4rTFSpHevsIcFXlhz38vhcNKpjIX9zvaYwFLJhzsDHe5OxlbAwWlrZUUq6tkrs9OXTtVDFzDZY9F1YFpOYXM9MYCxkznaIBluYNm4IEYGIEY2j1ZaIi7VaOZpqQJdxs9xpFAxJFO0S9r5ka0bCqosJ5n+Yz0Zz4T29uaa8LmTOebfTaN9/8/tTasCETKIn1mYGNynaNWElyh6fDUOFwco8Tot6buUfoRMTETHmCKBGSLxC2raPJc7xbSwpFyv1AkpAZKOJW0kJRZT4NDweHIfmWBBwEzEF+PI46vkUSl0bTgL7INmKuFvYzWOVepMiQ5Pq4bOXa3Yacpr41Dq1FKHzBM11ebBOoQj9NJzLFNL2DwPqgyHEnETtqkAhTQARsL0pcolvCDV0wIc79sB4IxtiRq5XL7bGihMdKun/cwLRfiEb/AFau9PY+xXMEqFDToZ8Bmp2ZMSrX8KwDsL+h9xt7jVqLIijpNoCPZuSE5rDVcZWRASRsgB7fDb6OnZKrcu4sp8f/AEzqfYY2T65ye7na6/8AElAxuUxEMyFRfiWQUjZsuj+WpuZA0M874UFcR6eyZ/rXYDVnpgvbsmLbGn0ywjxQzzJheubfCMVZPfacKqa2DXJfPSsf/C3TrCz+W2NPHmhg66WvQFea5z9OSqco9wuPq+fEa6YZKslbrSMx6Z3IjRpEIzu+D/gOLhYxtkwoMjJUVtKSs6ydmalB9gZ4nhjtsx623C5NsvGvXY8/to23GtteruV7vTgsqwa8Q6We7x6RydwYdfxmKq48SJIzB66ivRSpbLLhZxhPjGoO2fJlNZ5zMndLxU1maUXse1Mff03dm1jRAy5M6gw5UnFYTERUx8iVKsYTuNuonKNK5irPbvYVuYljlZIfGRyVfHJFr95i1ZCvUZZn7elRhePdZaW0WMvcyDSqYUd4eVTp5MzEzYyeIw7pdGTyRdy1qZiI3mdo6lyQDVGmgxljJU5iqayjsQ8CZN5ozK+kZ5zdZttHq54JHcvlaWPKG2Y2HREIxJFO0Ltd5vBYzK2MBQ8jnaNnW/ndSFqWoeIDtDrKk+CncoS6zPOx9CxERiBGNo9CKBiSKdoiYmN4+NEQhG5TAxExMbx8fs5mBiZnxAmJjBDO46tVoeO8eGVHyYytnhvpYTD18JnbVZhqL2z/AJ09RAfuEx9ami0IMPghExkSjeFEVZvYZO65iJiYmN4Sple3IjG6vUFrXvwGB0+vLWLYM8S1lMknG1pcz6jO/nchucN9upWazVDb3I99ONylbJJ7iZ2O9kauPV3LB7axeUjJCwwSal6z9S6N+tkKKpM432jfxPrlctQrwVdjYiwjMqFEssTuK7tLqGq6nEkliFQhC0xMlFhUuQ1MTxmvas4yb2MYyO26IT0kod4iaCwnG11zG4dMHKjuY8vn0MAYMgYwQ9NrCuy9UmI7muqkQzFyz/VjX+5oV3T5m6RUepa9iI/L6sAxTWtBHmpYizWVYGNo9LK7t7P2vZcZKcVjKs88xd7zF5fC1p40aZM0ecyjPCKoJg7Wab99sVRKbp/qXnzqKjP82rE66ddNTJOx5eF+vUzjcdbFp/UJPGpKF66XdI4+4oo31ibKVwSTnicrYQTMARDhZnZ0f6VZ6khXC02JIcqbyIMLSI2Lq2sTmKZubyZMxEbzO0HZTey7sg0t6OBrHkrzctajlCt7XVjDj7ddSyTK1eoH3CMCMDHiMtTZdoMrqPgeIw38Lrm6Yhtwdzx93JtGJs5nuG7Di2efqURm+oNo81epbxQsMbX+qxjKI0KS68bcvSSThOoZgZ2rZSnF6i2v/q6bygqicXbmQbbxmTx147+M/NXPU1rbiOOZ3UY3IZm0NzJx2UdU3xhQ45P1NqdP5F6V17rOxUt5Wli1Rj8YEMsYjCGB+/yUy62RCIyRTEDe6kIme1xS5e7+B5XIcn5OzK9Krc1OcUyIGogADmY1WiXLI2THa6VR28Z3JjafR9qAntqjuNRWmC7r55t9GqFoSB/BuVWGFLjkaqxGUNszyIiEY3KdoJ7nlwrRsKaq1fVP1sMwWPI52ibbWnA1w3HTbS1zwjc2Qhr5g7M7D9Ix/iBO2RlK6w9wopSc87ByZREDERHx+zIYIZEvMRypN2nea/z6WUFMw5Ph1d8PDl8FoLcd4ksjhL0A8Np8Eh5QXYf4Zpm9R3cj9ESgoghneHpFy5AtVXFMyhvhujHmBBvtqsZodNds/T+BslkMxYa6ZNWn2Cac1qwwZlzxdhR02zL8b7TIZA2Zh094ABYwADAhrqPIWaNMCrTxNl6vVrLdbZC4WwGgLFlBB1FfsUaQnXnizG2StUEWD+/PY69NpttSFElQQKRCRgYymPLGkjLURgZq2V2q67Cp3DWSHuXss+PMZQP/AJXpzG+2NLlj6pfOuU0OqZk94X6nM0uphLb8vWRrxZovTMb66TtKmkVaSiG9WPWDanGf5gciOcxd1RK7bOlLMNx8okpk/SsNqy+yxTiSpVCqraYCCKIiPEfH4L5HWsV7yvBLYLVg0J3D0x8Ff6kfan6lRkac3PYwyJsdNkQZe6nbYWY2te6jsVijgmekqMfY5w6DpHHxO5MaWkdP4lEwQogyAAAYEBgR6oHiNKz/AJy5NHGWZTG7E2XHSnG11cjprjG4kIZG09KrYyLV9vk9XIh/UNJUz49HFwSw9LgY6WbJfOUKSbhJmPOszcKljmuD78S1lKlzEuGunknfvOytmOXqRCAyZzAjSrznsq288f5TWYwIZAoekoTZB/UuKmVmubKj6oycfTNOBIsl1HkI41a8oVSxNTED/EMk6DezIZPNESMaEoqY3C08eETAwx+s5afeuBhqZbaxeMTja8KCeZ6JKSAgIBkLuAqunmsI3sV317jaYxxKoiK1VVePPqtKlzMgO05PL1cYI93c2U7S7lZdlUTAasvJewLHkytWhUcz+pr7AJj6vJChtiYOz9IxAiO0bQJ2pMu3XHmQVN552C7htcpI/XO2t7Nn4/JUlCkx9EeWXBieColrIrOdPKyX0gAhHEIgY/bMAWBIF8LCFhARvMej1Gs/cI+5LgcEGGnV1u25/OnoBw7T4JDzg+w/wxiwYEgcbiACAwAxsOrSJOIavw2tZF8bfDNWq0PDx4NImC4Fhci9VKKvk79U5+qy43s9pXnae4QlFDGLljcNgF0trFn8y3ken6N2DYI9qzi8nYo2pxOTLzrqOv38S7aNyNDMz08jtxBWMRWbUx6a7p/NtVUW0kiwEGtKVoUCVRxXkse4LU3fcmS1jyYI6YsGrJTI3Dp5pVrNrEMnfWSvLoVDsM+aK1vwWSbJwdmEHkemFLVG7OlsiZAWOfMQfVaN6qbYzsxDYchbh+PTqZRQhN1X68dV0IrgZiXef1Jkbm66Ku1A02V7lSOX5nUuMvOvw9Ciauk1eCyj12Jkx6SrsBdiyQyC9XHQio506xEbVZn8d1PfrGEfd0vkAfTioU/nacRCkyH7unLK6uOvW2fdSx2WcA5apPJ3T+Ht0mus25iGYqZb1FkGzO+rNlFVJPeXBc9R4eJ29xpnUuMiNkkb2JMmKBhhKy6nVLMSZRq8829PG9c7kkKlhdGpRAvfdS2ZRijGPuxVeK2OrpiNp0ie91LZOPI+mVPt420UfLDAel1BMfVkuHewhD5PXVLJcVTHhP15youhjp2ZuWErzWxddcxsXp1HZa2VYmrHJ9KoqlWXWVH066oexFJZqaxTMbfzNiQFdys6dTMDEzPiKS/4/lG2bIyVNawWEAsYANX7YUqjbJ+Y6Zpt4NyVnaWrygnlGY3tFBaEhKNxmJjR1wb1bsa+Q+rWrSsmsKBCy33kWspaH6cQSadGtTc0Asa8fM6Zbki7daOZorQue4yebXWFq8T9Rwl1j6rE8QABAeIRtDOfAu399epMF3Xzza+wCYjf6j7Nmx5cXbWtK1RsA7ehmCx5HPGFNho84iYFYu7hEwo4+vn9s1JqOX148pcDg5j6A9TDIBLcnoFwcZ8TXcclKHeG6sk5DofBSShITGCGd4FKhOWCOx/i6hx5kv8AiNaeFiD2FdLHz3X9Kdj2jIFfF+sXQv1LNgrFjvJ6gTVvNioEFOQ6dyTLtSVu8vaoHKNTI5B0q01lboMnz63slL7raIjHbx6Ja+C/06v4DJW8m98NEFYOByFJ2NyI9wvZRjc37JE7oxeQfV6ecaQ5ttZE71iu9CpXfyVQ7uMYgojvYbqJFarFS9yEm9X04j8lLCJmfzVvxWX2gmhdszBXHzOlYyorzx5yIiMcRiIjIzAPqM9LmDx92zFlwzLAAFgIAMCGup7yk0CqwcQ+lkAQEV7AyvQMBgwYFBD6vvVkfee5Ny7f9oIEa93JWp7VdXdbUwWd7hN7o1SqIOvXBLGk4tWukzO1yrtgaqErrqBKh4rMoACOfjpTdpXbRfcQiUSJRExnlqqUCsV6yJPCpWOOrsgBhmsyPLFWo1heDcPWiYghq4+lTmZrJFc9YPLu16+2w0+pnQIg85iV9QKmREpWU4W8P8QvveJBoLdY/tYOomJ8x51nS44i1OnL/wDliue2+snO54Up+mNVZi71S42xEx1b9Q016iIGIGI2jTGApZMOdg6fQVp78w/eT9LWWeiw1GRoSdWpjsek4tV64rZrqS9Naj2Fz+diaA4+itEff6dTOOzZrYxMzJJUCVAlcbAWas1c42bxGNdTzzlKwuVsqpxWP/h1OK3PuTmbLa9AzQwFtv4y5UqfxM70sfh3usY1Dn+WemStuzN2MXRL+W6hotUFWtWXPtMXjLeRtc1FPB1laY2LyUKfZnk6ZWsQWoNhiBEnsdMhWjwmuCvq+4/wSIzMFMRJef8AHo6zIl2kjzaurJl3LM8z/bmUAMlPwlwODmHxYUTV8BLjMRMRETO8wxZHIQUST1Go/cI+UuBwch0+sQnFivH1gUkAkUcZsI7owQzxbXsdzcDji5gCwJAvIgArCAH4/A5y0KJzSgFh1eiXcTrkKDzGMCRgrK4nJ9uMVakdiHCwS8tUkvp1OHqrrWk1R7ZY6k7H0Jr92G2Rv2cYTbGQf37uFx70k3I35ibWMqXrl2yzHP8AbjBdTY7cjgbybGUQnJqyVKJE6712Eg9U7gqzXcRClosK1YCrXZYZ9uLB1jm8o5NrIhCYXHzirF+pmWY+82WxqlEI6otqHwNoSZ1XWjh4ep+DvlDORY/o9ozNhEjG+sj09jzRZcpXF+MBc1ALjElp1+qneCPcqVt2QuhWriICvEqj9Q5LXUtVKKSGrGB1ExMRMfHrlcFWyRQ0yJbsjh7uOjk2O/WQsiKCqN4si/kFDxamSn+KWznipHlrLsjPuXwkUpJ7l10DsTun6WPxrrNmZe/pmkNbHC2Y2Z+HIOhFGw6ddKJleM7k+nV1mQqprRO04eeWLqz6ZEeWPsjtvrpo+WHTHp1StU4sjKI5rDHvrrE5DmimFmyxaz4LYi1VsglTZIu5mFTuQ9yF5i2qfqSQzczl+xWOuQyKTRw6RiWfOR8YfFOj9QZ3GJ1ghkM9kBLyXVy59tXfA76rOGxXU8fjWYr2LWPbXrbdx/UCMWAUKqu6VawuyhdhU7gwIYslzMxGMxmRoP4Ta7tKJifj0iYyXU3/AKkeuJKb3UNm3MTIBaZic29Nls+39/gnWJcxyTZUydW1bOvUgjHXUk1XmmsCysXgxGVyJpXeEatJSwUsVLjiEzERvPiLeTtZewWOxc8UqTjsFSKCZx1UTczc9hc+2xtSoimga9ceK/bK73emJk3WFp8F5KEtsTysTxARgYgRjaNM7nCe3ESaK5iXdackzTWQpcnMTOkG1gyTBgPWOEzMjtv+5BQLkpCNvWxWKC76PDa7xcG8eCeo1H7hHylwOCDHTWQtcnPmFsBoQYTvFmv3PrX9LatmXRIlGzPXM5eaUDXrD3boYjP8Yf8AxCRfaV1LkIihYVArvYlTMUVKsAwS8dhUUXjasgd7AS+zgrCC3LUY20eJr3FAaruIzSMkuBnYLOXw0ZKVsF0pbj8JRxw95mzHZbNlemcbjAlusJi/4bU4F5eOcxpS+Jbw0t01mGx4BZrULT8W9mMtM2r4PFZBOUBhgS1Z/IRfcGMpzyjG0BqJGJj69dRVC4LyaI2sVLK7dddhU/RmamUZmodRWcFjMxcXd/h2WHi99dFhcqesWBUx1KlJzVVC51MRMTE+Yw1ARt3KDTkZDH1Qjbhy1T6fxtQpOF908SAj1Lej7fTqRXcxDtvnGt72PrM/DcQNmq5BfGMwf8Rom1TO3ZLHdR1vpEO6NNeYykHCDiIo9KJWfdvM9wVxIR1DjlAMCHVBzNFdYPLErhSgUPx+Hqux28dCYmOWJrzWx1dJRsWstB5DLvVG0r6cPnh0ejo5JYM/HSs74qI9M8EHiLMTG+qnT+Ns0UOkJE2dKAM8kMkZbgsipsNEyIpPKKnY1AzX8QYP6tZg6vX1WK/aCCE+oZleOx9QCgQyo1LGGSio8HN6eu+7xq+U7soiSuqrQn41l0FYxthQB3Dh+Vx1WlZJhiitnEPyJUJCVlvvG8edVagWMpl6pRG2EzlSlR9tcIhYnP4l3gbAjNjM277Tr4qOCel0WnWm3TaZKuWRq1W2C+OlkFFRtxkfmac5SFy1xwtdnq6N+NNHLWHt/wAJyBruxAhZp0cgse+AuCencPI7e3iNV61WintJEVKyfUJkz2WKjuuw2H9jBWLBdy76Zi+/I2RxWNLkLLVbAIjH0xmxeq4B9tsXM0yWsWtaghaxgAIhCORTERNhtgpCtGwpqrV9U/WxtzY+2ge4aobAfmzEn+IigRkinaOT7PgN1JUoFDwCNo76u7CuX1/tpMYmBmYidOtwlsAYzx1ZcaQghDlCmg4IMPiwkgL3CPvS4HBBjpyjSc2ERpTlvDcfOl1SU7ko9laiIiZmI2nXUOTZRqitE8bH/Tl4wg2ZFnfcjG4+yGRuOnunn8d3AUgistbDJWcKmIYnqO/XYarqIeOMiq3MAHal1cABYwADAhrKYRTckI0GduzFbq1EyIMlkDgs1f2/iVqRWiE4hxoSuO0l6nDyWW+upcYCbAZEVwSLOOGvXm1QibOLw7KF6Jxd6OY4pNVg38OonL0FbF4FSiMZI9FkHDnQoTEdkwFgEs43Dp8jqWLWIbO866pqydILYeGVXe4rKftt+C6MUs7Vu/C/QI7HVp7+I1eXDaVhc66ZZJ4hUT+LBT7bLZCjttBlxAi10kEeyc3ad9Wf/wDKKnnxkPzuoqCfmPxZ6Pd5ijSH59MKPurWWd8x0me+NMP86KNxmNdIsmab1T6ZNXdx9lesC3u4isWiyWPEpErKoJ2bxSR5FZAtKNF6uLoGeB42qfwMhOax0BaoqAuWslg3ZBFYO7CyaM4XKNGAIw6WXNXHutPmASCKGQtpydZvI3vBC+4fxcZN+eDh5A7AWHcLIM7Tem0Pr47t2AlZ4qlaXl79p4cRVjMMGRbBbNt38BjbK42Aa05CK6+nWRi9pr9OOS3FKFMSOup2wGIYP+cWrs46sv4nIX0Y+vL3z4KbWVZFm8X5SrNbv+1UOrNZdkODNMJ9CyaqrmrFOW6gYQVltEyu4m+ddl/I2oOcTmk4wYCa0GVK/VvKltY+Y66nyM1qkVlFs4XBhKoUaY97KYbDzT5Wrc9y96NStvHuRvqIgYiBjaNAta9+AwPoRCIyRTEDmM9ZtchpSSqmGc1+MrtdMyzTbUwfaQPcYhbh3Jp8pmImNpjeHWFJj658/wA3Z/8A+Kk1lJj6Y3L9tYrA+IneRNItFcQ2YImqBwcDjwlh1ziu+fExExtPmK9bsGciX0aaBVWd9cbqEhMYIZ3FVRanS0Zn1mYGJIp2h/VdIHStKzeDbQZ/L1BQBRXyj7o0pbjOLWUMbayj/f5kZ41cbRpzJVkistZNLsLdjJU95rU/aNUNqssRH0we1m1fyE+df9R0ZvxSGCn0yo7WInQMNZQQFIkFtV1RVLYbxirJ4e8zFXS2QGOoCSzBC4nKhGMy9fKhEwnqBEWsS0g+osZai3QQ/fecyXs8vQyBfpaygzSzFPID4DWXDuYy0O2+un2dzEV5md59c/VmzjWcP1MdbG7SVYid51miirmcfdnwOiGCGRLzHTJTXbcxp/d+G8z2PUlexvuDf0j10pMTi9vQp7vVQxpJ+46qaQ/b+G5nMdUKQNnM7uUY/KFkEFK5q9TZQ/planS7qLICk5mlC9dLIJWM5lG09ObLfka0eI9OmJgbGQT/AJ02OSjGfjpYpLEjGmYPFMaTTrjJ9N0kMvXGEsSD0ys75vFjPmNMSpu3dAT1k4t+z4USBbMRjbGPFoufDouhDhGvExDKtJdf6vuYXLjPDbnbuZMbTJbdWlvTuRe4m1LTZc63Xp1HszLeXPFZX+NBaruV2xthWw2DZW58p6VqsTjpaeuo6F28lC6owcAMAAjHiOpLHuTXiqy+9YuY+9iEqCHicqojj0BXnaX6JnNHt2gLFzjKUsk+3tFdSq3L24QuTWthSTBgixDf4ZmIRO8o1YfN3IW8iUwSOnMbxX/ErP12fwm57TJdeOMJV2g2kpKc7l7dV6qVGP5g8vlWJZiXblZsV1Mt1MFX24qUtKxUoeK9AsA3kYiNGwFxuZQMTYc/6aw7CmqC55l9bP3bkg4JA9IcSj9u/wC6xLoXun7xmZGJmNpmImNp8xvNNu3/APrxO/mPgWAczAlEzrNnIYqzIztPS41JxwmoR77KaZQ5KohMdInxC3Xn5rXatvn7dkM1kFWHUnKrFwdhK1qrjxVbnduSq+8our/6umsgCadivaLtwzq6iJ8QUwxrdR42yMjBSpvSpgWMmImOeSpNdlbx0xiIxN+MhRW/x3MuPlR6r1m2C2CPFaoqsP0+S6ltUrpLRVgn2um7c2MfCWTPevVAu1GVj+MBYl9I6NiPzenj9s23i2F9WWpe/oNrx9+AvTcoDDP1uoa/fxTZj7se/wBzRQ+Z3mzx9u3l9vSpyWKiJ/BMRMbT8Yjehk7WLLwrXUNP3eMZtG7MNcG5jktj7tMOanVIlMbB+HqseNatZiPqiYMIKPMdOZGlSRYVabCSLqbECW3dItIctnVIuWXNWCuVhy95jWCGnWa6Fd5zBBYZvEn8WgjSrVZ0RKmgyNdU3rlckoScqUjGVIGCme9r2lX/AO0GmYuofwMhN9B1lQMPMxw9D2VQYlhmZ5I8Tmb0wO8R1W4vsTJa/wCo8htv7WYjp24P8Wtd2IWWi+2ddKf/AEw/TpYY9o9vrlv/AK9jI9c4xmTyScQidoSoa1cF8pIaWSbfzEvCdqkzAxMzO0YvLDkxfKlkuDpVjMjNcSdex/Bcn34CSr+4x+TrMStwmAvw+AQSRZya68GVyK2ZEyRVoYm3TeptK53aWspkAx9WW7cm1FrwtRmRyE87uKa3J5BuVu+VmZMMjL59brJVVYcTtOMBg1oJhTM2R7mUoAEbnkn+3x9h3+YA14hKY8G3N4vGcaUkREXV1XeYVXaev+r0RP1VmRqt1Ni3zAkcpISEhghmCH1Yub/VAkkt15SMbimsyPDld6exhoWV6z5taIhGJIp2ibRsmQrDykKccuby7pxER4j406ytX0z9RqMzDkYcJ/bJs8zlZj22atxY4wSJ21WsQ8N/g3JBwcS0hxgft3/fqxaNDRiR/KMQauRnyNZhKP2zfl6iqs9wn7BmCGCj4z7e9l61OyUhStWl46Rx2BGCsIzffwVh9qYh3SY88g2JjcbAx03UZ7c+4/EXWBxp37IOuaTar2CYKTg5srn+MZCurzrFW8sVcUY2uuYtqssISyeL3i3ihqpRbS4iVjaU0eojqw45Bf8A8Dznb+2jYrBYgROZiLeSx+MXxYcDMRls5MwW9HHU6FWirtVggIKJxvUEHtEVtP8A/h3UKnD4T1JTP+KV2qKFkFTqlw9p1kUhVX/BM6NcjkkXOM1Hwf24y7k11TitbWtdN2Wal0ZMVqr4ikilSFaGd4PwdQoYuEZNEfm1rC7KF2F+QmN/E/GDL2WUu4svAttVkfrNBeupMnTedYqbOb46hzTGRAv8lZzLJiTuyOpLIzG3vna/+JbbRfdppXgCSbkXQLYyFsC7Z2LVfEZZP8KTLTgnNqKJhMREJmaZzO82GTNXJMx1neyubKnrQ4i9nWbGq+PCVbWImTbi+TN1lALKjXrjyaRlI22p29veasbWTu2FSp7xsLgzD7SmNLsT9Me5auU3KNatJttHbbfuULR1uzJjCMvjGxArtLmcoIuzrYS2IEcW2Y2ZZKYHFVY+7kemYiZZJJZwFWTyeMZAC+GrmzDMdNuI2jpXb+FRt866W/8ApzI9cj56lx8T8aIoEZIp2HplZ2LFvJsncuqMlCURRUezaTqdeFCtobZm2scTZYoxPXTa1hiEyEef4WsmkZlMi6hUfXmsxcSp/R4fNaxI6pYgRzg0j2sgynVYj2xqGU461UTaLDVwPYzBYEZzAghq7LmZ279FSzYY1DM1ajaUo9jjk1P938GWbEkqvJcRG3aPkVWqba3TvK5mDtEO0dWtkcetUb75ZVbH4unJ7xcwtWnK3ZTJTBJIcnl3zcx6vbq6eHGwbAtmUW7GBxVgOPYFc18rkcK4qR7GutYXarrsKncNZC4ujUZZPzrBQOPxbsjbnjOMqtzV0sneH8j0dWhzIJhTICIhECMbR6FEkMxE7SmspPmI5HMxEbz4g7g7yCRlppJhLiWjxP8AaPQLhj/SarBCfZsfSenpJZ+4RH1KaDggw+HoFwbT4Ku8pmUu8NYsGBIHG8VyJDvas8jZRDg2idjrPlm6XRs3WRxtbIp7L4nWLwdXGkTFyTGz0pXK4TjbPYkoxfU25fRXydKneTFeyUDNTHY7FKkwiA07K2cmU1MSBCKl1cPj5jf8vGSbrFjJHEidKxOIykTP9tExMRMTvHVCpPFEUaeztdQ0bE/p5nFjk60LguDRy2emgS1iQrwOMoFWXfn+Ye+/TrgRucARGVvZY5Tiw7Cc1SKxiyGCkn4y5F2imx/q6lrd7Gk0f1MuM5HBKuhH5uKuxeoqfvufUlGbNHvrj87D3Pf41bGbEacRYaVuWCSBrOzOSpqxqQ/lkJBCVpD7PRthCf1WAvTM/iF772RmXdR49wEpSW2YoZ2xilsq9mSCtmcrkIZA2U1YyaLAmNmLR2WrqYkRgjrm05pWQjgvtkNWmutHM5iWNlxyMV2BGpXkPkXBOk97tx39udmuNhfAp46FMwHZdcmUA+kmOAGIx72r/wDcjT3qbI9uz2oiSiY43onSmnBfmOUQQxc/BjreJ+NO70h+TIwe9+PkVnpRMPeGp4aKugvuWM6CuhcyQBAyQVGT5gCkVKEYARji4VwcwVOePChM/LEkIsn9C7vEjlY8iyTjk6TmLrHAGMqdOQwTba7p5a3UTjGkwo7fTeVoV6XtnthTV2qrZiFOA56bcpdA4YYhPpdas+qqojO/ply44u1O+2ulpCcTED84ymjK5a86wPcSjD1bWcbVTExTyFGrL7MUI41+lEvDHkxhT22ZQv4urHIgTjVuyFSs2wf29M1j7Lcg/wAu1g4h+SyN2I+nqbMDJfw9P1ClxZ+1XqCHYpZcVWszTpFEQhzSa0mF8+pFAjJFOwksix1jJMj6rwBjOnJREbFgcb7ClEFO7epOEuxwsmIXlGPykPyc/TVweLnKMkGnI1rf8QrvppxyhirfxNC/HKwGxzStUq7LGNykMRcVbsY1eTtTBHi22cZYppE+7R1fKcxmV48JmalmSz2UGoneKC1rUsVLGBCZiPM/ANWyZgCgvVluIPtpHusSp0F3HHuREIxuUxEFdGZ4pGWl7dzp5WS2EABY8QiBj9m160yMH49bNeHhA78ZAZEBGZ3neI+dNWaDl6Y3FTQaEGE7xZR3Yggni2tY7sSBxxbIjJQUxElq3X7g9xfhtVjGK3bGxevU2NO3WGwmJl3vMVl6yhvtmtdXjsTyibuU90BZ/CU0wuuUENu9dzjIDbs01LFSxUH23Kw2UyH+vpnJy9M0nz+flE9/HWVR83yNuBx1xe/KpZXbrLsKncJASGRmIkaAlh8sWPL+0HpvGRYKwYkycMHss3eo7SK9YmJx+VtY0vCstbphUdXc8Fn0rbh9RlJswWsQ+cbmX4w/CZiCiYmN4pe8xObOmgZNGazV0n2qPIPb0MxFaoqrSpPsxezucWI/yntoDM58hmJFQyxuVsDs+6UCNGvE8jiWFEVl+YFYSV5MRsT4mGNptODlZNKHmO/aqlELsPJkAdcgjT0d6IjmQRGPrf6ok5XVrqLmsOJehgJjIF8Bj6oR9nKYrV4+FDrsq/8AQOuyn/7Y6muiflY6mnVn/aHU0Kk/7emUaYRyKZCIx6txNbDj1dbUg4A9912UP8AcTM0Kk/7evYAPlTDXrs3Q+10HoDsScLekZgqlY/uWOv4eiN5AjXpLkAPAnwySr1HR9olpuMCQkVGQ6rGtIdm1jYfqpQw1pO7UlTdR6X5LYd4ykR9nESMDd5qbAnADkLNTX8HzBXJt1mhYPHZ4tnoykQmwzIZvLw72KYipjUvxuGKHiIt6ab7fGXbZax7fYYGxkCL+YsIJONoUIni38jHUv/SmnQy1s5zNSRht3I9SVkg5/aRrqNjHlSx0FsxSwUsVLjiFtvYqudrGhdR07LKY87VfpdjaDDsTxv4jEKxiJiJ5vr3JXkHFaPkcTExvE7x62+5YarHo/Uu1wZksdilR+Tm97uRpYsZ+nWeZOQuJxFcYI83FWjhooLHeaFdWFxJMZEyTslbyGP8AcYqeLa+UnK8qDKrkay9VeKxS8cguZ5pAU+nYrbxOr8mvG4axGs5lYpJ7CZ5XIJ1dI4ilsd7FY1WNqwkJ5HM7RM/OoU+0Uk/daxFFcfGwRExMRMTvGgWmsE7bDE2jbPGsHLUVOc8rBywhAQjYYgY/atUDg4HG8VltUMrZMEPraRL1cRnYqbyYMrZ+o1R1yl6I+lbBaEGPxaQUzD0+G1rAvDf4Ky5yWCcRumJ3jePj0sPCuhj2fZicsrKLYYBKyvWhp1G2S86XjURiGZG54dRpVrQTMkQnGPpVxlpDJai3bJ6iXsK/S33aVleRr+CqWk3awvVO63ufXq28TOwh02mFYlUwUl6Z2iVulJJj+Zxd4b9JdiPuzkTSv08qO/GJiY3jzHVItRFbIILg3Bpw9mGMyLOVlbum8SROQYyy5k4v5ZNtaCILnUmVE4QNaKzZXZsD/PWmtitNOoP5FUJNmRts8dyRhzbG/wBAdydrxfMrXr27ij67Bzr2KJ++SPQ06o/Cx0KlD9oDH7IhE4kSiCGIgYiIjaPXtL5QfGOUxvG2vYyM7g9g67d0ftaJ67lwfuUJ693tMQxTA9CQk9+SxnU0Ku+8DIz7Rg/pWDHRndQEmRAwVWrFrYKVcmsRgcw6d7Nn2w27tulwAKjLS56gw1oOzbGQ1TjKUoIcY2taX9UOsHmQcptF16gP/wAOtJtotZHOW6zav8OINSy1Uw01GgS4zDIOwjDDIKr1zXluoFEmZ9r1RbmELx6p3dQrRUppr+N8qcXeoqtSBkhx8xkeorFudiVrOt7WJslvtONT2KFdXx6Z637TGNIZ2PF4CsWKhdxe7bGMyeJmSTE2qiL9Z8REFxLTWCpZMPwPTtMuLMpYjZmEn3l+7lS8Dg4K7ft5U/tvWgp1G2T2104qRVZy9udivNvZEm5dEyNfH5Onlq8rnbuO6et49sW8O2ZMczn5jhOOnmkchkM3XTekSZ1YfNdWmHlmRyaKiV4ymEWbRieLLvP/AJnM4HElSUT7Mb2/R7ny3sIHaQpDM8nlLTmQWPmYEZssbMjWDlA04KebylpRERG0RtHo1oJDmfxD7T/CQ7YpSYTJsZJl+1tpcziSp2mtZh0cS+luuwvu96PB6MSqM7oRukSExghncZqkNqHKmBEhgokSjePXqDl/B7PGZicWL8ffot7kdrqtshjICI8ChdnOoqMHdLEfw3Mmj/bMBMCAvthFbHHB/U1q7MnaZX4+NOECUYs+zpEbMC+fPtMh09Tv2YsmRLNawUsVriBD0QX8JzZVJmIqZGoF2myue2/T16W1ZqWJ42cnlcNIxXsfzcvxdvKt7qKI0l1Ok6StitGTyQqlWnsVxWsswN6tlW27Cpka7u+kWbcf6P8An9/cuRWiIiOR4rENyRw+8UyiLuJoMij3ArkBqfEMS2CGwHU9JksU2Liv+olTMrytAlwCOl8gQwkoS08Lk0jMVL5GuxjbcDPuMUByF+9TMVostUN/Jxku1F6JUbcdjXU4nH2e9bxTbmPdNhdM3HiKtnKZU8hdGR9OmEG+/auu+pl3E3cIM361nxjrU3KSbMjxnqTdsU6MTqIiI2j41fiMjnEUfuQ+yisvuPZCwiYmImJ3i/gMfdkjkO051LMYwpHhNqvYv+5kEtAlrytynVx81yb2JslGJ6fXXQfdZjKg06Ka8fPUxNtuRjq25utZS22knESmUMPt4yiqqqILWQ7Vd0WKzezZo9RZjt7lXm2DepMkQH2aBL10spjZs5F+5My4WMnnpr1fvY/HYBZKrbWMlgcUEwOVsn37MtWJQJFEF6ttCJcFx3GDWNswdmeUkS1DuUwIqaLR5jvtqSEfmYj0mILxMRMf1+8Hf7Hnn/QfWhn1rng2vY7m62RxdqWBBwElEFMQUSMxvC5KpZ7Uzur0yeQHHVfcEEsjNZZiMWqzTmImvlaMVFG62ojuwFrH2BWUFETP8Ip3hmOfU8ieOrtidwQf/wA2vj511bX2Wi6P3AUGAnHxkEWCap6Igio1pQJSwuTpmIjefi5bZZFiqoySsHARiavCIiPwZrHKv05EiFbF5K096phM2r1bp2xcYVzKNIHQnE4lcHxXXiM3ZtzI4umThjHZi15u3u0FLE0aM8kL/M6gV3cRYjxvjiiagRvH7mbCB8SwYn3tWP8AcjR5OsP28i0i9Xd434F/TGueRyo11xyFCQQoVLjYbNKpbGBsqFsH07WA4bRaymyI2iI331kr1emsZsKNy10+ncwP8vEKatJoqwlJcz/i2crQRXMfzCvmcRkg7bZESd07iHj4T25tdIPGZmo4THuZ/EFxnuAFXq6xE7WUCY1uoMXZ2iHdo7ODtDZZexNmFyeN6hycCq60QTWrqqVwrpjZdvFWX5qvehke305oISbmTsHTiiJLsg39TMprvomt4kUYJ2das1UWBKa8PhIRYkZdkLE1qbXxO09N02uNuWt/U3IWGDk7LbqO8WHzVe1KaUhu3IWip1GWBHlrp5BvZYy742YdRM2wtMTDG5aVlw2KO4Fa57hgdjutwONPH05Bk/mdS2zbK8TXHk2TrYXGByiZVi6eVvMc6uXYXmMbWx9JNSsHcs1kDWrqQH2hTSLJZO5Fp1hSY3OfO1iz8/kqBaa4eNhibLGzxrDvAVB5c3FLTNi1DucwMe4e/wAVx4iNIZnk45YWmOWqNzKI0l0OHlAzEfuLFeG7EM8W17EnMqbHF1hAuD/saO72h7vg5EZmJmImfTLZimtzMdernKcNgf4kBPaZLr2cBg67VJa5oMtYW/iOVzHvk11m74K6rxrKzBdMqlpbHGXGMlUvAM883X9xi7AR5nGs7lMP++vchUtWSd5mpSyOZnf9CnSplRv3MQc7q6YZzxQhPz6WrtWmHOy0VxOZvXZkcTVkh/gVi1PPK2zdo7+FxI9mvAyzv9QZH9BY0EI6erdyHXWHdcRprq3ORUqvnqtq1FestrY1ZRmskTa5wFOnPSeM8/U2NXOm7lUe7jnE0VuyEDMtptmIvFM8YrtkvcW5+2k6Y9xkpKeNM9oLI7eaDt+eQmNxoOmDfkh+abBlOQd34U8IH8LGAoZJhQIzkWsKRrK5aGMyyJ7dfloqmfYP6Jjr2+eTH1VzZBXLKdos1TXI5KrPzMjqLlWf90dFarB8sHRZCTnhWXLCiles+GsmNI6cMvPaMoX0wPjkIDqOnKsR8xva6TUwZJDYBn8Mz1Of0pcupYGyzsTsp50LgfKinVq4usXA4mT/AIlU/wDVOpyNSP8AXvpT1OjdZQXq20hM7Gcbhae0ZmpTY6emsZZqQ6zaDgz8I0aYviyKQF2slkrNJgwqmyys7vTuRna4marwxWSqjDcPe7qQz12rERlKRrh2SK/w/hF1YNXh1XlSeSqAizPSy++Jd3vJLp51Yu5i7hokr3UdIdrFUbIp6poTsNkGV2IsIsBzQwWDrqV5BQGsuN2VkjXrrQPx1C4EY42T5ZhaHsKAKmNm6yxnk8gvEJmeytYKAVrjiF6ii+ia9jfhUo1KS+3WXARmbDsjeDC1ZjglK0KBK42BkyKyIfm7eJRwtUdyxh7N6MsAyfOLDhroY8/t6aTFpz8q84Y/qmxwpBUDyxWaTi1DQmq6dBenNZyoS1kKvRkHITC54mmoC5ljZ5sK3ElwQPdMapMnnZLlJGtQ7lMAM2XP3GsGwhTHl3HFLTiIiNo8RoomYmInaVU1gXNkyw/29l5IgSgJIQMTGDGdxY9S5gTKBmxXh0QQzxYixJT2XRxdrI3QoU2WSjlODzw3t0WpELWurhOaKSj7MbUYGGXVJsQRDjKCEKyDxc6jkX26ty5YGApIYpeNtLKZ7tnF+5woUdoFmNwNs7YPyawhbiAUsJn6dO77WWAAywWVs5ZQx3alKGY9KMZUyiilkhIyAyHgeoapFWC8neLPTNtZ3ril7wFzIU6IcrLYDX8QyuU+nGq9tXTg8fVmbV05stb1FX5yjHqO40sdm8nM+/dFWtUx2Pxq91AK9BmMc20NVToNurWKq3LAPs8mQtS1BC1DAB/T6nx9uz7exVCWEx+QIBh+JOWHYsr3JtNwD/Fa+32nvXffvT26dfkVfpY2FDci/mVfGUq8RC1RqIiI2jxHqzHUGzMsrKKX9O4l0T+T2yrdL41DOZ8n6GpVAYAUgIiIjGwxER+HKYarkQ3KO3YHGdT7lW9xPZo9MUa8Cdj+ZaWJxhRtNVWv4FiOMj7Udsh02aCi1iZkWI6jfEdk8fzsyHUmTmAIIpIpYHHUxj8uHM/pWqVS2HCyoWQ3AW6pSzEWiTH8Zy+PnjlavNUVen8yMEiYQ8E9QYyeKpjIVsddO6iWMQdcnma0mxa5adbqWsTOzdWdN2Upzlag+0cvVLp7JLsEEmdVnf6koT+cobyW5ddzNU2WBmulT0vHklgsG+P8Rz9elPlGstkIoVJZH1OwuPKnXlj/AKrfpkro0abLM7TPTdKVViuuj+Y9Mh03XtXZYqxCjxuGrY+Nx+szATAgOOQl02+qUtxdslGnD5KzfVcyjQKNCtYlJiAiTXOJkqrj5QDQDZpcy0ysTjmWsmVkyvWHaZgI79l36AcBCkPLm8pacRERtHiP3Te52y7W3OpZlkStvhuoiIjaPEPrreOxeCQ40l7d/wA2K4uH/wBJ1rDZZNdsbn1ABHiLAiHOarsayrFe5yU9WTzOPCO6EZCq/qPEXUHWtqaIUc7YxhSlRRZqtzeHukLr9Ipdby9i+A1EiNOlepQuquU/ONvLv1AeExvqYiY2nzCaFKvO6ULXMxBRIzG8FXlRZHCz9uEsxaxiDifqvZOhSGYssjlQxt1ru9jVlSV7LD4qO/fbD7P8Uyd/6cVW7SVdPy0oZlLJ2zNmOxaYgpXWWWZuXC4YmtJivAssFDctZKyQV6NMIIVrSL+oQM5RjElcdVN7K6zshC3EQjG5TER/WbTqO/VSs9JQlA8ELFY/tCGCGRneISoUrFYzMxtG+/8An+tMRMbTG8WsBjbMb9vss9r1Dj53rui8lfUq1H2sjXZVZkIuvqQWMaItPLPXMJztCCBWOqOjv4K7KDHM36MwvK1S2qZOjd/tnCZZi3YVbOLGOGzRTjcdciX4W2VZ9U8jhLbrF6ub4r53GWFG0W8NY1bMtcnLWR2R63mTm8yFBZfykRERERG0azWXcETCpmNDDIsrKs4ztjO4xPz6XbQU6rbJ/GDXZ9rNm0Uk7TGrUPI54wqyDjkQiZjTbalzx35n/OP+dkAuqlfnbkWm3FB9IfmGA2mFBsLthpz1Jjc58jPIYL41M7RM/OkLcTJe6ZGf2z60MmGBPBqLEkXabHFrhYS5hc8TqMduSnxPNyQcHE40lpqOEWNbR86a0EjzOdoKjjbap3QshPpNEFJVrLEzPTmUCJBdwDAcPmaowI1qtqMnjb9SBbaAFhVxyA4tkpbOmWHUXOnHMIQqNF1VLRKTH1zpVE3at3viD8bQzDO57QioUfY4bDB7mzPcaVzNZSNqCvZ1qWBp15hrt7Vmzbq0187DBUE5DKZTccYv29atgKizh1oiuWLN2pSCCsMFQjlMhfnbGV+CTxVcI9xmrkvlC0AoYriIq1bw6r1qHWmGxIAKwFYRsP8Ay9isi0uVWFwwFKWlYqVHFeWyFeioZsIN614zDZT+YxrSrOxlfIoWarzheN3A0bZdwRmu+V9Q44NlEOQSM4TKHEzvjshRjMIsRWt8bNbIdO0LkSQD7dyjzODCFGqLlHH5ijkI2SfFvpcqXMDfm/WiGV8flqeQCCSezNZLDw0pmVyxdLFFEwIr7SojaNo+NdTH3YqUBn6oiBiIjxGiATjYogobYSiOP+eNqz909lSq6kx9A+fVSFK34DtLrCkxuc+d7dnzH5K1U0rnlMcz9d432/z+0e7sr58ZLSmi0IMfh6AdHnwarBgcJseC9GqBwSBxvC2nXOEvncDAGDIHG4oruQ6YGd0TMRG8ztBFAjJT8V7APGZHxN2ou7VZWZEbDbPHSVK0EyfdsXV7Vw9tXxhY9vcxNJROr9PuNBPxL5/N1dz9KsfZVvZsQrOZPy4/4fWpYPH05gwX3HaDDqK8d60c2GWr1SmHKy0V6jJ5LIzI4tHaSjA1+5D7xldsWLVWovk9gqCcreyJSvEp4qq4Kss+/bKbln48R8WMbTtWAsWFww4iIjaPEf8AOrxVJVyLil9tukZCnYeyupsE65YOtXJ4KJ2or4vO1hskrVLF3aFgBRallHVqx7aub+BN0yph83HfpthFuMjl8QXDJrmzWqXqt1UNrMg4bK4Wct27fshOpYzEM9oNXO5ekpTLyZbVp5ahdiOw4ZP1UcZDqgijyr0s9+QgUfKKq1fVP1M06ypPgp3IZtOmJ/RXpr1Jj6y2mW2bE7JHtrVUWE8y3Yz1n4/763uunaIhIKqrWXOZk2ftVqWqJgI2jTVA0eJxvEMbVmAduavBh4nwsm1HQk5k1sWLAkDjeBM6hQDfqTExMbxO8OSLg4FvEIYVc/bv+1yirM9wmPpU0GhBhO8W6Va4o1uCJ0qEVDnCZiOdasbMBfKs/wD+ndQPTXtVrtVozdGtmMvHK2c0alPHUqAbIXAauZ4OU1saPu7eIr5NQsPIu7hXcnSojvYZEF7zNZGZGmn2dep0/TQfesTNuxey/ZeNKkv3Fx4WGVTFJwqxVwKROLF4yuWoiIjaPEf8vFhBH2xaEn/Tdjab3hYYqIfqIiPiNvV+TzNOy0305ZTBGGzcS6rM1rk3MpjI4ZNfu6c4ilcGLuFf7d2Tv5gEewyIwoaVT+MOX9MhiWpU5UpaEGu30tQdMnXIq5i/M4y9NOu/3hf9U31DvYpba/i3UGQWRUasLV0zj21KrG2AkH/h7a+fPjHN1hSY3OfPdtP/AEh7S1U1hPM/zD9GOWqNzKI1Nl7fFdfhIMANmFzOZiI3nxC2rZvwLl+2YD+6JrPcdXEMbAkudirWe5+Wz6WkMFEiUbwUMpzyDc66nqdG4Tv6EImMiUbjBFSZxLcq4lBRBDO8WEC9fCfEiPEICZ30qoCnS0ZmI1kq1GxVKL20KUdrIVyxdDk6pjMHUx0QQx3X3stSozwaXJ3ssplyk75zUpzOMw1b/Shfvcpl4kKATUq0cLSpbHA91/oFdC2G1axFk77ePmN9o3+ZmBjcp2ixnMVX352BKT6rTO/tqrWxPVL4iZ/h5xoOp7j5gK+PIzbl+oJnbtpRqb/UU7/zEapdT2UFCckvlFeyi0qHVzhi/wDhv8/+34beXx1TeHPHnY6rg4kKFcjIhyN/6r1owBtJQ369egbIOImIiJnef62SxSsgIlzJL8cF5daAvmLHBjqYW/eAuBfMRMbT5ixgl92bOPaVKxWZbsEWOy1WC0zBWqRy/DvkJx2fVYZ7W4HtbV64ulVZYZPjAViVXbk7cxDuDOoLXIpkcUAAsBAIgQ9DYC43MoGBtE04hITIaa5So3MojXds2PCh7S1VFLnkX5huepMbnPlTbDSguEAprlqjcyiNS6y/wgeAKpLCebPzDNi1jucwMTcNk8ay5PUVWt82WTMLWCx4hG0NtIVvBFuXctuj8sYUMfHn9lFti3SFgeAahgSchBRz0+sLdiieLFWSguzYjgyYiYmJ8wpK0jxXG0PsPQ7ch3QJCYwQzvDVC1cgXwpYqXCx+PXI5mtSnsjEvtpxV3JMizmSmFrUtQQtQwAaiuiHTYhY964VkKrCqjBvpYQ2s97mC79i51MC2xWxqvcH/Hs4oYY2uogp9SY6wrk04rstdU0EOha4J4h1djp+5bR1PVmL2mYhszY6ntWI4Y+vIaiqy23vZOww9V4w1baVU+RRmgGNhRtB5pkx9CoGXXLLv1GTMejVLaEgyN49tcpn3KTC0HUeWUMcx30PVV7bxWgpHq+Y8Nr7SPV1SfuSQ6HqzGz9wsjR9W42I+kGlJdXo2+iqydB1e2J/MqfSjqrGtiO5BqNL1vHmveRid/+Bz+SsWL0Y+qyQUrH1l+ZHnMRAxtEbRpjZp5Svcn9N2SpIrxZY4YUzq9EH+VWM11OosZZDcmwg/4jQ239yrZTlOHmkxYP9feP++pasY3IxiMlWxN9fCwxYscNiqysvInNrF3kOyi666rY/h6lLSoVKGBX6sqLa2GHMzo2pQP1TAx3bNj9GO2tVNYTyP8AMP0lK5Z3JGJM4khmBnjKqSxnmyZYZsBY7nMDHuXvnjXDYQpBvzdMtOIiI2iNoO6G8ikZaXbtujdh9oVVUq8iO5f1/q5f44+rVA0eBxvAkynPBm5oZXB0w9B8THfjHLzLnggYI99mKW9exeYFzaxQt/1LiYmN4neDATGRKNxABAYAY2H1e9NZROecLWWTyWWOU4oJTXoYqpjFm+ZljhyeTyzSXjBivWqpNCBUbScXr1DmxSsqNUuVimkaqeAffpmOrsPn5GVU66vIhG8gBeJGJgUJGdxAYn+pMRPzG+iroPySxmfZ1f8A7Q6Faw8AMD62aKnDPCIBnTGT4TOMsfSX7z/96u9T0q5SuvE2Wlns67clLFYjkOojjjL+EH/F2Rsd49VaMIOWGXNnq9APXIH8LxiQKCMpPUWnQvtDMCuxQQ6eW3bIcdQiIg1lOt34mzFulJe2rWF2UA9U7r/Fmc2rGhAL4ssx1BmlxDm1BJIdXVoj86s0Cb1eufCkFGi6ouFH5UEUzmM86Po/Ljnm2Tuy6Y6KvcZvDbjTiMWr/JnOoxdf/MlOv4ZV/wD5aTSBJTKzLgv3mNZ36BTIYzqGrdIUsjsWfWwyzz7SQ20qmAzzbPcZ6GYBHI5gYXaU1kgG8+hsBY8jKBibDneKwbCFMN+bplpixZFICUTPoIiP2xEejbqV+InmVdlhhTLA4B+zmImNp8wdc0l3K2kWAdG0fSbFgwZA43hFcklMQySWQiYyJRuM92kXj6662CwIMPt9chlKmOXzeX118fdy7Rt5XddYFgsIBYwATETG0+YAAAYEBgR1vEfPplHWEUHNqjzdjyogZtuMMWlkqkfZJHor1go3VXKYK/ZXEE1HEUthyhYPiP2t+tMx7lXhuDykZGruXh/4yLjG+0z6f+/9CN9vPz+Fz0oCWOMVhY6pxiigVc3yfU+QKYJNLYGdQ5o52XXAIfGUvf3liYCvSTXnkO5H/UMYYs1lG49LXCUxuMdPn0u5rHUt4a2CM+p7liOFCmUmx3UJRydbBJfxHqOI4w6J0mm9lkn3PqLUxE/Mb64B/wCmNRER8eP6Niop8bz9LMdnLVFoVcjPOuti2gLFlBh6NbYlvaSG2o328/Om11ukZZvOifXRHHeI13rLvCg7YhUCC5tmWm24pf0B+Yfbt2P1C7Qcl1j7NdfNsb7Rv8kYgMkU7Qd0znhWCSmQliuDPEpQlX6cfV+zPnwnhtyVbYsu3aHjPz5j4s15LZqfDl8+A9zbn6TETG0xvECIxsMREemQzcw2KOMGLFyvia1OJyOXbDrANvZpn5cnUxpsUod2HACTVAPMzERs9R45G8QUt1Y6lvWvy8entR7Nzpk7bzYYBfrzE1rjB0nqLI052vqh66TKOSRFsEjMCpYRsICMEAGPExgh6oiojt1kjMOqJJCBAp3n9rO207/HSET7qzI/Z+0mYiJmZ2ixmsZWjdlgJm51ZEz28emSIq9i4feyDSYSwWqOKghcfsbVdkmNmtPF9LqutNeIuiQ2Ludt5M5qUo7CK9TG1IiYV7pzLb2Dw5cV/sGLBgSBxuNW1dwzJJX51SlerXkw6uXIfWw6UhygZLQhbseWF2lrrIV5EY3ZbUJcQjuM7Vmx5cXbWpClRsA7ejGJTuZzAz7trZ2rrmYimTCg7J85OxXrxwHaZ/nLG/8Asrr1hRE8ZmZ/aMWDBkTjePzac/5ZXAxYMEE7xqxYahgzx3SBiYwQzuPo1q0rJrSgFsyOQzTTqY0ezVx2Lq45fFI7naxirdlbrBSxWVzV1t08fUL2y2YyDHkbzY4MWqI/MMikKFUPPDfUQIxxGIiPS+1ikxIDBR0/kAuU4GFwotTMREzM7RmbyrGWKwn6gid43/bXmwquXnYulqvZxvdKNi/adXWmLrprBO0Lp0krEQX3GCpQTuACM/tJq1yPnK4khERjYYgY/acLNCxFyjO2sVlq+TVJLjg3SvcywibsIaZdCC4KiWn2LD/1y4AtKlRsAxHo20lXiZ3Llcf9sdkF01DPI/zDbYSmNinzPu7Px+StNZSY+mNy/alEzExE7SuyxRdq14nwUf8AeDSxByyt8IsA6PHgpiCiYmN4VW7LJICnt6vX61BPesFtC03+oGQ2zvXxqEJrqFKQgF+mYwC75zZSfate8fUada6E9wLll07V68loMbmbPkt1C3prIguXqPk3H2qjZlN5s1nWHVE7SNlbRsXlsjsIGWn09hrNGSfZmILXVVw0UQSsuJVKK1iDDjkz9oZgAyRzAiy695duoM6x/TjrZC+6ye2ACsBAI4j+PM5VmNgCGImKubzi92xMvWPWCoXPOsUOPNZ6xPNUAgEZrOoj80AsCnqVsnA2KcrjH5VGQkoTE7fi6uqkddNodVramrGZKIPuL/8AVGiuVR+WRorrnl26YTOrVHJV0e7YRyNZ4vVBj8/8O+u1TYt05kH4bNKyIdtn5drT0y4OHKRhSVqHiEbejrSlTx+4+Nux9/5K1Vkq+0fqbZSrwRfVB2n/AGR2VpqrVPL7z1yHfjvG8zERvPiBekj4CcSX7QwBg8TjeEpFIyITO2rFblPdV9LqptNW7Y2LWXzK6EQlUd65SwzrDYv5gu6+71FQouKsQmbD6q4RBzRbC8f1BQvHComVO9GUqjWw5qQNggARsAwMelrEY64fcsIgmf8ATmH229vqpisfSnlXTAnqZiI3mdo6kvLu2wRWmGwETAjE/P7NrIWsmF8VkOvs7jpmQx+HBQiThiP6XUWKbkK4GiN3VLe38vYjttkRmd5iJn0syMV2bztrpCYkHR/n8RgDAJbBggZ0xiTLeFkvTOkqcz+WwhhHSuMXtLIN0yuhjq5NgASq5lLuYIkh+TSQhaA4BH/E2qx84s1p4PweX/iKSFsQNn0apjS27nBaq6k/YPnUlasMIB/KWmolXmI5F6Sm00phjOC01lJ8jG5NrLcUSe8wtKlfYMR+6zGXDHrha47lvE40afK9kmCV3JdSriJr43851BR1pJ7Ig7JsNhcjKSK/V4xFhMcSw2RjI0hbMx3v6N5lzK5ksa1vCutCU7wodo/aZRmyxTH3YbHLqVFSQxLv6edwg5AO9XiBtzUzyYgDRvrs5v57WhjNHPbhBxKumb1iIKwcxOHwq8Z3ChksP+gZisCM54g/qi08mLoV42YGTuDtctFIISCFwsPj/iimal1N1U8JrXEWQggKOXo6wpMfXPlLe6uD4yOmuWkeRztru2rPhQ9pYQQhAlPIpmBjcp2g7vIuFce4S2MUuStEMSix3+UwMwOksexpSQcFft8vllY1O+0HYOtZxlM8xZHu5Fdd18/dWmycqQpMbLGB9ZiJiYnzC2vxF2Hp37QdW0pj6lMkx6qrbSR1niFfqPEvKB7sqITExgwKCH8OeXNHNBcj7f2tBP8AEM2AT9Sv3XUV6KmOMY/UoI7NeN4+r/jJiCjaY3htV9ZsWqZTBYrKJyNcSGYh+iSozhhDElogA9uYwWiMFxucwMTcJk8awScxTNs8rJyUgsFxxAYGGV1MODOOUxERG0eI/cX7qqNU7LfMYam68+cxkPJ2ELsoNDY5A+u7D3/bGXcV+BiwYPA4ghWpSo2WMDqJmJ3idpfWRYHYwiCq3r2InivexToZWlkB3Qf1/g6xiOzVnbyqZlQTPz+yIxCORzAxYtm8oRU3mencdFOpLJ8s/cmYLAjOYEMnfLMXwBUT7X/jzQUMh9Y5TYxOZ9yXtLkQu5p1dzmeWcVduRV2wnaV0Y35PKWEIiMbDERDbiF+N+RQy2+foHsr/cmYLAmMKBAIPqHI8y8Y2IiIiIjaLVpVRJObMRF2wzKX+Y7zH9F1NLZ5R9DEZPNUth5RbVjc+q4UreuazYmJjeJ3jWcpIuUpFpws6BWgLsNCeH9YjAY3IoiDv1R/3N9Rf7k8UJY2RpZy1+mia4L6YuFPKyXOU4FgbRHbAQEQGAGNh/avtVq8bvaCo/jGL2mfdK2HJY4igRsqkt4mN4neH3jzl/2xu9vSYqoo+FQeKv8AkGVZs8IWXbsYfL+8ia1kZVe9G3djlSRlh+2e7zYZtC0JX9gRE/uuoXvsvTh6s7HTqpoVAQO0DkswYJIaEQbjr3LhQy+8jlalqHisYGP6dm0xDBjtyS1ZyERMrIxmOpck8dq1cN3Wcw1nNsCyUE8hmXBAT/QY1ao5MKBickJTMJUbJ7mSZP0LFcLxWcfO8FAwvprIlP512AgekkzP5tpp6DpXFj9/cZKcRjEbSusuJiIiNojaP2nxq71PQrMJS4J5t6iytmZ9ogUgVrqAZ7nuCKY6myZ15rQqPdJoCZS29JNYePoFEwCyCZxlbff6tpp2q886bzGaVUkwTGTu3/gXWntdC6fnR/xMRlkkMRRtTYCYPbuf1n1JyaRsVyheSw2U9+iRbHC1oQAZmRGIn92ZiAyZzxG1YxyrUWlrhtyxbfZndhePV3ekPyZiDjfaN/n+kxCWfeEFoREBgRjYf6NknCqZSPI8bhitFzeYk1OFrhEdwpPSqyE/prEf3V62NKoy0QyY2Ll7MxJlM1ccKKyxgUqEY/rEQjEkU7Qm0hxSKy3n9xbQ14iAHwBKVpCACNo0/HjO7ETKzXat1i2siRAqyl0zCy5T/UrvOu4Wh83hhNsM1j/rhLQcoHLnkH7zNvIKvbXtLA58Y57cv3hMYoZYreWYzqZDR7V6YS1bAaEMWUGH4bV2rTXLLDIWJ9XUoLZaWnFLqLHXGQqJJTPQ71JZwB2FiYMBkbgUFH9XNALMVaEvEY1xFjhVPx/VffSg5CYkibdtmMSlUgIU32Jhls52ZjFzMEkpWQsyNctiGWhOTkGSLFSMV76nn24iRL9z86BKllJAMDP9USICghnYv45Tr1RZYngVd67CQeqd1/u8wU37I42mPKwVVlTZDInf97ZqKsR5+kkov1o4VrhrCuzMgcunIBJ3brpr744lMeAdUOHfvV1a9h1JPzkAjX8IzBRMPyhCC8Hh0l3Lb5ssG7jK4QtO0DllYu8sjUqVWq+Qz6kikTGBmLlreL95nC+vHJiVVwMndK1WJFpnMxP9XLrlmMtBHziy3QQ/iN6VzsZiMnkaox4KTkclLJ4pQbCnJ8JkXJIC/iqNo+kt5IYHlM7C27WV4k4mQyFUy4wXGZQkj7khEn+CYifmN9CpQTuACM/i3neI28f8DMbxtqOG/wBYwY4+a/tFhWjiv91cKyNVk1BgrGAbSkGqr8mOs1lWV8GRq1UbVPicbj/wAkQ/bMxr3VmB4909iMy+4pL8LD4LI9Y5BGybJx4wwbViL+tm2knFWTH5xgxFbePn8DbLXsirRGWOodLVxDuZCZc5ONx6I2VXWOgAAjiAwMEIl90ROrvTmOtDPAPbsX0in4faYwVdOYhY8ezzm50xjrAj2YmudTF1qyAUQw0oQiPhYxo0JYPEwGRfhlF5QUhL6VlHkwmR/qQJT8RM6iu8vhZzqKVuf9ktRjrs/wC1OoxNyfkYjX8Ht/8AcNOrPROzQkf3VO2dVvKPILMGBBhO4/ufnVapXqLlVYIWGmqW4JWyOQ3aDKs8o+pX/DxEDEREbRUzNZNhGMgZI/6tyqu5WZWbvAEFrEuOvYXJD/Eyn7UFMIyKmnwOJWWrDGPYNKrHN2LxVfGpgFxyb/Vfj6r95kOJNwzhL8ooMYw1mfkgjUYVv+WjqMJ/6najCp/ywtRhqsfJHOoxNOP9MzqMZSj/AG99RRqR8JHUVq0fCgjUAA/AxH45iJjaY3h+Kqt8jHbJ+LtK8jHcGYmJ2mNp/b469Nc+2yfyYmJjePj94QiQyJRuN/GyndqY3V/w/tnose7ps7bqefWUor3Bldr+sxS2RxYMFGS6bqWxk0T2HR01mJntzZCF4rC18YMkM9x//BurIfGzQgtPw3+UHptWwouJrmJTi7bfMj2xTh0B5aUsksdTKNu1EaZhq8/YZBo8K+P0zEoPHXA+VTMEBh9wyP8AWgDn4GZ1Fd5fas51jCtCPZesoH99fxe+7q0eZjbxPz+z/wD1+yUlri4qGSmvhR3g7MxMgAgMAMbD/wA7MRMbTG8HSqM8kodzw9Uvtkg0eFZH6bILR4y4H+3yg1NXOxgQ/grU3WS2CNhViagDEHEsKMfSj4VGhq1h+FBGoWuPgIj/AIO9jRsbsVsLTAgKQOJEv+CWlrS4rGTmnieUc7MSOlrWoeKxgR/8FOpWZ96hnR4ioX28gleGSLORnJiIiAwIxED/AMPcortDv9rJxt2JmO3M6/ht3/7U6nG3Y/2p0VewH3LKNTEx4nx+8Ulri4qGSmth4jYrBbyta1DxWMCP/GRvPzG3/hZLA/uGC0dKof3KHR4mmXwMjo8KqfsYUaLCuj7GDOixVwfgYLRU7Q/Ki1IkP3RMf0VUrTduC52XhXTG7GCEnhrEfYYlo8bcD5XM6NLQ8GBD+JSWuLioZKa2HiNisFvpa1qHisYEf+D/AM7f+OzET4nzoqtY/uUE6LGUi/29tFhq8/aZDo8IX+hsTosRbH7eJaXh7JffIhCsPXHywiZK6yFfprEZ/CddB/esS0eMpF/t8dHhUz9jCHR4V0fYwS1Xwwx9Vgt9LWCx4rGBH8AS2TODGBD+kI8d/Mz+KP8A38f/ANCl93jPd25f8tO+3j5/5n//xABKEAABAwEFBAcFBAgEBgEFAQABAAIRIQMSMUFRECJhcQQygZGhscETIEJS0SNAYvAwM1BygpKi4UOywtIFFFNgcPHiNFSAk/KD/9oACAEBAA0/AP8A8dmiYRxHL/wYDEfoS0jwUnz/APBlHDt98VKOxjz+fD3nn+yAgf8AgyMuaArz2XgfP3A3Du2T6jYMSjgdgxQof/AU1RAPvkNPuPAEbA/6bIHmo2XT5IuP/gJpB9EWjy9+4PTYwwD7geNhBgIASNl0+Sr5nYIpzTgDH/emvuBHMIkT3q6K9imCiNhaYQkHv2XThyQJCBHmror2bDZ+uwO9T7gIRA24q6fJR67C4IAD3hiSjn72p/7nxHYgACiCJ0TXV/PYnAjvTXYc/wD0jiEKAIEH0RaPLa139vRYQnCY5ouJhU8wro8tjmkeex+WmxmZ2SPNXR5bINVepKhRsvDyPvuIHqgK89gguH54e51nDX/t/VHA/orYeOy1Ejn+Z2kU7EKd201/PegQVdHlsunwUR3U2V9xzyRy2U81dHlsKGACMDxTGiidWFeCiipQ7XGuwOlx0G0G6D+eXuXYA91m86PX3GiSj1eX/aoEjmE2QdpwKaZ9D71m4HsREjtVm6vJOEjax5/PhtLa9ydomgAdmwgqT57JPp71PMK6PL3CQo2U81dB8E53l74BPcnuJ9PfeYqhUq0cY5e4zetT5BDYaN5rEnn/ANpneYNjhCY7zVqIPP3nCEwkJ2aGe14DhtNJ2tmDnTY15GyT6bXYDbA81dHlsaPGmwuG2nmrkd4hGT47RUlHMbSI76KJ767GgnuUwI09yxEdqebo7U0AbTRo4lGr3akpoq7IcEBMDFWVTz/7RJgcztszvcQnCR27HxOlFZmU4A7BjB2HDZaCRzx2kxROEjtTmEefvObPlsD/AK7A/wCm0CI12MGHcqeYV0eWyY8TsLp8RsBhEgLdCujyWA7URJRp3oiT2122jq8goACIklOzTRG3BvMp+85Mrd1OwGSBntNG80/edKwaOKfvOPE/om4gZf8AYMxARw9xxgA5rFvMKBI47CIKsjA5bXAjvVm4jY4S3z2B0GMq47AYPntAnuqgI7qIyPec0jz2Ez+e9PMck1wKIlOFR37XxPYqeYV0eWwuPlss2yfz2oNPknOJ9E5yLggAFM0QoE939vVCg22Tacz/AO0DJGvuPOeybzuXuGg22Hi7ZY48TswbzKiT7j6NCisa7MGjUq0qf2ycEcPfOIK6zHenuWdRyzRFeeex2Gmx+49NNYRaPLZbDx/I2MMO5FGo2tgjsRFeyiGiePApjig/Y/PtjYTHlsD479jmD0THSZ0oqeaujyV0+vuU8wro8kGnyRJnY6gA0UKJ70XeoROI19xgnz2hWjqchteakZAIAkBNlxnXZN1vuMqB+LY6jeZRq7mdhqY2M3noYDUoiY02CpKbu2Y22WHE/to4HQqyMRw/Sv37P6bGmQ7YKjmERddzQJiNNlm4FOEpwhWRunlkphwGHbsII8Ewkd9UQYTSR6p3WKDgdjXfny2MN4c9jXA7Cz67IV0ICCe/Y+N7nsp5hXR5K6fJSUagZ7IHmro8lPr7rd0fns2kQO2iiT212Ocbg4BWdG+Wx7sfzzTt0dqiTzPuAwY12WdLManX3MGjirSpPBM3nc9r+sdAmiNlpQDghidT+2xQg4OCio4/oC2HjyjYcNlmbwRxGh9x2+xSBVESnAjvVmY7NlsIPMe4agfnnsa7+2wQfFFo8kIPcUWjy2iD4ogIiPPZdPkqjx2x5TsgeaujyV0+SBKI+uynmro8kBPnsca8co2ASnOTiaDM0jY939vXY7dHagBsApzNEantTN9ytDAhAVJT3c6J9GrFx4lO650ahQbDgMzss6u4nYBTiU4y0RFEMBqVaV5DYeq3Up2A+Uf9gnAe8yrVAkcdlrVnA6bCJa7U7GGHcijgU0ROy1q3nsZvA8k4SihUTwT9088NloCR57InuUR3UV0+SEjx2xPcro8EHRsIIQcRtg+uynmvZ49iunHtV4qPrskKB5JjY8hsaAe6uyI76I171ZN8fydjRPnsLrx2uN53IIJxhvJNEDRCpKG7Z8kyrUMBqVaVPAK0oOA1TBVxWFmDkFgOZT94+mxm9aFBMwZx47GiV8DcgP8AsB9bM7LOojRYO57MgmGCPdZvN7EceYVkZ7CiJCIpzFU3dPZsiYTPHYcUwy3kdhpezVmZ704V5rAnkf77C0jwQJHjsa7+3psInyhEEJpI9UHem0mRCcYHE7DI/Pfsp5q6PJBpgdikqStSi4eRQAQp47Gj0A2EgeKAAUx4nYxseWyzbH579rBcanbo7VEnmdjzvcAmiNllvP5polP6o0aid7iEBAVnVxyJ2ZDUq03ne+2l7j+xD9yGI9w4HL3GbzSsHDjstag6O2vMWmw4bHmL2Q2P3mIiCrMy3i07Dvt2Gh/PI+47ddtcIVmfNDAjFDYHHZe9Tsc2nd/bY15/PgsQU0QSmmK7A8bA/ZTzCujyV0ok+aveoWhRdhsLvU7HGB57HPCAwTjnTXY93589hdAPadjRKeS49qst53PYFaYcBsGA4q0N4oGXDXaRAKxceOyx7i73z+sdkAh+02658tuFoEajbZ1ajiND7lsYdwdsyOhVnRw14rUpwhWfVOrVZm8I0WDhxQq3mm7ruxWZE8k4SEww7i0o1BTTDuSKxB0KaIJQxKOBCG8OxCjuYV6sckap/W9fcm8Pz27H5bCI8xseJHntveg2MdLtdgeNhgeKDR5K6VJ81fGwG8e/+2wuUGBxRJMbC6veNpBAKGJ4lASnOJ2WhjsQEdyeZEaY7HurywQoBssqu0n330aM+axcdTsJAdwQxKBhh2fG/IBZnMlGoGyzJa1vzcf2izDiNpoQhvMPA+5a0OjXbBjy1Ry4o+BVnTmMimGDyTOuNWp4odCmUDtQrPHi1OEq1q3nstaO4HVOEJp3U4QrIx2IinNWZunlltIMJriPXY7eajUcwgADtOPHY9n58tgqSjmg71GwUlOoBrsJkxr7l8eR2AzIQpuiR3qFHqr/ANNjckBQapxmDj7jWz5+6QQAgKjZYjx9yzEN57BgOKtDeKFSUKNbx9wVJWFmNBsfRo04o9Z2qf1nJohOxPyhZnUp1Gjin9YnLhtOE/tRswee01AzRTMDq1HFYxss8Rq1OETstat4HTa/es/omVbqhRw4o4cCrKh4jbabr+ey2Hj7hN5v57dkgU4H3WkFEDvR3T+e1CpTgWyg4xxTXeaIB2Xo8veDh5FELQ7CKDtWmlVenxCmPdAknY1voPeGKgmvBPdKJgcTsfQDPmjV3M7Gbz9j6uOgQTqNbqoqOOwVtHDhkjRreCA3gNdE7qDQbXUY3Up9XH02AyJ12hN6jdR+0jDXt0RqD7jK8wEcRoUyrTrwQo5uh2NrCOIT96z+mzFp0Ks6OnPihihvMKFHDinmHt0OqOBXVtOSOCYagJwDm88UKO5hMIM+COPMKQOxOAPejuuPuET3bXAgIEwDomuCcPNB1dgE91UBHdROoEw3ufvXh6q6PJMx9xhkIGQQgIn3ASB2n+2wU8f7bGugcdkS4bCRenRHdaFEnmapgiz7c9gwGUjZlOEp5lxTRKtMOWxtGDTY6jBxTqvPFRAaclZmv4nbchmSj1G/KPdyGZ5IdSz157DjGXP9mWkNHA+6aEJ1bJx8tgMsdMmDttTT8Ltn+K3Uao4bLWrOB0TKtIxWDhx2Zt1pCNCF1rM6hW2PByNCp3RonCCrIwOIThCaIqrQXmjijiChkEcUKAIVHYhunmNvRXtJHy710XRoCFaNDgeYWk120MciojuoiZH57djgR3prvNB3uYWbMLzjkg2+2BBFQIEZbDkhQBT6lRA7So2OMNnE8gEcCPXZq0g+WwJ7vJBOd5f+1BgcUSSRsJgbWmRP6BxvOPBDDZjRDFMpZjZaYnRuqHidgqSh+qYfPYKkphq75uGzIalfBZ5AcdmRTqud+zn9TROoU6tm702CrToUzHiNdjOs3IhOEopmB+ZuqKtKsJwB2Nqw8QsHDiE74ePuWdW/ReTgmG67a2jwMwUago4DVWZqeCcPcK0GyzaXO5AL/iDHgRW64m+InkrC83o7xiDeukeN4LpRbacbr3Rvcx7pEE7ce4oEFXRXs2DEscHR3LoDXOdlN0f6iO5Wjfs2AQCJvO9PcfjKKAgbC0h0aBwmEQHOaflMRe+YFMZNq28GtjMuea1V37YAE2b3DGG03RqVYOAMCJBnEa0RaY7kRJ7Srp8U6TtAKdJ9w/Dnse6ByCAkqd3iNgqSnG6zkFhtf1zoE0IYDUq0qeA0T6N4cUMXHVWZl51OiGA2HRBZDMlf4bNOPujEoUMfskUMbRVp0KZR49Vi12YKGLtdlnjxanI4omQDlsZVrvRNo9uhQqIQ2W2OgdsYbxjREV557X/DkFaUfzRwKxaSZ7k4QVZmnEKzNU8eBQJLT7jDrjltADnAZtaQXeCNqRb6A2bxh/CrYAWl2okQGnkQuj2bGxNRcY5wk7LQkA+8cSgRPero8l7J1RyTmua4CcCIr6LptpvxEtZN3PLd8VYC0DnDCRu07T+gbvWT9HfQqyfUnNmIb3q0ID7RrSDbOFReccfJWwHtukEbxP4Z6rRqVbEutLUk36C8L3D67XHyqgBttCAOxAAd2y0MdyaIlAw2c8p2aBGhCGA2WpjsTRCshJHHY0K0ryGyywGTnbAbrAcgn9Y/KEMTqdjhIGw9VoxK+FmTUMSmnKhchTiURI2N6z9UNjshgBw/YGfvv640lGoO1nXHzNR9y0MsOQOnuChhN67fmajsOJ0ThLTxyVmbrhy2P3mbHdUa7DgdCrKnYrOvYnDBCjwNE8eBQmvasJzRFOeSYbrp2Wog89gdTgntLT2iFaODrFwkEPjdcNJbQqzaGtGNAICspfZAUEEBtf4XbA8e+cu1CaHmrSzc0ZVIgLo7w68ACTe3S0/yqwbdYDi5x+pVu4hpOYBlx7XbBi5xgIZtbA/qIWRJAnzTsBaCBOl7D3LKLtS01IHWFYTWFjLB2+3cpS91qLpDrr7a1oQMmtBFFa/rbZ2JmpA4bWmaIIuBMaDYwXjzx2MFRkfydoxJMBGoIwI92yF1vPYcTrGxm9aH0QVpQcBqhidTsdRrfVOq92x1GhP8AvhGpRwB+EIVJTT/ADFMGAQ/Vsy2Drv14Ifso0ITq2Tj5bD1DqNlqafhdsjdTPELI6FWdDxG19HcEVaHeHylGoKcEyrXZQnbtoPVHA7WVaURXYaPH12uEFNJg+5aAOHZjsZvBRXmE6s5TjssHC+4YxO67sKtGyRo4UI710hty059T6bRmfTVZPtDdE8hJVn12NYSADrBlfMyv9LoWbcHDm019yzaXOPACSra0c57jQBrf7kqxdAHD/cfBNENaMABssJDbOYLruMcT5Lo4+0t3AOhwy3gSa0xVj+vtwxsyNP7K2G5Zt3myB8Taxhqvhe6SWDAEHNqOGxwa0fxOA8kWXv5je9VYlzmTXO42OQ95tAdjnQPNNBKefAbbQMDMwC4kEwnVsHmchIgaEeKPVGLjyARpNRUZV2EQEauPFDElHAhGjRxT6uOaGA1KtKj8I02ZDUp2A+UbMhqUeo3QLIZlf4bDkNjeu7VDAbBiUKOObkFkMyjkdnmvg1I/Y+LToVZ0dx4ppkHYU3qn5m7AakZga7GdYahHEaHYaEJ36txy4FGhCd+rcfLacUyrDq3ZaGHA4NPvNMGf0BxQJM6bbRpa7k4QgbzDlLdB+IGVYPq7MNdTzhWjQTzwd4pmJ9ArM/Y2WToOHLUqxH2RaLoMVDfoV0SR0uwiBaDUNwyqFZC7aMc0G6T8s5GFZ73sh1mjO6c+Ss6WjAe5w4Hb0lwYAMboq76J4abRv4jh/USrNoBOpzPadgBIHJNMNLj1ZEkovlxivVp4yi8SSKkgV80XAVxa100HdCafZvPA1b4yrtx8/MzdJ7Y2OtGgdxKbZMmcoaEXNAPA3jHvkGFJJCeQPVACeaInhsrvGnxNiSmANvfiBLrp5gq1DSGDCzvGgPGMgnzaWg0Lsuwe4+pOUBBWWE5lDEqzO6PmOwVJTTFm07AhRjeSGA1K/w2cNTsHXfrwQ2DEpp7XFDAI9VoxKODcm7Mgsm5BZDM8kRuj9iMOHDaz+oaLMaHayrHeibR7fctfA7T4Ifq3fMEKtPFNMAnEpwkHLlsszXknCURTmrPdcOSmH6o1B2vxGW2zBc48AmuLOidEYYvk03tU4A2TCSHj94uouDgfX3RvNJzB2A3bQjO7Ud4kLpFnu/xCnaFYWhocg7+4KsCTbkYAg7/AHYDimANaBgAMNnSWEWwmAboNT3BdIBNvccC0F0zN3jBXJx/0p7Je26bsmb1KaSnUc04tcMRs/4cC8kYC4ATP8XkrJ0WTcpIIA/hG1sANNBLjFV0mWtsx+EkXp7wjNGC7XvKg3awJ4wVauve0AklwBcZdjBFQmvYedY9U20cBSKQD67HWh8v7pli6OBDU61M9jW/oW4EIVRN0cBss3EmcCQWuunnCtWh901gmqsTdZNQHA3WkTwbscYAHvHEpu0YAIVJTcG6lDAJh3Ga7PjfkAs+J2sNGaoUAR7m80cXHLl7hpAwCybkNoEuOQ2BA7p1++nAZ7CIJ2GgnY79a31RqCsHtzHFHApvXGoR2DJZHQpmB1G0Va7QplHDXjsZVpGPJEVRoUa2ew0tAPNPHmgTEae9auazs6x8l0RoY0zSYG851BkjVzRLz/M4+i+GWiBzqmVuNmf/ANZkFYNFbryMccDw2zDuX5Oy0EA6HI9hXR3ktac25xyPmrVl6yAwvHeb2VK6SZBONwYd5qmm6bZwLbNrgYIrE96xLLMmAP4bo8U4br2kQe3eTIvPe92Zj4YTwHNJLiCDUGpV4igEBpLmlo4Ik3Zwv0iP3mpwLbEavOfZiv8AiZD7XG8GTus7Sapol5HxOOJ22rr5aP5WjtJVoW2RdButgC843e9MBc4nAAJj7rXO+IRKeGUiKG8x3grGyeB0YmL7mG6DORmqYb7rEPIcdXXRogSx4GF4ZjvXtHGeQCcGs3SJ3iJEck8G0MZ3jI8NjgXNY0SSPJCCcJaOLnegTus289w9ESRuXmBhy+LNUDbcCn8Y9UcDtKNTsfaNBdpAJVnYNcOd2neV0hxeTnHVHlsscOfuPoNvxn5R7jOs7VDAJ1GN4rMr436BZnMoYkrCduDrTII9ZxxKd1WhEzyCGazecSs3HEoYkoCbxw2FfC3UrFtmtPvTBvNyLUctDsZlqEKOGh2/CdCmdU6hHEJ5mzdpwRxCeaH5Tsed4fKUagpnWHBFNyGewYlHApmI1CzGh9xmmewiCm9Q6jbQ2jSYDW/Uq0aHAY9YTsZgM3E4AJhutYDjHwt9Sm4NaI2kw1rauJHBWO9a2QaQ57Zi+BqFY0eNRk767BUTtvez6RGYIif5ZVh+stQKuANTPHAd6aAGgZAKwcAw5uFQXd4Vq1zCRiLwhWYi8aTJn1TDv2RlhJacsJVkfZgEEDdpScQraXOOQFoM/wCJNoy1aZa6PhcWrojYYC01dMhz9ezFdGIFkSCLxHV/3bWNLjFTAEro8ezbofgb5lPN11vaGrQaTQUVpe9ob14Q6haKzCbJipqeaY0BgANXAmhu81060c61tThZMo4C6ZiSSukWbrMWdA4PdSW0FAnF1oS6gice4Lo5M2x+IuiY7kwQ95Ju0+IkzCpAPVZTqt4Jgkn0HFWYkt0aOq2R8xr3ptQ1r7xnlN4IYVAd/pKtLN11rxu0F6bxjNWLyLO1GjhIDtRKZLbEOyc34QdCMPeNowVyzon2di08oafRMs2g84REToiZJ9zBo0G13WO13WchsiJRzRq48djcBmUMAhUlYOfm7lsynBOxcfRGl0eqysxgEMlkMysWszPNDIbZ3RkPvr+u0ZFGo2O/WtHmjUFASBqhRzdEyrD6JtHt4o4HQpvVPzBFD9W7UI0ITq2btlpj+E7SjVr9Oey06w0KcJHan0Og91tCeGxohg1eeqO9dIex9o51XNk0bwpim2TJ/lGww5/N015wEwQ0DYBeLLwkDWFMSwCOy8QnEvsrJ+pcXOocYTi0Wtkw4FxukcJBwVod8fDfPWaeDlaNDhONcjtbi5xgeKaftbdwoYNDy0GKxe84udqVBPSLsi0dGQOhRLWg0ykxrmhiSmOlzHRDxpJVoIujBoMCABieSvXejiIMDGSrAXYcLzXQLgx1q5OJvC6Sb/HmETLIiQMDAb6lNklxxcTmdtoxzRzcCEwm0s7MiQS2GvFKyMVNXFsNcMq6804Xdx5eY4NBKB+1t3Q0NpSBWU3FxTGkWTbTG1/FdLSrKpaXXqDIuwA5KzIBIobSMgMhwT6OjIYmTyqUYNo/N7tTssjNoRg5404BdKN0MibQzIkGKUnNPLHGojfF4XYzCc0B7KhrXZk/2RBLSDLTAkgymvaO4vCsC2+4Y3Z3TzBREPbo8Yj3XWjQPEpxsgeEMKAA2Whhk59y/fb9Ufxj6rCQ9uPeuBn3tXEAeKPUukEvOjdU8xZml58ZyfRObLicandmOHuhCrW5uQwHuHE4nZnoOa/pHLY7AIiLgwG3ijhNPvxxCed13ynTYeu3U7G9YfMFmNCmVYfRNo5qbVp9E2jxxXwnQqzz4IodRxzCs8uCOI47dDXYU3Cfe6CfaWsVDrQZdmHerS1aB2AlWTBeOQDQrKr7SSN0ZwGmO1OeG2RNbgg0aeVNliXXWiohrroAFO9BwaQ4iodSkAK2ZeNoQJLnVGOitLQNdbMN7ednkplr3CLvISa8UWEtJwDhVp71Ym812V12XftspLSBIrGIpoi6LOxaC0Occ+torQl7Q8yWtOAr3ogieaeQ12QiaWg7FaiQ5p7nAhDqhxLSOyHJpM2YkgjS8YjsCdFmRZ4MHyCMynb9qfxHLsTxB1ByI5J7qOblPy6HgUdMRzGXuA3nsbRwcPibrOYXxWtiTZuJ4twQO620cCO26QmGXOtPiANQ3q+SaQQ9zdwHVt4VIRM3Xk/3QMuAdQxnElOoxgy4wukQXzi0fLz12WkttXNxb+EHzVq+/avzcWtju3kGMuWjjduvih3o1VmZZddekH5Wqxn2d8SL10XS7zVkBSIkRdJ4Y0TnMMDASXmArRpa7kRCs3l7Ty3HeQ91lo0nxb6oeyd/Td9U9jXDtE7PZY4hvWdqiYZbWRBZ5fnRalxbPO+1DG7aMd6KKXodX+G6hAc1z57LjyfBAlto3EBw+qz29Cs3mzAE3nN4fiPguiS67q59Qxv7xCaY6PYDCGmgj5R47chmUeoM+1BDqWevE7dG5bdSs3HrO5LM5n3dRs49Uch+wChWzdqPcP61nqjUFDr8dhpaN1CIkbW1afRZrQU2RTmgSDlhsOG1glzjgAE9xbZ2hIg6SMp2MAug4SSG+qtbTruHWaBSO0lG0vvbwkR5FPaWzzEJxJsHWnUc+LsV1GCNLJ7QAMKCGUjiEMRoibzhUAu1BGE5phoxpknuAA5qxEWTm/CKCK8kzea60IAbFZpCsIuWgxcZidIOStMf3QrRr2jLO+PAbSJZZCrnfRMrY2JIgjEdms48k0TDaiBo7A7AYsrX4pPw8QmTDCJbBrIIqE4fZlpLgT/LgnENcbOA8zX5iQrN14vOJBoaeaG7aM+Vw9CrNhc2a1ATxDrpIa4Z0IPmpo2zLjdGQLqINHtCNezYwS5xoAAmmDbDruA4/RMbD3OAc58CLx1KZEENAdjrCMx7QlwxylDJEwKSom8rM/YzgXjD+Xz2W0w84NA041VqftHuaTA0BEqyJa7dcG3XZyeITWtBPyWrBmOKsY9iCd1oGnoQnMO9cF9mYqwAqzY5rCWhoeQ0wZGKfaxOe60fXZ0oC9kJfLT/AFCfdaGuHY4JllP/AOp1fBqY32Z5s3djNxs1yDP9KOMVB7CuRs/8kBZXXBzcMpBKy3R9VaxeJAAF4wKeKFTmSTmTtYJc44AK0vBrngAuJcCI5VTrUOtSwR9qd2Kc6pg3nauNTHCdowacAshmV8FnltdRz56oR6zjidhwYPVZM+EIICS4YbRiV4uQw+/uoJ2EUKbVpOYVnVp9Fg4aHaVaGWu+U6bSIITv1Z0nYDv6wjXYASBqhQj3AQWnis0wEuccAArF26MDaxn+cFYAOb7P42jzOcqz3LYfiGfare0A7G1+is2hrRwAhdGYWNLjdF6IivFx2Wf6u07cCmNLrO0O+5rQK1EHDVdJM2pMzTJDFpxCI3SfECcE2ntHWbyTGt2iNCw7sj9xtT2rF5FXOdx0QcJjENwVm433NiYi6aDRPMYVBGTtNkQ67BBAwxVmC50w0QNSmlzLJtcT9GqNyzHWeeAR/VWZwa3JXXeSsi5pDiAcb2BxxTLQOIaDEQRjEZp4mDxXRQ5/smVNoPkTpY+wtCA6tKaq3O7acPm5jMJwkEYEHPa+HW50HWAPIV7lYWZdGE3ROKa4tLQZ0Mp8dhkUVwbBGU5hdJcC8gjdumDemqsxDRs0cAR4oVcbjZJ0FMVaENsuj2dGwMCQKTqU9sPu1DmE9ZvKERIye0YcHBfic4jzVy42yYRQEhpwoKHBPl/8xkeGwOME4brmuFO3YTdNCWtOEUxKsny9ljIvNJaGy4HIyulWdmTaz8Tt3trirRjg3nGCs3Oa9p0fX1XRrQvbZmQ13w3hFdFdIFq3eI4ks9QnEQYpRcDOzUUWjqpkWdm51BedQR/C0o4OaZB7Rt6MZ6S8YOcMvQd6dDAGClmDpx8sU/8AWWmn4W+78LdPeHWtMhyR6zziUKlDrHMoI0E7NDs1yCcOqMB98OmSOBRz0Teo75m7GVY70TaPbxTqWjfVGoKcMUzB+UI+CHUd8wQImdE4ShVp0KYYBOfuvo8aJokuOAGq0Y0n0WTXAtJ5XgNvRyD0h7TR50HkO9FoaGtpAGEJkP8AZybrmumlcFaAl3yhxdLQOVVZWsH+IT/pTwHNcMwVDrWzBjd+Ig6poc2eDXEDwVtu2QxjV3YrVzZYSIaCCSDmSmkm7nBzQWYAp5rV30C+UUHhsaCHUx1AVrvWQObohze0LpkGz+VriadxptcA5zgOu6cOxNAHtrQy1up6oE/mFavDC53VwJMBNAACcIJGK44dwXIbOlNLTzOHi1Gri0S1x1IpVNMtDnuidYIRdLGtktYOBOu0uINvZmakyngue4UN1omCdNUHEEailFcvci0hycwEcRsLTA5Jlo5vk712sqT4Lowd7GyFC9wGGdde5PMSDF0fLF05pjSWOB3hEXoJEg8FNLwk97S1OEOi8ZGnWXSLTetDV7o3anm5MaGjkBGyQZieu4N9E/7OyIyc7PsxXSJc5x612d0eqsRvB4Ba9p4dq6Oy5YdHjdLwDcDdMVaEus4F03DhI/NE+85vY6R/S5dKHs7Q6GjT6FWTC4DUgUCBtCy4269zRkC2DjRO/U2Tjff24IHqtcD4PhZG46B21CdhqCnWntKAEYBsySDKcT7Rr63WfDP4tlvQQf1YPxH88VbQXkbxvOzj/KFazekzcnHtOewiW2TauI10CtBPsid8RiCPcdgNjjE6LFziUOs/N3JDbwWTcym1DczsyGa/qcvPZp97OITqwTVp2MqxybR7eOxvXb8wThUJ5F3hO04DNNq13FNo8arGvuO3geO04HQp7CwngaeCtaWds8S0QaOHA5rFpb1Dzb9E2Ay2cQaHAOIy496tB7OzPF+fdKth7V51vYdw2PFxxOGJH+paCiawuBzDh1Y7U9pFnZEk1nrAYBWjfZtHF9FYkm0BEddxIhdDaHOGUje8yF0FzbFo1LiQT3jyTmsNpZjK8BXkUYDycQT6LVagym1JQMHmiZtG2ZEzmJ5qwPtWNsgR9kTImcSMUwFzYH+I0Q5vJysN20vYkfCe73HOdTOZYPesLQE94PonAEEZg+41pJPISn2pkPF4Ua3VAus7Foo1oIM+EIPPkFaWbgR2YpgcGniHGiFCEUy0Dv5hH+nbYGbZ7cyOsezAcVaObZw0wbNp45EqAHviC4gYlPZGG650FpFM4hOJLWESe6RdCOJDiS0cN2R2LoUudaPOL4/3FWYJa53xBpg0yVrFwN612d48KKxN4OdvENYb8SrAl9z5hFY4qzaGNcHRMUEtjRdIkNsyCDBMkwagUomuPsLMkgTS8TwTmglhxbIw7E4hlrGcY97VZ/as/hFe8Jz22NrWst3p7Q1ezDu1296qxN60ZMOAkG80DPLYyH2jx1XCd6TypC4hWdu665zWzDssOCNSGgDyVpRlm2tyc3LplSTWCTXsr2lWhvbx6k+sd2xgLnHQCqcbtlZnCOqEXTZNAyVm77UvGF7ADu2k0Jy2BDqszdxKGAHuZuyCOZwHJDElDG1Pos3O2xScEcXH0+8uoB7mp2NyycNEKObmDstDUfKVkUBJOyzy1CIqNCm9aMwh1hoUagfoBVjx1mngujktsyfiY3KeHkrNpdYlsXrw+HkVZmWWdtIqNBE5qzbdJbhTDHhsYXEPyvbtPBWjA5zeJCc9jTymfRCzYB/KEaua4SKVRdDAMABedA5AhWhIszoXGfNwVt9uc6NII8GprTZuBr1TdjuRqy0aIgjhogLvtBBBB/ESrYVAMjlIzCtMWxJOcBEfaC6ceEOVo8h9q4xQDANwUXY4RCs3X7Pl1fEXV03LKXn/AHj3LOzDu2XO+iGZWjd7yRwddIHfVauIJ9Voxv8A/Ka0uax3VJAkDFNeWkPPUAjdbjSPccy4I1fu+qLHWkcHS4eCNo7/ACtQcPJFpHgr5unQmEOuNRrscCcMCx0V/m2WwLLJudaF3YuliYiSxs0nl5rpBbb2xdN4Sb0Gc4bsY03DjvGjceJVpL5IAN0ndw4KyaXkawJhf8QtItH13LPGZ43jPBMYLN17Nzmtc6I/EulOu2bLWTcs43t3U4RkrWri6pAxujgNlsYs3DrADrGeSuXnudQwa73ILozhdBzDeoIOuJ2AX7M/ibXxwVgbjpxu/D4K0cIAPVcQaR2I2TANKALo5uPIlsxgD9VZwGWhAF41wLaFOcGhrceJ7ExhfzpRWloSXOMCGgVQpadKdRoH4Z/Oi6QD9o6pk/EZwE96fvMafgnXjwy2DEldJIvEEQLPieKshetHA0MCSmEMsLMaj6JzmEf1n3D1WjEodSzy7dgxJQ6z9n9TtmTRivhsx6oYAbRiSjs1NEfugqSjgdjeqfRMo4HPjtxB4r/DdkRpsHXb8wR8CjiE79U4+SOIVpPYPcNTCYanhsNLOzmrj9Fi1rJYD3b3ehQl2P8AOPVN69mes0/RHqsFXOPAJpAa9+D+XLYyGktE7wNLw0Mwsx7gghsExPEClE1t4PaJvAcFQwYJABkOGvFWbQwOOJuiKq0Y5gdjF4RKbZ2l2gEvI3SDjUK1eC7jLi4eACNiwEagtEqxtC4d9w/5drhBaaggqxtetFS0yB5bLF7XDt3T5pzG3uYEHDiultFm7t3PChVhaY6TUeLVatDo0kbbMXC59A0NAafFCvsr0D+Vu8Vk6zswJ/idvL5rV17wbC0smDzNVwcW+qz+0KtRes5Obajvb7lu4Odym63xr2IWdxuXw3QrIl0Y9ZuH9Ke6QTgaYSoPVBKF0ycpkJtBd3yRhW7oiT9taDdbyyHaV0o/aluG+6HN44oVJK6AAW53oMMA/edVNd9m3K/H+kLozTx6rbnm7Z0m2a3sCAgDknQW1gGDN13BOaZjAZhjVaWwYxxHUJ33Fuh4pwZeGbnEsvHt29ExIwN0/wCp3gukkBwGN0mI/iKAm0cPiecTt6Q0Xmj4A86cHDuThLD+JtWpriLG9xxZzlWji60shjE3i0jMcqr4gS6P8kpnUsYgkAzF04cSVakF7W5N+FtMyVZmfYsMl5xLjln/AGR3WsZUNc7Nx+I8E+oa6tw6nU+SAkk0ACJj2kS3+FufNRIsm75p+FpDQrGmGLjgFaAkDOJhdFYS1mrjJqrZ7nTqBujy2nIZc0e5u06L4WDHtXwsyahiSsDaH0RxecUMypq9wpsyY2pQqLIeqHcs3ZBaCgCFB90IghPOPyna3xGiFHN0OwGGnVDquzBQ6p+YbLTrgZHVHAhZHQpn9Q2OBE80asJ92wcW2YOAAJDR67P8R5qxo+qpfYPiqCAQ3I6IkBlk+WtJ0JyjRNENaBAA5bLR92/EwIJpNE8CtauImgEp4lrhUEFWjwy/Q3RBOfJWjAXR82BTg0F1C51IqHHHkg0AsxA1Csbot7NogEYTTXAq0Ej6dmyyY1s8XOs2ehTfZE9rTj3o2TK/whdMENOUuj/U33On2d08HN//AJ8dj2OgfiAlvimPJuk1uujBWRNoBldkerVZMvBodMkbwIwzarBxEHJrqt9dtq8+0LSQ50kuinND4nVPvWbgCfH6p4DmngRI29GvBpOAjcbHiVBNwSYisE4SiHktGALXx6pjA6LOG1DWcNSubf8AatJaP9KFQXku8DRDAAQPBWVsBPPe/wBK9mY5HreCtrS+9zaueAN1sRkaqwsi+0A+YC85W74nlvO8XbLCzfbEcTQeW1rSe4J3SBd4nd9EQwmsYmz2QGs5uMT2J82jzAwGHgmm7ZE/N/8AFu1olxOAAVkQA35o6rfU7GjrRR8YXowjVCA0kG0bHBza964h+WKeboeAQR/G5CTJqAT8s1c5YO6Q6WkjMSPIL4rZwrP4dNjv17vGDwAqUTL7QgAun02P6zSBB5oCAw0EaBPcGXODiC0KyY1s6kCp2uxKf1bNsTGpnAK0EgHEQYI2P6vDinVc5HqtGJXw2Q9UO5Zu+ELT4R2LJox7kc/iKzccStBh3rKzbghkPu7qJogTt+NnzBZjMFDAjHYOq4YgodV2TgihQbGVEZ8EOs3Y3qlDE+453tmcRJPk/wAF/i2g+Eac0es4Vrmf7oia1azlx4p1faNmC78TcE0xY2xwM4AnQ5Hs2WUWg/hNfCVZQBJirNw94TAS4TMXiTCdiOOoKswGtGgCcbvsDgKZVwpoiQO8p4LXDUEQVYOL7I/hmD6FCjG/M84BWhv2rcwGm+D21QYLjZxNmbseCsq2U0dE7zY4KweADwd/cBWjQ4fxCdvRbQOb+7n4gJwl1k2sH95GhfF495oFaWjZIwBvBPa1oLBMEaoWV03c3lrXx30VsWizBzDb0xwrss2Od3BOeT5D34lvMVXR5oc2TQ9kxsa0kZ1ATXSdTTdHaSnWjjBgE6u3qEEkhWgu3Ab01DrxITQWTyc1o/ypuLoJx5LW4+P8qPVs2MdJOlQE5oJYcWkjAqyc1/jd9VaWDSTwcBe8JTnh3SLYTLf7DGitiLPsNT4BBgLv3nbzvE7Oj2DbOeLiHfXb7J47xCtOkGDymqu2U6RLI9dltaBxGfyN8ynxZsbEc89Ai2+7m83vXb0kgvjJk59yYKnU5k89ntAG+zMTSd5Or7N8h8ZyGtBpsFSVYi7ZtMgY7opHMptA1ogDsGyzFBq40aO9dKMtdndklx7SrNt72mRoDh27DgRUbAwPziQyju/3GAlzjkArQ+y6KyY3qVp8jfFXZ9k5wvS8lwEduzVZu+EI4uOXJHBgxWVk31QyCjdnVHWoCPVaM1/0249q1z2DMqaTnxWDWDzP3r42ZELMZg7G4hCrXZgpuB+Ya7DALcgjUFOxPv8AR2m+RS9ZkQe4K26zxiSfz2Jj7tq/EuzGy0M2YJJMk4kHDsVnZm0s4FC0VuE8cQujkNcTi4Hqk8aJ4LXDgVYuvNbpUtf5D3OjwTaTUuIqPFWZDieOQ2Wgo+YMACGFo5Loz4Acd5syMcaGV0yxcy64yQHA4/xNXR7QtrW6HQbxHCU2j32dfaOEXXBuqcwEDK+N6O9WIIY4CZAwaeK/FDQO4lHNo/1OWklx+i1dXwwQyFAm2kzyLTsEXgDAdd+YJohrRQADZbXdzO5NT4JuDudahHAj3PlbUo4F1T3BTi1uA45DtVpR7g7eIx/w/qmCDaO6zqzXHY90ua6bzQTUNpB4JghoGiaCT2K1eJPO84+aNCDUIOaHF1m0w00pTVPYHOc1oBM1yA2ezJ7qo2d1wNQYJaU7EjHvNU1ptJ1JN3whCl4gOB9QnUF10H1Vs8XTExdLt3uWhMeey5FOJAQt3V0m8PRezs6aVZs6MHCzGUsIYPOU60NP5R6oCAOGxgLnHQCpVs4tsQcmfmm299naMbfBblN6ieJBgggOGhw2dJNxoGN34vojvWp1ece7Da43ngTG9Rs8qlWbQ1o4AQmXmCya0GW/Acucp8CytTi4YzHYgS4uiMdBVOhrHPcG4mDdnNOLQHMcTLThD5qnNqdYJAPbtaZt7UYGMewZaldHsy51oAYDp3nPIzpKs3Av6QZxFRE1JRwaMSsrMYnmgsDanDsRxecT7owOaz2aZDmsm/CPvDRJhYEHEKQZQFSm4hf4jMiFmMwUDLm6oiSNE2rHJvWb6o4ptB7rBLnHIIugWkyY1LY9U8AiuTqidO1OsnkEGQZGKc4EE0kGR4rpQMmSBe+HkAU4Oc28d2/GDZrAVsLtn0RhkMrILooF0gSQf8NuMcE1xJdJALXON0Q2dEKmDLuzB3mng/8ANdHIIh0w4E4V81aAOaeaYYeGkEg8YVm0uPGMu1dKeXeKxcdSrUFzC4yCesC2cJANNltZ341cbrvUplnMnAgB5nvK6SXBzW4PY4ERX4myhFo12dd0jYWue0gmA4Vo3CqkyYE47B8LalGS5z5m6McFoKBMtmyc4IP0Rw9xrboe2CImagoGBaNy55t8lX7Nxh3YcCh8RBHlRHmfojS6IvH+FqtXAMtHUJy7AmsJBJLWBxoIAgmpzXSN9xzu/CO6vvNs3RziB4q2tHOHIQz02Wri537rP7leyaO4RsNk+n8JTS9v9ROxrm3HEVqagHkgwAmQHTCYTBNSU8bsUFTx5IcAf8tV+Eub6K0gOLqmhnGAi8WjOEvjyTbwDjjAKIQv73/+is7Qgu0vD+ytGB3eJ2WkCpikiV0aLO1cd1st60ZzKtGhw7U8FpIoaiKISRZkb0nDHDv29Bbjleb/APM+HuWYcGzl8DfCV0r7Rto+oaTUE+LUWhoL4o0ScXDimC8+2aPs5+XYCblkwndaa78Kx6tkyKDCAASZOpTAGtaMgEMSv8XpBkS3PkPNOq55j2j3Ybo8lZGS0EmTM4mrneCb2kk4klZTgEcGjFZWQ9UMBsynBHKd0bBkEeqM447cCR96cZIy2ipGqFHN0K/xGahZjMFN0RTeq4Z8Ezrae5bfq2ATE0vEeScJNmS66DpmPBAgvtQA1rozLpg8gmNHsi4DrCKzqYxQaTAf1HgUa2OtXFD2lnZTo5uHeV0EuF1zYLmhxfIBGUpo+0s/VvBWUhpiRB4SE3edb2mDeLZwVpuPtAMRo3hqVab1qRhOTRyXRzD7wiTJG6MTgumyH2tSakzdd8LhjCt2n2NsKtbf6to0/Kc1ZSX2nwuEYNdg6UHzbOGEjLjCgDkNNnRCHEjNgM15fVWgBHDUHkgxt21bQYEGXGiNGWsASTUA3aGciEa3XCRKtIvEEnDmTsNCrB95oAq5pzr2LV1VMh1rDrvIYIB8Dm5p2MLXjscPROs2zziD7toxze0iisrQtAd1SIBGFQogEFrx41VnAeaMxnQcFjcEhs5ycSrOzcQ0CAAL0QOxdItWta3WK+cJjQ0cgI962eBGd1u8SO2E1gLhxdvHz2dD6O81wm7Pm4Jt5p7HHYWkd4QtHjy2BocOwgq0s2OcQTEkCUMCDdPjKZRpeL8dtUPlMHxWor9EXCjhFE8AESAN1rQJ710Joc5rDJLWi686xWVY/ZvnE3RQ9ytGOc3iDdciwlreIrTig4t/5dwLeqSYIiocComzeTIfS92Uw2WjXQdCXXh4lWT3BrbpJumvZVaPBZ4uACZ+u6Y4Ua3MicPNNlkuJ+0dxmcFZtLo1OQ710m0Jvatb/8AKdjcXOMBSRetMDpAbWq6SGn2gq0E1BnSsFCrXA+TmrUOdP8AmQk456klO3TatF4T+DXmrUfaPJmATMD12mlu9uHEXtBmnwYiZc7N13wCOFiDQDQlvkE2jWtEAIZlZ2h9EcXnFYcAuGHvipK+b4nLzRyH3c4CanYR19k14BHEaFDrtycFmNCv8Rmo1R6zTkj1mHYcdlubrTm1o6xCNT1i0HP4pVwWTXPrJaILgAJkhWhAayybeNdZhXTcLsA6KSrMlptLHVuNcPJWryGttBUA5kNpRNENaBAA2Wn2j7MUFm0f4kjCuSGBL2PB/wD2VU1s7140/C3dRgk/GaZuzWYzHNOcB0hgJBLpx/iwXSGzbWEkub+NvFvhmmk/8pandfHySD3BAkF73AugG6boAEK0eGe1iXFxzOgGx9leFK3qmZ7E8FrhqDQqxd7SyOrXRPodnRnA3s7rjHnCtWNfH7wn3OlD/l7Tg74T5bekWdP5R/s2Os3jwKY57f6p9feLvaM5A/RwTQT3K0tT3AD67DYOgfzqya60I47x/wBPvt3ncA418G7bQlg5OL0y1cI5hp2EFWdrI/iA+mw2bo5gSmtufyEt9E0wQXtoe9aMN89zZTxu32w6JjBwX4T9VbWl3eGhb9VYSHAiQQ4io40TWuZZ36Xg9l29hUVT3XgTTdYKuVgXMLmYOp1XTpKwAGZU7tnkD9VZtdcY2jpg3d7KqvuIa6himXNPJFm7JwLpp2AK1m1Nlab10ONS0EappvF9mA2gxBVA5zcS29DyeKspZaA/P1ie2VaOY0d970Qs2k8yJPmsGNGLnaBY2Vg2jQE2eqBdEITdd8pKmA8zZ3h80Amic2WuPs5u4SXH1qrNs3Gm+6SbrRSGgTonEm1tcHxkBTJAw4QQQeIOzpFDBqGDHvwXSA02gFbriM+WQ7VayXOxuziAddTtbghgBsJk7AJJOAAVk4A2rTdc9xmK6UwTm1ccTBIB7Rsz0HNO+EYBaLJoxR/mIWbjj93HVcEMxosjoUf1b8o0RxTsG6bHfrGDLiEcEZhuQnaKknABN61o2AOwHFWG88vA1vGYkVuwmu3oh5ujG6Mymi7ZdHILRzjIJ1C6pPeZ2Wzo6RZfDP8AfLirYB14NDSZ1jbaWvs7Mn5GfkIuue1EXb+Ed+wtHmUMwrUXSRgZ8inm9Y2hwrgeTvNWRJsyGihNZCtT7PpMZSInu8lZRasI/Dj/AEkpzAH/ALwo7xVbK0OTQZr3OPdstj7C2nCDh+eGz2Tj2gT6JoLf5XEe5Yxa2cYyzHwlPaL/AAcKOHfsJNm93CYM9j9jhB5FWNoXN5dU+Q97pTBZv4Sbn0KunyQtHT3DZYWFO2f9y6PY3Tzp6u95uLWbx5JpHspxAaIWhNx3+b0Qad82kxxiFbPLxObYDR5KztqDgS4f6drbQHxeNhaR4Jto8Dz9U4kuqQCTWYBhWLrtmHAOukuMRPAbQXny2DC8AY704ht58ABppTirQh0Bt0B0bx7U43mzoMV8x9FBuzhOUrqltmTAikYJg9ox4hwuGPiHNWbLrgKgjqimquxumd18t705rmMmLz32mJjhKt3X2t/CN0HtTXkvBIBwgGvamgADknEPdGLNI0OvBdIN0WIG8HEVu8k7ft3j5jg3k3Zo8TGdCj8AJu+cp4uuIkkt03iU6jnEST2rpMMHNx3T2GmzoIiyBFCZLbL+reXSJc1zsQ12fN3vNMOtD6I1LjqrSDJaDN43Whs8Va2gsy8kXhJgs3aYqydPSLQfFaRLz2BMAa1oyA2OMnmuKztHYdiOLj6ffMjmCvgfk4KfBEVCOStD/KdjcY2FhHfQ+Cktt3RvTMgE6RCtgQ51mA0y4RKs3h3fLT/lVmbr7uRTmwx0xXnxV5zom8WgnCU9puz8wq3xXRCX72IacRHB3mvmgDwJUbrLSkng6oQtHXhnWF0b7V0UOALrvGZK6tqBk8Y9+KqPJDFxwCzecVZk71mLzbpxbTFdGJs3g4x8Mz3ditBAOjvhPYV0UmxtGnNuA+isLQus5zadO6UResz+JtQrA+ytBnTA9oVjFq3+HHwlPY0uP4o3vFXHXuUJlo8Dwd6+64+26MDocQPzlssftWfw9b+mU0Bj/wB5tD347Ol2YbORMfVo96ytRHIif9KcJHIr2pcGmcIinctQx0eS6RZSx2RFz/4q1LjZueQAd8mJK+cmnYuJu/5oRwuuB8tlq0l7m0LjMRPBH4pp4I/hC1afrKeY9m45BPa0uDzIaY6rRkFavk9u8PNcv7rUiF0iXBv4r0x47IK9s7ybstLd3OABtr57WG9auMwHRPg1WTYL3mSQMyU37OyBEE8fFCpKsiA0uqHSKeScZcTOKtG3SBk0xhxBCtWkODSA4AjGDgsXNEOtHHK9GCNGBom63n5lOIvWb6j2f4YkT3bHbtjZ5ucfoukGSMXEmtxvquij7Ng6rXHqgD81TjJ9yIB4miebwmsBXmkjhflNs3RzIgeK6fbyDqyz3B/U5WLQwhgkC6IgmRVDMwKeKGNQiJ+0EDleEhGoIqCPc6IGm0cMAWVu95VuCLJhqL2bwMuK6TvGcWtJnvOOwYkrAvPVC44Ds2nBjalfLw+7j4TmNjes0YlNo4LI5godV3zDYcXJwQ/Vu1Cwc3miJTS0uJkA3jvGfDgrUl1o8Q8iKgSZmncm3rGgi85zd2najYuDu1zV0t/2cijGtnnMSrQlzGsN8hsXoc5oicdli65aRk7SVattach7bzarP9Za3ReJkneLiE3rW3RaO7bpcD2rpFpdZu3XNacL1ccV7O9WPtJAMO7yul9WTQT/ALXeCaZpnwQG7ZNq49n1RxH+I8eGPdzRq44uceJX/ERdJyFoP7+ezp4uWgyviBPfC6SA0PJIAe2lSOYWBeIv00LBPiulMAvuzccCf4h4r2b73K6VZEfZWxaOt8l8R4o2Ti23aW5jGjiIhPPtPaZOvRhw93obgXRmwmqtGhw7dhcbSyH5/CQsd5wb5qwcXX24Nw76hOMBoa2K/wAKGTGgDwhc48lzP1QxN4/7kwS57w67u45uFFZi4WAy7dMNnSmqc4uMC9E5C9giLuOWiDA2xeGtvWcZAwnuvB1oRDQcgAPVSaSYAUYGSZRwLBhHehUNN70ojWrRIOrSWghHQr4pF5o7nT4JxEWbWFpaOLnoO+19oBDQS35ZlR8RunT4oVs0XbRpDgdwYdy0Ex5rifpCyEGnbKbT2ZN9sDKtWp1j7QAnCW3sV7R8+GwWz/Ju0Mmuu/sAkngFaOLGk8TePorb9ZHw2f8AdMIMyBNcU5lwFrgeubmXNPvOedXXiPRFxIaKYnVHIUMjORWVWloJ8WwrIXrc1DcJ11IQEBkCAOGi6M0uvuIIqQY1+JNEucaAALo8t6I0jIUvxqTh/ZO+y/4fYHBs/wCJGox/IVp9rbHi7Adnuk3rQ4wMEyQy0AIEN1gEBWbCYyBIuD1VraDuaCVYNu9Hun4yAXu7Measjda11b9p1jT4lYAssyw3DArFMTXknA2drYW8BjqzQOzpmsnWe6fDFWT96zdp+E5TirQBw+mxg3W/M44BW59oZxLR1Y/eJomECxs/hMGgriBnx2twYKV5oZDaREjJZvOOzhgsx91bVrhiCvhdk7YOuz5gsxoUKtdoUz+oaoo1s3cE2rXJuIOY2Ayx7es08E4RfdFG6ABOffFgBGNYvTgulAAaAOjyeETNkZAcHfhnFfFbWhE/zHBEltr0twhrW53fzKsWlz3ZvdmeZVo5wswMPtJv9woukUIyAJ9Eagqye15/y/6l0iyDb2pII9QrM3rNxwnMHmuji7a28G+K4FzsxwqrTedaWlS12YArnmm0NQTOkCqFLTpVpiODW6/mi6OBaMfgS5gqaaiU5sPAycKOXR3C0ac4wcrNrbYRlk/u9ERdtBo9tCujb7SMbvxD1UFlqNSKV5hdGY95Lm0JbgztQhptYIF0H4n4QFZtDW8miNuO84N80Jo0F2HIJzSHNayhByMq9es22hLXMB1ohldJdB+WjkRde8NLC3lXBFokvtDjH4Vk8tF8TxhHF2QHBYumpWhEei/DggZB0KbhZB8NjHCYXBdqGMTVcY+q1Bgz3rmNk/FhHYuBI80M5DgVyCiCeC7JQwEKaPZprRDUf+1o5xC1a4IY3Re83NCys7UezbwmaHvVqx1nZ3a3i5pgCFfcYdIEH8WCOAa4HyKNu8NDiBNG67bMNa7g7eMeOz2ThPMQhaPD+ePlCaXQCTi50NrwAXR/1kkkkihbPFy6E37a0c4uvPm7A7aK0dNkw5AUJ7SrrndIdm2ktA2WbS6NTkF0txMn5Z9TsdaezYeANfIIEHpBBiYPUnzXRmh1pZNMjdpjTHALo7DaWg/Djd7mhOPh7jRJPALpDxYWAOTQbziP5Y705gszxfadf1VtFpaUiJFG9i9tLycIBbPgrBwsrEQd4E/klWEFwGZccFMW1BAFMdKID9a03XADj9VYAlzHEOAjL4h4J1obNr4hzmQcY0LaL/iADmB1C1zontB2dGN63IwvDrf7V0Qw9zcDkSOcQO9MENaMABsbjHHbww70RAaMAtSuFAv+m3DtWg+6PwOXbtBkFAAE67D+ss/UIplWn0TaOb6oYHMbGVBGJ4IGNJ9zo8m6MXNOMclZC421rdP4vlqhVrDaQN3Gd5xTRu2dk0+sBMdIA8zqUwQEKsPFWHVnEt/sjZuI5gSPJdGdcLhi2N0HvaFaCe3MdhTusDgZ1XS5f0dxycPh9O5PcXXHulsmuAjxTx7SzGV0Gng7Za/b9HHA4gdnkrVjmNDqmSPlbVWR3GHEsdiIzEq1efZzSDiz+YURoQrR4Dm1j2ZqHzkWhF10Xam6KjeBzzTBBfF0EkySIDsynmGktLnSOdPBZOIr4Eo4tswGeLYWbnkklagNB71oDPkm0BDSUeTUcXTIGwY3TErVxPohnJ2uEFauMrkFyC5BfuhcBC5n6rCb0eakGQZkbSJkCQj8Joe5cz9VwNFo5seSM77ajuK4U8kflcpoXETyWraf5ViGky2UPjaXNce0SFJuj2jnDgZKbJshYODrwipqCgSGtBbQZfCnYe2abtOLX+iBvt6S17YcY810YXnEDrNGNK1UXCwhpmeL8TyVk19oQ3kXC8cymkuvalrZjxXS3ODHZl0lo8ZK6e/2tqf3iAye9dHZ2w0eZTrV7mtcYkcJpGSc661jQHPeeW9RW7gbUDCpDRPbKYA1o0AoFZsc6nASrYl413nXZriYCtTebeMhlZrE1OafBtbTKmQ4BPLmOtCZje8qI4Ee5bkXjo3Mrozfa2g4Nwnnd8UXe2teTZjwB2SH21pE+zHpSp7FaRZ2LMXEgglya32ttFSXEYDyVm4F9nQvgYtu9sq1s3NtLSKMMcl0q0l7jQuuxlzhMFmwEjF0gkhWZHfukf5VbC7ZMFSJpe+i6VXpdoDJaT/hz+EdZON60f8AM76IZIGlnhKOpx70ag7MyVm91GhaYNC0H3bLgU3qHOPcBkaJlDxR/WWeUahFM0zCHWCwcM0dtk0udGMAKzdDmkzQ4FWbSQNTg0d6tXE2AJu3hXAcSm9ZtO/BWYJN6uHBWp3LER1QYM7WkXxkefPBWgqMwc2nkrK1vyTvESBdAzycrUueZyJN2B3bLA+0sSMZGIHMIi7aDR46yaRY2wHymT5SjgVZONnfGO8JHkVeki1fdaQcwZElPpDHG1MaCphWRbdsx13hhvVhPgtD5c+DwMeSPWs2m631Hgv+pafaO8fRaN3fJHFznQuALivwgN8l+JxXGvmuAA+5HEFCgA9wYOgSitJlfibH+VfhdH+ZaxI7xsOJgLUEhaO3ghjO6foqXqbrZ1K+WzMn+kgeKa0B1swguJA+USU6hZbMkA9kp8u9nZuBMnO64tdK6Q0Nbbezo2CJMGNAKJxn2DjdfX8D7pB5FWjS1zodhnErpVreZIglrAL0543fFdGuj2pOZaCXO5LojWmzBEbtnGXFxXSXCWg1ug073KzYA6ML3xHvVgW34477uyF0YXbM4iRuNPbU7HNuD+Mhvqm2bZ5kSfHZaD2bObsfCVb/AGhdg5kjdAPJVMCS5o4jLyXyuodjRJVvSyn4bPXtRPsrKcLoifABOPsrH90R6AKzaS0HN3wjtKti43zSGDec7lPkuhvAsBFYBq4eBKLYtrF3Eb0ahNmbN8SQcgcD2o0DrrwJ1quib9pdwABvwYpMkBWtpIHLdH+ZNDWMbAcGObQEiu9wXSf1bet7Gczq7RW9XE1LQcp1125vOC44IdgCwL3UC0+EIZDbhRfO5ERGXd92ZWNSm9ZuyIMYHnsf12j4Uago9ceaOIPuQJjS8JXTIkNJghxuXXDgVa2jWk8gXei6F0dpaw9W9ArH8Q7la9Xk6oTgQeRTzds20kJjQb3ExTYQb3JE7hPzjGOzFGA8MiHRzGMJgDWtGQG3p32lnPw2hy76dyeNwnJ/wnvXRNx7XY3RQHswV4EWVmL+8MMwE4ybR5IJGpZ9AtBuN8K+KxuNgOPZinOPsn/DdwbBHBHLl+yHYDIJtQwUmck0TcgtFfxRHioIBYQW178FMAXQTGNW08EdW3gTxa8BO6oaTZOnkd1RSxtxfaRoZkeCH+L0V9w/yi9/lVfsulNoOHxeis5u2llD2lpNZYXCtNexNMvbafZlzdGtdmOac26wlroAOdBVWJDrpaQC74Wi9kI2MlsnG88m8fBB4EDdME0vDAq0bJboQYPkukWwnkKf6kNnRB7W3GV44A+CkNvOMCSjUEJ3+IyldS3AodV7QTTzCDwbUCriNBMK1syyxABLgIjALphMWrQQIfUx/DRNaC46uNXeKN60fZtNIA3Z7imXWWgAILwKNF2O9Bt0Xs/mcRxJTa3bMR/lwQMX2tIOGBLBHgmguL33iGgDE0arZ10OOfxO8YVkGtDpgNu7znTwJREOtTUMPpy71bb7DJ3Z11KOAmvuH4RlzWVmOqEKDJTAkROzjs4/cLt/Axdm7jh+hbg4eqbi3XiNhwCNCFaHd7dt4NugxiukFoa4wS1rgXSBrkrjb7rwBLoE7uKfZvAc0giY1XQbc2ZbwJ9qF7Zjp4Frk6yj+lh9FZuuHt3h5JwBHarPL1Vob1odlmL1raD5QfJFlY1z8fdZvWVq6gaeJ0Kst1ji4us92geGNivGVa9eyYGtJAoJc2Rlosnu6x7TLipgW9puWf57UetZdHF2ml/FHG1cbzz2n0TQHifwkHyQJHj95HEbOA+q+V1P0jOvoGjrEpqGF4VHI4oZscXCORPqtU8mQxgeBHzSqktZuPH8JoVZsu2brSskCBehNMF1kTrj8aMg2VuAPOiODrMkfULJtpuu7xIQwB37Mx3hCpdZktIHIypi7ai744eKtTNwGBXrbwkHuTHC8CWzzhgqYOasxDczzViBLSagiaNEZ7LNpc48AJXTLQuE43QTHjKJllyrg8AwQrKBFrgL09WkqPtCzqzwTGkh2IBih71bEizkZZu9Ag5zLNtpLWNAMN6sTTCvFXb24y7ZsuibokzTVMExwmF0lxFmNGA5eXYmi6y2HWaOI9U3FvAp8wSJAJPWVob7xpSIVuWl0HATQeqsQGm6BJc41PaSukE+1tzSQTLmtzXS7QNNq6rjdyGlSFZNDR2DFEyL1Y2ZNGJRy+IrNx+qztHdVccB2IL/AKjvRdw2ZDNZXhE/eW9VybiNeIQq1wxlZoYHTa5om0EVkTLRw1TSW2YFXHM40Vu67ZtkGT/ImSXtOIbnIwcELWyd/NT/AEossrucugUpwTbNrOkDC8RLDH8MINvt5s3vRM3T2YeGy0ILA2sgT3Kau15fN5Lpli72Lj8VDd9VZPewjtveu3KcTyGJX/3Ftus7B/dY+ys9yzEfnRH/AArEXnkj5nfUo/HaVtO6PRDO0O7/ACpgxMNaAETBtms3BxOcbDLTMPe8A49q/eH+1DGyd1uzIoUvNY4eYXyhtVrdd/tQoRccTXsXBrv9qz3Xf7VhVjvoE4xgQQe33RqtTJ8lq0T6rCjYK/dLv8tVxBbTk4LiPps4GfJamgXyME+S/Ebo9F2uK/cCyBbDTzgofKb47PiUwLN+7J0BNFw3vJZtzHNciuAK0z7tugqfBYBxabo7G496toDWnrAAkmeZ94SA8AA1xw2Fsm0ZgDOFGuRMF0XSDqXN9QiKWbyC08sW+SmDbMG73YeKE3rG1bBf/MPJTBfYui9+LdPmiftGWvXj8L2QhX2b95hPZ6gof4lnUx/B9FmHNvAdor4LVpB8tnS3izaM4mT9FZtDRHAQj9nZVwc+hPMNlP37X945dmGxhFp0twyA+H85pgDWtGQFApDgWmCCM1mcXHmcSpDukP0ipHIDxVm0NaOAQBI5wrTI1idUTFqBBaBpRWTS8/wiU5xaG4lgNf7DgukvAj8LTPnC6OLheAIc4VLq6ldHF5zXQYI3iacYG04ErEuOA71qOqO1ZMHVCGGXcv8AqO9Fxw2kUOi1d94PWIyRqE7BN6rgm+PLY0Q1urjQBSSyKB7eHEbBab3aDCcxwFrZHAPJILT2ro02lm+0M2kkzIbJPJAO9gCIJa0OvSV0h1m2zbGIYbzimWbLk5PaB/6ViwNayhv3Rdbeu6cU1pL/AN0CqefsxgfVMa5zgdwkASaHeKNp9s10FoIOEfw5ogXYpTJdDcLRhHyyLw9VaxbMacjO8Oy9CODcXHk0VRx6TbCp/dFfVCrra3O6Ow071FBZiGd+Pgj/AIFnBMYwY9SgN61fF7tcU+YuyWyK9bDZZiG2Rd9nOt1Nwa0QB3fpLKQ4N6wkgtICNDaWRLZ4wGuCBqS00HOAtIH1XxOxa3mTAC/6bMP5j9EMJr/b3TiSxs98L5mEtjswQwbaEXZ1gAIYANAHkuAj3gNy2GPJ2oREG0NpIjhi8IVJd1J/d+qOjAPJHHGewymV9iTP8pd5FNoQ0ESRq26SE7rEC4Y77yFTaWgvGeAwH6PKRUciKomTYvJuzzrPaFnasAz4jd8kawyGWgPFuBQwa4w8Dtr5pri0sfwzGCaCW2YMFxGUrR43e+AR3KZDi1r2uyi9WOxBt6zt7MywwatJaQQhi5nWjsg+C6P1m2hi6+pk9sLVhDh4Lorfa2o44wfDY/csWYlzzw4LpBv27jUyfh7NrRDGnNx6oXSiXlxxuEyO/Ha8S6xgEwKS2q+Y66pwIcDmCvlfgRxLforCLjGYEiugArsdQuAAJjUodZ5wCnHZkwU71k0Yr53rjghl97ikpuIwnaMHZhfA/IhDquGKb8X1UNMZiHAymv8Asuk2bRLW068EF1UR9nb2ZmnFzQfEJ4giAYOorkh1GulpHLRNEAtMgjIGrUSG3RmNCadwVh4g4lERaNHwuzGw4hataJ70aEHRPaekdEaT8u/dHZ5JrRZv/eZu/wB0R+rG84/w/VEOb7e2MvLXEdVscP7o1L7XfcT+FlVh/wAxbQB/C3/2sfZyW2YP55LICAT2CpWB6RbC6wch+eSx9k0ltmD2f2TPigNj+JYSAQwcZ/PNOEvYMAVqafp/xNB8wsbrAGiez7qREgwewhNES4lzjxJP3A4grK0stwzyFEP8O06/ZJ9VyvNjwKJDmuMEOboDBFUd32t3/wBg9hWPsrxLf4mO3h2oTPSbIXmnsw8exYlmDv5TBTQPtC2TXF18TGkKpNleII4EdYDvVtF+3Yb3GZ+sKzF57X7rgOWfYrM3eh2RyjF/5z5e50c3rQjMt65H+UIUA2OJbZgGP4igZfaDAH97NEbLNpIGpyHaV0p3tSCTDWnqgDKm0DrRTZ8rald7lm51Tsya2qBkMbj27MmjEoiYK0WDbPID7u2ocPVDLJ3ELIptQ7Uc1kcwvgtMjw2YJ9SQ0AzzFZUyBjHddRHxtrOlQ5TRzmtJE1xdc8VaOMCziGnGN1ULCcByGxwHt7glrK65J7GkOdiZGcZ+5YmHWTRefaN+WBhmK6q2eXgP68HC7ScOSMxaWpvvcfwtR/x7TruHD+3esXWtrvV4NMrATnGQCND0q1xP7jVm+2N4Twb9VG6DiY0C/wDubejf4WjFfK93s7KeDQiAW3AA0g502NADOjg3WA5kxVMAa0aAUH7YOTh5aJgDWt0ATzBDWhzRHzXqJtS1lC08WnDsKFLJwBvR+IlDC1st0zxGCFAHA+0A75PeUDWDcN4dwPgUQbvSRAcIFA4Zz+SvnYKHm3BM6rrOjmCeU9/es7J9H08+za8mprAdUsdpwK+KycQHjs02SS2Jls8kOsSIJ9Shs6RagkDQbv8AqQoBs4iVkxq0HWK+Y4+4c81k0YlHDUr5nV+8zgEfBDquGIXwvydtyOhRpZ2noUUamcVqUBKGLSniAcbrsndisXFsj5da+CP6zpdsbrQJ+HLzT2H/AJjpZpLiKGCO5dGcTZz8VmdPPt2GgsrLerxKd/hsrauB1OXhyQr7W03nTqMhsJ+xY4blm3IAarIE7x5NxKw/5q2oP4W5+K+a06o4BmiApJjuCwPS7YQ0futzRxtLWoH7rTTZZiGh3VxmrcCh+3oIIYbrTOrRTZZEteyoMjGJxTKljOtGZHJOpfIu2jS04SFW/Y2tXN0ukf22MEljBLjyQrebuvn8TKTzCwbbsqRzP+5Zj4m8HDJAEvvYXYrKFof+VYwY1wFRH/tWg3bQiHkfvfXFH/Dcbr/5T7nQmkDSW7v+Z204nQLNx14bMmipXe47MhmvndieS+Z3uaLXNfO77sTJ2eIXw2mY5pwxHFP6pRRox/y8CjgViCNUeo/JfGzgsxmE9t2/AvAcCr1/olsZArlIwxXSXF1la5MPHyPemG77Nu8XsOt3mjhYs/WOH4v79yA3rQ1cRxcUaC6Js28yrQgtswZuYzXtRws27zz/AAhH/Hthvx+Fv55rE2lrUTwanYsHVYPxkJzd18SGu7Vm60JLRyafVaftj5Q4T3fpLNwc20buukaluPb7oJDDZ1IaPikT4obxLN17TqQKHmEd3/mGCSB+Np9e8rEXCbs6Obi380Vo5oPSWihZn1cdV0PcsmYe0cMSRxzREFhEiixF3eb3GvimiTZbzxEXqg1BjQrXeYP6gUQQH5/wueWjwVs6od1rowntJ975s1kBij8bsUa3nbfFfO6gRqT6bG0MfdpAcw4RsZNNU3Eao4go4tzas6YbDiE7A/KjgQsQdCgIk5o4N57G1vkwW8QUHQ7pfSB1W5BvpnyWdq4V/hGSPVsWbzzOFEer0Zhh7h+P89i0+N5Hi4o0PSbTrOH4B+eaxdbWm84nUThtta2jgILo12ZoYkofCzfP9MoZndHheQqZcf8AYsXAEu8mrEYOI57zvJcAz/asPagQ4cSMD2I4OH7NAm43ed3BHqvfgD+62fNOr7NomP4QWtTy3fdF5riesLsICp1/T2c+ztWEyJ4JpID2Ztymgqrt2W0EchSUcQvwfqzzYrpPtmibJ4FOw1WJ6O8y13Cfr3oUuuo1x4TgeCYN0fM49VvaulTaOcaXWY9k49ysHQ0YG2eM+SaIa0YADbxU7zzQRs0zR+M49i+Z1UcAMSvxdYrIZo/4jkcS7Du2fMaBfI2g2D4RUo/E7H7m7qnTYKluexvVeFkcnI4omSjQQjUFFDX3HdSwZUycL0YIGbPojTAH70f+02ga0QBscADaQL0Dig37NpoCU6osidxg0gUPkhuzW7TJobUrQdbuDyfBDrMf6HNfE9mA5TiuTT/qWl0f7l/1XwY7MEcWWcd0mAO5D4rQhx/qlZC9HotSZ+i+UUHcNpWgMH+6wlw+oXI+izg/Vc59FwE+q0ugeblFJIFfFVwcQY7WrMOAI/mlEAgxQyJp+wmUtCw4u+KSNNFq6vgtBsDmlxxoKO8E8XmGZLprujEoTLiQ08KVUVZaU8cCv325dqPxNIcPD7icCSAgNy0Dmh7f7KxfLLSzhzTOF48NDlgrQ3re0ad4tEQwJghrRkB7gEXckMG/QL53Ynkji520UBRFDosy7BBf9Ry44IZBcMEfhbivmNT9wjtn3PJfC4Yt5rJwwKip4omKI1a4eiPVtPqjgQjiEMB7jcXFAw/pT6GPw/2ryUF1r0h9XRiY0TaO6TaCT2D89ibM2j+sZM+4/dtHNrcBxH7xVoB7V2Z/COHnsJkgGhWpqfFcQjoB+m5BclwAG3Iik80CTYk65s+n34YXaMn976BHAXf95WpDJ/pajiA5wHhCOfP3MjmCm/CcERBY1rQ2OQC1YAB3CiiCQ+DzqCpAc1xBmfgfHgYVoLzT77oizJ6o1dCfVoAIMHDM+S0EO87q1dB8AVpdb6Ao5kCfFZ3XEf5YRxF53q5cx9Fz/suaeCHsJlrgcipl9iatI5Z+aNLjuqT+E+4cX5LEk4Tt1KFb0U2cV/1HUHYuOCbiBlt4U2aNRG7r90OIXxWeR5IdZhxCKyYcu1HEFE4ZtRw9w9Szb1ndiFbHogp/N+ZTaBrRACKGDQIHh7jW7oFSJoXRwFUDuAMvVzJqKrS7BR+Igx4I8wnZH7sypjExn2KyhtqNTHWHP9BTDj9wHxOIA8Uc2CB/VCn4i4kjsuriCfEuRr7NvV/lEBfMfT9K8EGfPsQJdZafiA89op7Nm87uyTjDXGX/ANLQPNYizEXq5bjT4lawyf8AKibxJMlztdvL9EMHjFGjbepcNK5jxThLXCoI2jF7sFnsbgJQwa2p8EfifjHALV2HcsLrUfhGKOJJWaGZXzEUThvRrwQxdifukUnCUcHxTYDiKSo3o12nJDIbXUkVYzWeIQ3nPeZY05Boz/MBNIIeJbaW0aHIIDFxig4lfMSAEPlhZ2jt4+NAj+Imh/eTcGuJc3uw8ET+sbunwonkzfYL0jvWgAC0IkJ8PcB1Q2oHaSsTwnL7tmgwDtmn3UVJKiQ1hvn+mUaB9p6NCxDJoJy4di/CK9rsT9yszeBGJjDtTaOuiQ/iNEetaOMOI4kYDgF89r1J4Wf1Qws2C60DkPuJxCd17M5ceB4rAg0c06Ee4TEL5W4lD4jUrABtfFfI3HtWZz2HE5lfO7BD4RQL5Gao/wAydiTw+6leLUcxsOJGMo4Ha0S5xoAEJFp0gzVvPKdMU7r2jus4/TgrIbvRz+rvfOdUx1y/1XEtxM5DkvmcKeJJXCgXGShgBtJh0iRCsd0tbAaRq0bBUlMLQDrc+7uF1utV0hxd/CN0fdbYkv4hkQPFRv2lpWTndbgB4o5gAfdTitBT7qOvZjAjMRmEz9ZZHEcRqNmDWip5zs0GHev+m31Wuez5RUo5nrLV1Vk0Yo/zEL5jj92ihXw2mRRWLrLI8kOs04hHEFO+Djswa0dZx0aEDLLIUc/jx59ybQNG0ACT1XRhOYPFWZgkRKyJWjRHiYTa3Cd48k2m83dJGpyKOBYZPaFaboAFN7zTxDbIGbvF2Wy3dDoxuAbw8lE8BP3UZlZv/OCxgYu5EpoAaBgAMP0DwYBEye9OJN14kY5RBQ+EOF2eZr4IijYB7d+SiZrAd3tjyRnfDpFNd1NEk5e/ZOLXCuD8D3hRvAmKrmFwr5LAvITSJNQBPNfEND+yGG8QM4qmDfYaXvxN2ZxmFmczsya2pWYHWK+Y1K+UVKPxO6y+Z2OzTPZoPupyKJmCZjY2oIz5oEjSdlpRlkKxOBdHksWWB6jNJAp2JgEhgEA/LJIRwcTAP9KODLSBJ/CcNooHOaCVoBG3AuBLSed0ha33/wC5H4zLnd7p2alWVLzaguOMIAT90aJUw1ozOgQ6tkMB+9+isJIb8wMSB3Jm7vUwpB0K12lpFaZJsT2/+vfcIc01BBWjXGP6pWhqvxGB3NhWYlzgPyUDURUgGRe48FmTiT+yWG8CKEx6qypaNwvD5gNujcT2rU1OxpgnNfM6p2zQNxhZuNShkDAWuf3q1pZWQrjS8fzVWovONoR9mOZzTqe1HVby18k8zfcLxbynPitSZTKuu0/iom7tq0ZOGfb+iZaOgAQA1gJniYWpxPM/dXmY4D+6LZJ0nT9I3PC+0fCeOhWTqO8WlaQicbhHjgtXHDzKtAAcgAP0LQXOJwAFSUDDbV1aaxQBGps5p3CAszmTr+yw4C0mjSDjMI4smu3JoxRyKyGZR+M4oCrtVqVrkso0Q6rjnsFBOJP3h9LKy14ngrVwAvVFjf8AijXyT8fmMU7Frn37TQhOoRkR8pWjACD3kIfFdB76hHAWgujvqPFGocDIPvW4mmIMezd9fu1lV2kN/v8Ae+kTZsHAjePcn7x9P2acQVZ1AGLeXBNH2tnm0/TYBAnYKiVxXzGgC+VtAFwQEAZIZfeWCjc3OOACd/8AT2Z6rW5OA8u9WgLXBPgh2Eg5xkR7pyK4LgsntAa7wx7VMmzd1mzoVEusnUeOz3bz68ICLQfD7nqaI9Z40VsZLuAw+9NEucaAAKxO7IxHxOPOKftBuD20nmminy2g1bsGDW4oCA41Wk0QyC0bVfM6pP3polzjQABdFdAHzn+/khQAIYSYk6IUnIAHLT9FiHtoUMn9aOePmm13uof4jFUcDsZvWTz82nahMOOXCf0+pK4SUcLox7pWZfDT/VXwWhdT1KpN3GO5NEAcB92/G4DzQEneCIkC+36rVNJLRBqG5u4nKU0AA/E7i4/tGzN6wf8AiFbp5nxVkItGOpej4h67RThK+RuC1z7/AL3b71qfw5A8KSVZjedhLs3HmsnHqjWJxXyaegXD9IRVw1Q+DX0X/UdMd0gI8oHiFkAZ/Q8VlARzOP57Fqd0eIX4AXH/AErhA85X4nf7QEMCRePe6VoPuzTBLIuz+8V8zt4+MDwQM3QR/liF1fbAQR/DESnVIDoqdXQZRwN8mO9RhKHwhxae8J+PD9hM67qQe9NEkCMuxNxjMa/p+iwWuw9oBhKsTdt2RFdRsNSQMfvgxJTW3Q8EwBhyWTRRo9yRV2ELP9HrFe9DAD9EaDhxWbXmYHAZoZDdC1ivf96swCWjEyQPVA3bral5FYynyQ+I7zzzcfT9OMSUMsO77z8fELM5nns0GB+iOZxHIpuP6UYjUZhD/wCssRQlubo8+9WgDmnga/fbTAHCG181G9GE/fWiWwYMpvxHqmPIpwlrmmQR7wwBNTyGJWtB4SnGGttBEnmCRtJu3S8TPKVhIMj9MLMu7W7w8ky0eR2hv6YZBE0dEkr5B+aIdv8AdN4TPbihjWq+Gc/vbsSP0wwIQN24wTWMuCtAHNOFD98H6+2ysmHIxmmjE1njP375gKqZuiQO6VMGztLxYR/LA7ESJD3AADOhIR0F4+AcEdGj/Ys7gIPfLVmbR/o2qaIDWNgeiqWvAADj+OCmUaXhrjAymqPwWQAHm0eCMVe6SOxoGKcBLZoJw7f03s3ERwF70TXefvaE1WgH1Wgr5Shl/wC4WYoiJk0oVo2qObhAXzR72oAHvnP9h5tcJBCYLoZMlvP73H2YcQBOtdE03ukdIcKWjzo7yXwuGIKPVeMD+weBhaXiuJn3WgnuVbs5k5pzj3Afprl3+chvqnOJPl7rjEtr3J1S0Eho7RBK1ugnvNVoBA8FhUSpm/ZiO9uCHVDQGwO28vme4k+gTfibUEcQUwQbR4ElfuhaQvlNR9V8zaj9LyXBpXJcSB6ri4ei5n6LXLv+9Gj26hOEg/eyS4gannsOSODtOB/ZAwATgA60bUNe7eukfprQRIxBxB7CiZa4YEYXmniuf9kcJw2WpumPhBTv1lqRVx+nD9N8zaFcaELmfouRXBv91yC5j6L94riT9VxE+a/dC4D3ziCtW4dy1bj3IYg/eHH+U6/fjQgrEtzb/b9kAyAcK4jtVrTdEskmBWc/0/ESsji08CsJvONOV1PEPtSIpo0ZD9ia596+V31COFJB7lq6nhitOqFqJC4wQuMg+q1bB8lxEfpuAXBpQ6jiMOB+/wCLrMZ/u/sfghgxv1QoB+3tCtRTyXOR4rQgj6r8JBXEEe4MXnALMkkeAXGT5r90LgB+w89Hc0KEH9h8EDRmvMrQf9jaxB7wuBnzlD4YjvKGAGH7IHVf6FDMER5rmPquBB9VxB++8F8jcO0oZD/vjiJXAR5LgfrK4gH6LiCPquBHquAnyXEfoTmaDxWgF76LtC/CQVxBHvcF8jcO0oZD/wABfuhcCQuwr8QjyXA/Vc5Pgv5R4LWK9/vakBcCQuMH6LjI+q+RuHaUMh7oIuEGS4RUkRT9GSTXjl/+CF50XcLs7uOcftfL9tf/xABLEQACAQICBQcIBwUHAwQDAAABAgMAERIhBDFBUWEQEyIyQnGBI1JicpGhscEUIDAzU4LRQEOS4fAFJDRQY3OiYLLCFdLi8TVEg//aAAgBAgEBPwD/AKrLqGVCek98I34df1S7jSlS/QaFjb0lIz9//Q0kWJ43vYxMT3gixH1Mb/SObt0BFiJ9Imw+FTZT6O3pOntW/wAuSWXAYxa/OyYO7Im/urTs9HYecyKe4sAayUbgB4AUCCLjMGiyhgpIDNqF8zb/AKB0zF9GlKkqwTECMj0c6Q3VTruoPLpHWgP+v8VamkVWRD1pCQvgLmmRWKlhfA2Je/V8607/AA0h82zfwkGpwWhkA2xsPdUAtFGNVo1+FML6bGfNhc+8CndURnbJUBY9wpSGAYamFx4/50GfnWUr0AisrcTe4p5sM0cWHKUNZr7VztanmKzxxW6MqtnxXZQjYTtJfotGqW4gk/Ooy30uZSTYxxsovkNYNqdQysp7QI9taLKBoiO5wiNLMfUyPwplP0qJ1uVaJ1J2awRQH99bjo6/9xrSshEd08fvNvnU0bGWB1F+bdsXAMpFNIisqE2aS+Eb7a60z/DTf7TfCkN1U71FNKBNHHhvjVmDbsNf/uDho597Vpv+Fm9Q0osABsHKJidIaIDopGrFuLHVU0wiUEgtidUAGu7ck8vNpiAuxZUUb2Y2H+Yc6w0nmiBhaLGp23BsRyPOElijtcTYhi2AgX9/LpgIjEo1wOJPAdb3VpeUaTDPmXWTvXU3uNAggEZg0/R0uJvxI3j/AIbMPnyRoZNG0iEa+cmRfE3HxpAQqg6wBejlpanzoGH8LD9a0z7of7sX/eORowzo51x4rfmyrSv8PN/tP8Kj6i+qKsL3tnqvVi2kyhThYQIqm17XLZ1pSn6JKCcRETXY7bDXT3bSdFsTbBI5GzUAPjyIhV3YuzYyCFOpQBqFLGqu8g60mG/5dVTdLSIE83HKfAYR/wB3I8avhxC+Bg47xyLKrO6C94rYt3SzrRLsjSsT5Zy4vsTUo9nIjM8rEG0UfR9Z9vgvxqR1jRnY2VBc0jYlDEFcQBsdY4f5QGBJUEXXWN19VaUCoScDOBsR9Q5P7s6yZd6sPaDTxPFoi36TaM/OLbaqn/20CCARmCLigQRcEEcKYBgVOYIsfGtFs2jmJ+lzZaBuOHL4UTHDHn0UjW2+wFaSR5BxsmSx4P0fnyKqrfCAtzc22k7a0lnVoCpsGmCtxBBqU20nR+IkX3A/KtMygJ81429jjkjMhL4wAMfQt5u81JKJNGnIBGFZUz9EEGovu09RfhWiOzxF2JOKSQi+xcRAFQ56TpJ3c2vsW/zrSmU6LKykMDE1iMxqpBfSwPw9GA8Wb+VCZTKYc8QTGd1r2rnW+kcyAMIixk7bk2A5C4E80p6sEIX/AMz8qxs0WNAcRTEqnXe2QqFXWNBIcThRiPHbTuqIztkqAsfCkikOjPbKXSTib0Q+X/Faa6paNcRUWVb299MCVIBwki191YiJU0eKwVFxSHXZdg7zU15ZlhB6C2ll7geiPE/D6pIBAJ16v20OpYqCCy2uL5i+r6jNIsy36UTjDkM1bjwPJO7pEzoMTIMVjtA1+6kVGbnlP3iKOBGsfGgyPiUENhOFhx3Go5TDDKpBc6Lla+tNan2fCgQ6g7GX3GtDJERiOZgYxHuXq+61aIvNmWDVgfGnqPmPYbikhCSSODnKVJHqi1R9DSpk/FRJR4dFvlUmGRXhuMTRnLg2V6L4tBjc605q/ejAH4VJIka4nOEXA8TqrSJTFEZAuOxUYb21m1aXlGH/AA5Y3/5C9aTlJo7bpsP8SkVOJGj0sP1Al48ty3PvrSHbmomUlS0sWrLInk0hVXRpgosObkPiQSaQ4YVO6MH3VoQtosXFAfbnUaskWkO4ws7yPnuAsvuFOuH+zlQbUjH8RF/jUWelTtY9FY0Bt3k0EUMXA6TAAngNVaP0ptIk9MRDuQfqeTRkWSFmcXGkO0hB3E9H3AVLII0LEX1AAayTkBWmTOrRwR3DzsBjHZAOdaX0+bg/GcYvUTpN+nJIzKBhXGWYL3X1k8BUkixoztqQXNaMjKhd/vJTjfhfUv5RlUMJjxsxxvI+Jm+A7gKSTFNNKWwxQrzY824zdvDVQIIBG3OtJdujDGbSTGwPmqOs1AWAF72G2ovLy8/+7jukXE9p/kP22dGWRZ41xMvQdRrZDu4ihmL/AFEZjixLhwsQOI2Hkg8m76OdQ8pH6h1j8p+VCNQzOBZntiO+2qpQFnRj1J1MD9+tfmKiKJ5BSTzSLr3HVn4V93pfDSI/+cf8qn6EkU2y/NP6r9X2NSR4WdrsecYNY6hlbKprrpMD7Hxwt4jEvwqXo6ZA34iSRnw6QrSkA0WYKAOgzZb9daUMeiyEfh4x4dKhhkQXAIYBrH21pgvo0vCNj7M60o9CJv8AXiPtNTC8Ug3xt8KSMS6PDckWEcmW9bGtLd0RShsTKi6r5MbVpP8Ah5v9p/hSn+7g/wCl8qilMWiaOwGLFzSfxZVpRto8x/0n+FaQP7tEvnPCvvFTSrFGZGvYWGWvM2om2dRTczofPlSxdjJhGsmRsq0tyujyMMiVwjvbIfGiVggv2YY/+0UsBZIMbG8bCRr7Wt8iafpaVEv4SPIfzdFfnRlAkn0hrlYF5lQNp1t77CnlCR844IyHR1m52VzpMxiUXCLic7ieqKby8uH91AbtuaTYv5dZ40SBrNq0qVkjsn3kpwR952+FGCNIBGxskdmY78PSN+/bSzKYhMbopXH0tYFaMrMW0hxZpclB7MY1Dx1mp3Ln6PGekw6bDsIfmdlIiooRRZVFgOA/b4pVkDEAjAxRg2RBFLMeeaFlw2UOhv1l2+zkjkWRA6G6tQiUSNILguAGGw21HvrSUOESp14TjXiO0viKmlbmVmiOQwyHLrJ2h7K0lDJA2DrWxoR5y9Jai5uTDOo6TxgX4HO1TqZMDREM8Mw2+Dg+BqWMSRvGe2pH860eTnIlY9a2Fx6S5NUkDRaIQDjOjyc6m/Cpvb2XrTGHNxTjVHLG9/RbI/GpVxRuvnIw9oqDp6LH6UKj2itCbFo0ROsJhPeuXypZOfhlFsOckVu7KpWxf2eH3Rxt4raiLgjeK0QMmjRiQYCq4SG4ZCtM+7Q7poj/AMhWlf4eb/af4UseSy3yGjYMPvvQH9xg4GE/8hWmkDRpv9s++tIHS0VP9YH+FTWmi4gTz9IS/cM60psOjytujb4U62XRIPSUn/8Amt/jWl5iJPPnT2L0j8K0zNEj/FlRPC+I+4ckUgCz6S2oscPqR5D2m9aPCBAgkGJvvGv55OL3UfLaRh/d6ObnjIdQ/L8anbm1JjA52Zgi8WOVz3CmTCiaLESGYXd9oXtN3tRiLSoCLRQAFfSfZ/DUXltIabWkQ5qPcW7bD4VpHlXXRx1T05fUGpfzGnjVwFYXAINtmWqmBIIU2NsjrsahhWJLA4iek7nWzbSailWVSyXw4ityNdto4ftLSIpUMQMZwrfad3JHNikkjYYHjNwPOQ6mHJHI4meKS3nxnVddo7weSTyUom7D2SXh5r+Go1pEbMA6feRHEnHevc1RyrIgkXUd+zeD3Uo5mcr+70g4l4Sdofm10JvLNCwwnCHQ+cNvsNCR10ho3PRdccXh1l+daOAjS6OdSnGg9B9ngbitGODFAf3XU4xnq+zV4VovQMkH4T4l9R8x7MxUUbxzy5eTltIDubUw8ddXF7XzqLyekyR7JRzyd+px8DQkV3khI6ire+oh60VLwGCVbiNjH0hkyjqn2VbK1aCf7uinXHijP5TatFjaOLA4sQ727ixIrRMjpC7tIY/xAGtFVX0doXFwjSRMO4/pTOyTQQr1GR73zPQAtnWlqzQsqgliVtb1hWm/4dzuwn2MK0o20eb/AGn+FHLRs8rQ/wDjTf8A46PgkR9hFadbmGG2RkT2sKn/AMTow4yH/jUqltKgyOGNXe9sr6hWnH+7OPOwp/EQKeENLHJiPksVl2HELU6M2kRNboRq7X9Jsh7r1NnpOjr5vOP7rD41pEnNwu+0L0fWOQ99PA3NwQAXQEc6eCi+fea0mUxwu69a1l9Y5CoIhFGqazrY72Os1H5WdpexDeOPi3bb5Usah2cDpPbEfV1UyhlKnUwtlxomPR4dVkjWwHwHea0eNlUu/wB5Kcb8Ny9y6qSaNy4Vr82bNuB76jkWRA69U3tfhlenY6QTHGbRDKSQbd6J8zSYAMKWsnRsuy2yopOcBbCVAYqMW0Db+0SxrIhRtR3awRqI48ksRZ45EsGjaxvtRusPmOSaPGt0I5yI4kPpbjwO2lfnoiUJjYgrxRtoPdSqWjCy4WJXC9tR31GJBKsZfOG/W/eRNqPrKcv/ALqRRG5JF4dI6Eg2BzkG7m1Gjo4MHMlmNuq56wI6p8KdnkiWVR5fRnOJd5HXX8w1VIOeiWSI9IWkjPHce/UajCTGPSFLKwUrbv1q3ca0jybxz7FPNv6j7fA2qfyc0U+wnmZPVbqnwPJMMOlQSecGhPj0l+FabdBHOBcwyAm3mt0Wrm15wy9opg4WGfJEWXSpY2YsGVZUB2bGArRjZp0PZmLeDgNXOsZUCgNFIjNjGfSGrOoTbStJXfzbjxFj8K0h+ZieSNVviBI1XLEAk0VBIJAuNR3X5NO/wsvq06rIhUgMGGo6jUiycxKCQ7GN8IVbbMhtqbLQFuMPQhFj3rTrHIcDWYoVe18weyaaENKkpJ8mrBV2dLWaZ1XDiNsbYV4k7K0zMQr52kR+7pfLlZFEnPsbYYypvqC3uTUzCSSCMG6seeNtqp1fabVcar6ql8rpCRDqw+Vk9bsL860mUpH0PvHISMek36a6JXRtHAXpFQFUbWc6vaajvDDime5zd24nYPgKBuL1JKruZHPkNGP8Uv8A8fjU0jNhhjuryi7HaibT37BT6Oph5hSY0yHR122jxqU4yNGiOEADnGXsJ5o4mnjIiMcJEZw4VPm1FEsaBF1D2k7SeJqacoVjjXnJX1Je1l2sx2D9njlDl1sVMbYSD7j3HkniMiWVsLKQ6NuYavCoZecW5GF1OF181h/WVRsWUMVKE61OymXmZudGUctll4N2X+RqUGFzOoJU/fKN3njiNvCj0kOBrYl6LDPXqNFneNZLeX0ZrOo2+cB6wzFeTmi85JV9xqETCJka2NMSxs2eIdljb30kpBTSCMOM8zpC+a4yDe33GkR4pSqi8Ml29Rtvg1DyU+HsaRdhwkGv+IZ0YCZZb5xTRgML9oZZeFIDPorxMemuKJj6S6m+Bp0lMBQPaXm7Yxl0t9aUrHRycscQEo3YkzqNxIiuup1DDxonkMIMqy3IZVKW2EGly0uVbG0sSPw6JKmtAy0dV/DZ0P5WNN0dNjP4sLJ4ob/Og3OyTQyAFU5sjjfPPxHJetIjMkLxg2LqQL08jRtBGLdMlW/Kt8q54c9zNs+b5y/ja1MqsLMAw3HVlQRQ5cDpMACd4GrknGLSNHXzS8h/KLD3mp89I0ZfSdz+VbfOneT6RGi3wYHZzbLcovWksxeCNSVLyYiRl0EzapI1kRka+FhY23UsJE7Sm1hGI0A2DWajjCYrXONy5J13NJGiYsIC42xNxJ200as6yHMoCF3DFrNDyukk9jRshxkbWfyipYjJJESRgjJYrvbs+ytJkYBYozaSY4QfNXtN4VIkMUKgrdYiCi7S/Z7yagiKgs+cknSc/BRwFTSsCI485ZNV9SjazcBUUSxJYG/aZm1sdpNRTLKCyg4QbBtjcRwqaZg3NRANKc8+qg85v0qOIQqzdKRz0nbtMR/WQqHnSuKWwLG4Qdkbr7T+y3HIl0keGQ40lLNGWzyPWTw2cKgjeImLXEM42vqHmHu2VOpjb6QgzUWkUdpP1XZUiCaIhWIxAMrrv1qaQM0QEqjEy2ddY4+2omKNzEhv+Gx7S7j6Q/nUETxF0y5q9494vrXuGypfJSrN2HtHJ/4N4ajRC6PE5jQkLd8IPibUrBlDDMMLjuNSQEyEgYo51KTL4ZMPhWjSNhaJ85IDgPpDst4isQ0qAlQY3VsgdaSIcr0dJP0bnwuIr113WNn9lFhFpCsD5PSrDukA6J/MORpEVlRjYyXC8bUAALAWA3UeRmVRdjYXA9uQ5AoF7AC5ubbztqWHHJFIGw8yzG28MLUuWmyDfAh95om1bRyNGrMrEXZLlTuvWBcRewxEWJ22FHkFGIc6Jb5hClu83oHFpp/0YQPFzf4DkHT0s7oIgPzSG/wFTy81E0lsWEZDedg8aklkXmgqXaRwG9EWu2dPGWkja/RjxG3pEWHzoiTnQQQIwpuNpb9BU0oijZ9eEZDeTqHia6WjwIos0sr2z1c4+bE8BTMFBYmwUXJrRlLk6Q46UvUB7MfZHjrNR+Wk50/dxkrFxOpn+QpZSA+kvisx5uGPaRsy85jUEbLd5M5ZM24blHAUcEgZMmGaMPkaeQg8xAAXAHqRjYW+Qq66OAigzTSm/pMdrMdgFB1LFbjEBcjbY1POIwABjkfJEGtj+m80mPCMdsVulh1X4UjFlDFSl+y2vx/Y54ecUWOF0OKNtzfpvqGXnFzGF1OF13MP6ypsGkIyg2KNa/aR11GoZC4IcYZIzhccd44HZTTosqxMCDIDhPZJHZ76j8hJzJ+7kuYjuOsp8xyKfpCNHIOalibZ2T2XU1BKXujjDLHk4+DDgakL88Y5OnDOuEZdVrZg99aM5s0Lm7w5XPaXst41BFzSlL3XESo81T2fCllbnmiZbDCHRhtGo34g1pClHTSEBYg4JAouSh4eiaWHDM0itYSKMSekO17KJEOkYT93pXuk/wDkKVFVVUDooAF22tqqUYJ4pBqe8L9xzX3/ABrTEJixr1oWEq/l1jxFSOTJo0iEmNyQbemvRJ7qNGpo+djZL4cW2r1ccnMjnueub83zdtlr3vR18hrOr7+TbQ5BGgdnAsz2xHfh1VGjK8rsb42GHgoFq0Q3SSc/vnZ/yDJfcK0LGYecc9KZjLbcG1AeFGr8kqNJNEtvJxnnWO9h1V+ddG4BtfWBt76n8rINHHV68x9HYv5vhU7FiNHjNmcXdh2E2+J1CuhEmxEjXwAFRK0jc/ILfhIeyp7R9I+6p5WLCCE+VbW2yNfOPHcKPklGjwZyHMsc8N9bvxolNGjCIC8jnojtO+1mPxNRQFAzsQ0zjpPb3AeaN1XXRlCi800pv6TtvO5R7qjjEQaaZlMhHSfUqjzV4fGlYMoZTcMLg8DRlQSCO/TYXsM7Ded37HHIkih0OJTf3VMrI3Pxi5AtIo7Sf+5dnspMDDnEsecAOIbd1TIwYTRi7oLMvnp5vfuoc1PGrWDqbMOBHwIqaISoUOW1WGtWGoitHlZ1KvlJEcDjjsI4Gpoi1pI+jKnVOwjarcDTEyqJ4hhmhuCh1+lG3ypZ0MaSG6CSwAbI3OytJBTDpCC7RdcedGesPDWKmUyRBoz0ltJGRqvs8DX0hBCJzfDYE2FyN9+7bWvMU/O448GHBnzl9fAj6ujw8zHgxFwCSt9gOzwqGXnExWwm5VlOwqbEVwq+VW5NX1DrrOtlCtpoauUi4tvqWBhopgh14ObGI7NR91O/NtBDHa7G3ciDOjqoj30zKilmOFRt5EhCyvKTiZ7KPRUdkeNLGYlkZRzkjkvnlc9le4aq0eIopLnFJIcUjcdw4DZUsPOOmI+TQ4innN2b8BU85S0cYxzSdRdg9JuArA8EYSMGWaY9JyMsW1m4DYK6GjR3zkkkP55H/r2VBCVJllOKV9Z2KPNXhU8zJhVFMkkmSjZ3sdgpVTR1MkrY5JOs1s2OxUHwFLE8rCScWAzSLWB6Tb2+FMCQQDhuLXGzjSsqYotFXnHv5SVs1B3u3aPCoYyiWZ2ka9yzbzu3CldWvhN8JKnvGujKgkWInpuCQvAbTu+1lcohcKXw9YDXh2kb6VgyhlNwwuCN1JMeeaJ1wHrRnY6/qN1KqrfCALnEbbztozMkwRwBHJ92487arfKj5CS+qGVv4HPyb407NDJjYloZDnf922/1T7qf+7uZR9zIfKDzW88cD2vbWkSukYkQBlVgX29DaRUkZLxzwkEmyvnk0Z/TZRIAJJsBrJqYFG+kRjFYeUUdpN44rsp1jniIviSRciPcRSqQoVjiIUAnfxrRo3jDxkdBW8kb9k528KsIpih+60km24SbR+b41GgRFRb2QYRfPVy66sK1auSOJkkla4wyMrKNxtZvbR30OS3JqocnHlGur5kDkzNZUDyFVLBiBiXUbZi9HVyTI8kkSW8kDzjneV6q+3PklMgwiMAksASdSrtPJGJBiMjA3Y4QNSrsH61NIY0LBWcjUq6yTqqCEpeSQ4ppOsdgHmrwFESBnlN2wqQkaHZx9I+6oI2vz033rDVsRfNHzqGZ5WLKuGG3RZusx3gebUswjsAMcjdVF1n9BxpIiCZpvKSAGwXUo3IPntrEVPP6Q/NjqpEDqvvt1mrDLP18UMXmdt/WPZHCmkWK0MKBnt0UXIKPObcKiEgUCRg7bSosO6pmlFkiXpPfpnqoN53ncKhgWIG12ds3dusx+2ih5tnwt5NjiCW6p22O47qmi5xbA4XU4kbzW/rXUUpkUqehInRcead44HZSI8kRj0gZg4cQ7VtTjdUbXvo8/SbCbE6pE39++ozgP0aXpBgebZu2u1T6QqImNjo0nSUgmJm7S7UPFfhUZMEgga5jf7lt3+mflS/3eTmzlDKfJ+g/mdx2UJA8jwSJbK63zDpt/mKhiES4AxZQeji7I83wqGAxM4VvJMcSpbqk67HdUUciSSZ4on6a3OasdY7uSWJZUwPe1wcsiCNx5Nta8uTOhW3kPINXKfd9S9Fhe1FtgpRYUatV6GqhyHVyGr1nV+Q1e/IZk5wRA4nOZAzwje26pZipEcYxytqGxR5zcPjUUIjuxOORuu51n9BwpJUcsqsGKZNbYaYJk7hehchm7O/PZWOSbKK8ce2UjNvUB+JqOJIxZBa+ZOsk7ydtcyxl5x3ZgOog6Kjv848kZlYlnAReyutu9jq8PtiQBckAbzTEhSQMRAyG/hRfnEXSYgRJHcOm0gdZDxGykdXUOpurC4NTRCRbXwsvSRhrVt9Lh0hMMq2eJhiUHNWGog7jsqeLnEsDhdekjeaw1GoJudXMYXQ4XXzW/TdT83NzkDA5KL3HnaitKuBBcl8C2xHNjakkWVA6G6uMjUalUVWYuVFix1njQZsbKVsoAKtvvrHhy7aG2tVBgcqyq9uFA3N+TOgaL55Vs10jZ2o0BRq18jto3A8aGulW2fId1a61UNVDbV+TdTHLKsZ250HG0UOUckiMgP0dFxyv0mOoX1sd/dUMKxA2JZmzd26zHjUiY1K3ZcW1cjTNFo6Kqi2xETWTw/WhC0hx6RawzWIdReLecfdUczzNeKwhU9cjN+C8OPJLCZWAZrRAZouRY+kd3Ck0hCwjhUuq9EsvUW2y+3w+2mjd1sjmNlOJSNV9zDaKjJkjKyphPVdT1T3bwaiYxtzDm41xMdq+aeK/CpVMTmdBcH71BtA7Q4j3ig4gcMvS0ecjV2Hbb6rU8N5UlDFWXoncy7jU0bX56L7yPIr56+aflUTtJIJUbFC8dip1q6nd8aMI54TAlWw4WA1MNl+6nkKvGMBYSNhLDs7r8kUTRSuqjyL9NfRftL3HXVzV6FAVtrbRoqb5VjaszSi1CjR1clzSnOrDkIvlTZAV1hxHINVGrcnhV630OFba+Va6wisAoCiaFMbUGvyRNIwJkUJ0uit7nDx41KQs11xSzMtkjv0UG1juHH2VFDhJkc85KwsW3DzVGwchnZyY9GAa2TSH7te7zjQ5vR73LSyym52u5G4bAPZXNyzffeTj/CQ5n12+QqwRbKtgoyVR8BUbOy3dcBPZvfLjx+zuL2uL2vbbapjIsZMSh3GYVtu+opVlQOuo+0HaD3U5YIxQYmA6Kk2ue+lKaTFmCpBzGpkcfMVFI2IxS/eKLg7HXzh8xQ/u72P3EjdH/TY7PVPuNTSyRyR2XFG7YGI6yseqe6lhCyPIpPlLYl7Nx2u+lgZJi6EBJM5EPnbGX50jSFnDrZVIwMO0CKJAoEHkO+rclwD38h5NlYRQUWoa6+XI2qsG81ZBWJaBy5NtN1aW4oqDmMr0OQVto8vGhRrUeS9bKtV6OZpdfI87MxigszDrv2E/U8KihWMGxLM2bO3WY8and1YMXWKFbFjrZj5o/q9YXmF5bxQ68GpmHpnYOHtoSM4waKqqgy5wjoD1B2vhV4dHPSYvLJr7UjeA2e6nkSMYnYKOPyqOR3uxTm07OLrHiRso6SXJXR050jIuThjH5tvhQvbP7KeHHZ0OGWPNG/8AE8DS3sLixtmONSK0LmaMEq33qD/vXjv31IZHRJNHZTniseq67r7KliYNz0VuctZl2ONx47jT20iMNGcEsZuuLIq41q3ftoqHTDIAcS2YbONQyGN/o8pz/dOe2u71hWkxOSs0X3sXZvk67VpSSoJGEkXsdnDka96UGtVHlfXatVGttDkvasQvW2hqpzYfUjOVuS9bqsKyrfQoUOU0oN8+TXR5bUd3IQDQHIFVBhUBRuAsOSURpJzszYzqhjAvb1V2sd9Mpcc5pREcY1RXy/P5x4UDLKLKDBHvP3hHAdnxz4UVaIiPR4+k+bSvmBxY62PCugklgH0rSBrJ1LfjqT419HaTPSGxf6SZR+O1vH2UGQMIwQGtcKN3dSMWF8LLn2tff9mXUOEuMRGILtsKDNjK4bKACGvr3jwojmSZYunESecjXOx2svzFSzOESWECVL3e3WK714ipEYETwjpW6S6ucXd6w2VIBpMOKJyrA4kOqzjY3zFBcQUuqllsd9m225bimGYoVwoGhyAYmvsFa+ThRoua6RoIRmeVhesNYaC0FHJattHVybaOqr8SKxHhWLhyHZW08go/U4141socsolwgRYQxNizdkbTbbShI3Kxg6RpB6zMdXrN2RwFJB0uclPOSbPNX1R89dNIwdUVGa+bNqVR8zwpmCqWY2Ci5PCkZWUOoIDZ5ixqVXYBUfmyTmbXNvR41FCkYsozPWY5sx3k/ZkgazapXijCvLYYTYMRqLZeFSSLGhduqoubC/wojmj9Ig6Ub9KRF2+mvHfvr7ry8PThk6Touy/bT5ijJIWR47SwuLHDrB87iN9cwRMJY2wBvvFtk+48Dy2rKrV30TbgK2ZcmzKgAK21x5Qo3Vu+rYGgBW/6m2jQ1Vt5LCinGsJGzk3UzAUCdvJnWytlF91K16vwofUkSaRsOMRxeh128ezTQsqrHAVhXtEC7eHE7zSrhULcmwtdjc+Jq9SypGuJza+QAzJO4DbSszJiwlCb2D+69qxJG562k6QR2dY4bkH9Z1FzuG8uEMTkF2Ddfafs5I0kUo4xKdhrm1wc2emuHD0s7jjSEwMIpDeJso3Oz0G+RqJUjJhRWUKMXo9InIGj/dmxfuHPSH4bHb6p27qjSKIYUCoGJa287eTXWvk21cVsp+rSBvChur51srZyDVybaPIOXfQOVfrys1qx0pJzrFasQJ5BqoaqPJtoWJJ3U51UDcV8a2UaawGW2r2q530uujWQq45EhIcyO7SPna+SqNyryCpZYkK4s37CgYn8BWGeXrHmE81c5D3tqXw9tWh0dOzGg95+ZpWDKGF7HPMW9x+zlEuTRm+HWh1OO/Yd1XTSImVWZNht0XRhvqNiwOj6QAz28JE84fOo2aJxDISyt91IdvoNx3b6ZnjlKynnIdIOFb9hj2TwOynisV0dyVsb6NNtB808R7xVjbOs6HIxyoZirVwPKa21wocptRcAXJt30dIiUXLi3DP4U+loFJU4idQHzrn5jmZUS+wj+VDSZcdmk6N8yoBqTSrC0QZ234cqV9IJsS6jeV/lX0h1QDosQNdmHutR0mbETe19myo9Ie1y2frBfdavptjZlvxDA/Cvpqk/dkjfUemBiQEPtHztRmG3EnevzpXVtTqfGh30NVDVR5NnfQFr8asCBerChV8uQm55FGdBQDR20OQGjyFlBAJALahtNLEiszhek/WbWfbQ0hWfBGDJY2Zl6q/m2ngKmaBGV5SuMdS+bflWsU8vVH0dPOYXkPcupfH2UoKqASWsLXOs/Yy85gPNYcfZxaqjYsgZlKE61OsGhEgkaQCzuArHfbVUxDyiFwYyRihlHnDWP5baBEobR5xZwLm2phsdD/VqikIf6PNm46SMe2o2+sNtNzc/OQMCClr3FteplqNSqKrMXKi2I6zyGsdqYnbSGxo8orhW6pJo0tibDTabENWJu4frR0xyLrgT1mufYKadz1pj+Rf/AKoup1tK/ebfrXOLYDCSBsZifhamIJyUL3fzoSOBZWKjhlRZmN2Jbvz5DI51sx8axNvNB3Gpm9tCaQHN3t307s3WJNtV6UqD0hccDaseE+TLoDx/Si9/3iP66/Oxoh73Sy/7bfK9c5iFpCzey/vpZMPVlYcHGXuvSzztbAqOL6wf11Ut7DEAp4Z8l/rWoi+VFSORRYd9DfXzru5dfJGLOwhQzSgkPPNkL7QDr8BUkyR2Dm7tqRRiY9wpUmYBQBosY1Kti/6L76jhjjzVczrY5se9jnUmkRo2DpO5zCIMTfy8aRmZbspjPmk3Pu+zkidJGniuxa3ORk9a21dxpWjmVXFmANxfWrD4EVNGJR0WCyRm6sOy248DtFIV0lQJFwyQSDEB2XXcdxqw11ejW2nFjWy9AXoDLkNXrv5NOQYQ9s8QF+H2ojc6lY+FGGUDEUa3d9bR4w75jEFBJFRphUDK/DUOA+pbkFuTZy66wi/Id1NcAmlvvrbXGpDKMIjVTc9IsbWHzp3VFLOQqjWTRkklHk/JR7ZXGZHoqfifZUZXMaMnOE9aaQ9En1tbeGVNOsYCseckI6sY6R/LsHfUTSNcugjHZGLE3jbKucjx4MS4yL4dtuP2glQu0YPTQAkcDt4062dpICrOtudjv1u/c241EInbn4yQWXA66sx5w84Vlfvo1ajRvTi9Bd9WAGVDVye/6lqeCJ+sor6FDuPtNHRIT2feaGhwjs+81NoVzeOy8N9HRZh2PeKj0KQ5v0RuGZpdFXaqAeLH25V9HTUVS3q02hQnUCvca+gJfrNQ0TCegVX0iMTe+ho47UkjfmsPdSwxqbgEH1jUmixPmRY7xlQ0GO97sRuNfQI97e3+VNoGeT2HEUugC/SckcBao4UjFlFvq25MuTOiaPIOQUy3tSi1cKtSzh5Ciq5C3Be3RuNl9tTxgssgj551yUFrIvpEH9KcpitO5nfWIIhdR3rt72rDPL1j9GTzUzkP5tS+FRQxxiyLa+s6ye87aOeVRxRxDDGoUbd57zt+zmWUOssRLYRZor5MOHpUsquhkQFrA9HtXHZsdtYedA0mAc3LqZWyxW1q/HcaRVkcTRsYXBwzJbX6LDfuNY0L4MS47Xw3zt3VnWdFs6BvyH/I7Ufqd/IeTO+2/JtpsWE4LYtmLVQ0gr5O/wBIm2rGLAd52eJrmZZM5nwj8OLIeLaz7qSNEGFFCjhRR7GTSZQirngjOFB3trNc9NL9zHhU/vZch3hdZ91Z21+NRRiNcIJa5uWY5knafs7S87fEDGVsVIzB3g7akhbFzsJCybQeq43N8jUUwkBFisidaNtY/lxpTz15IxzOkRHA6tqPotvB2GlEU7q5BjmhPSGphwO9TUs5jkCsuGNxZZdYD7mFaPMxJhmsJk9jrsZfnWEX+vb/ACO3LegKvvq9CpExoVxMuLapsfCo4kjUIihVG6pFa7NNKI4l1KnRy9JtfgK5+STKBMvxZMl8Bral0ZcWOUmZ9YL9UequoVJpEaHCTifzEGJvYKViVBIKXF7HWKjgjJEhYztrDscQBHmgZD7KWdIsOO4DnDit0R6x2UqqhJxHyjXszZX9G9SQukhnhN2a3ORk5OBu3GiE0hQ8bFJEyDamU+aw3bxRmMMSvPruA5jBKjieFTxYwJIiBKgujbD6J3qaKc5HhlUdNbOuzjTQxsULLcxG6HdyW5PdQZTqIq4osoFybDjR0qHz1+NHS4R279ymn089hfFv0FJpzg9MAjhrpdIVs16Q4EX9htQkG4/H4VjXj/CaxCsQ13oG+Y/YZtJSLI5k7BT6c56qhe/OoHxRqxNyRnVx9jbkvyuZixLskEKHXrZhxJyUVz8kmUCdH8WTJfyjW1Joy4g8pMzja+oequoUUmLli9kXNUj1tbzmPwoieQEysNGj81D0rek+zw9tROoGHQ4gRtkbop7dbf1nUiItm0qUyYtUY6KnuQZt43oPMwCwxiFNjSjZ6KD52qOMpcs7yM2stq8FGQ+xZQwKsAwORBqWAouEqZoAbhf3kfFDtt7ax85FigZSSvRY5i/GjE7gSgcxOMj2la2xrax76inWTFE64JFHTjbPLeN6mgAAABYDIActuXSjM3RRThOsj4UkM6nonCx2YhemZ8RxE4hvNEk6zf64dxqZvbRJOukdkN1NjWjyrIlxlvG4/allGsgU2kRLrYfGpNPN7RjLe1HTJjtA7h+tO7O2Jjc8iyyKMKsQKE0oN8be2otLkDDGcS7aBuL8jOqi7EAcabSYgpIYG2wHOv8A1A36nvptMub2f+Ow91HS5OzZfeffR0mY9s1z8vnt7aTSZVbFivwO2oNJWTLU248ksUaNzsuOclrRpbEF4KvzPJI8iviZoooVtm2bN8AK595MoEJ/1JOins1n+s6JjL2OLTZV7K/dqf8AtHjc1BNjxK2HGhs2C+EejiOsjbU08UVsZu3ZVRdz3CpDOxURYEVhcu+bDgFqNCihWdpD5za/d9iRcEXtfdQaXR8pGM0X4nbT19440kSB2lQ/eAXAPQPpW30kyM7xg2ePWpyy3jeKsL3tnv5Xmjj6zWNPpqdkt4D9ak0yRur0B76h0qUuqkh7nblQNxepFU2JUMwzXwpmLMWOsm5+z0BTZm2E29n2c06RC7eA31LprtkgwfGmZmN2Jbv+xg0wxrhYFgNVqk02Rur0B7TTMzG7EseP2AJBuK0bTL9CTXsbkxNJGcGKEm4BZcxxwml5oN5NW0yYa3bMA+scl7hXMSSffvl+HH0V8TrajPDH5KJcbL+7iF7d+weNPz2EtK6aLHtC5t4sch4Co1Yk/R05sN1p5rlm7gcz41HoyKwkYtLINTudV9w1D7I1Ek0ZwFudj2M3XHA+d300TwkvB0k1tD803HhqpRHMI5cJuOkpPRYX2clwOSWFJBZhehoiDVhXwufa1/hTaOjABhjttP8AK1DQog2LPuvlQAGqtNkYAAHDiJvvsPsgCTYbajgAbCAJHGu/UXv30i4Ra9z7Pd9RwSuRIzGqn0uSNirp3WOyvp0t9S2qCeSQg6hiCkbPqaRAJQL5W1EUugi+Zf3fqa5lIhclVUa8rk95NPhxHBfDfK/7JoulYbI5y2Nu76ZVdSrZqwsdlHyaARx4rZBVsK5mWX758K/hxGw/M2s+6o3dhh0WNYowSMbiwy81BmfGk0ZQQ8hMzjUz6h6q6hSSyu2URRL5tIbHwUX99Ezl7ARrGDrJJYjgNnt+1JqbTES6qMTD2VJK8huxvw2VomkZYGzI6vEbqDA/zy+o2QvTMWJJNyfstFTFJe18HS8dlKoUWHjxO/60kSuMwDuuL0NGTaE/g/nSRhdpPuHsH1dJn5pLjMnICpJ5JMmOW4fs2i6TYhGaw1Z+7PZQN6UTc4xZl5vUqqM+8mpo3cnFLzcIGYTosd+Jtg7q+k4gF0dDNbLFeyD851+F6EWkjpmQPJsTqxLfhra1RhwgDtjba1re6o0KLZnaQ3Ju3H7M0SACTqFaZhLKwFmYG428L8gJGYyokk3Jz31HpUqHXiG5v1qLTI31nAdx/WgwOog0xABJNqlTC5FwdoI3fXAJ1Z0sErakb2WoaI4ze/cq4j+laNGQeoY1XVi6zHeftSbVpU3OSZdVch+v7RomklWCMbqchfYakiSVcL3w3vYEi/fatITRlwNNqXoomZBPBNpoNpDi0aLAuxpM2twQavE1FGUuWd5GbWW1eC6h9rLpccdwOm24avbTaZKw1gZ7BWbHM69p+skkYtdWBHaRrGn0tmywqVGxrn20Tc3sBwH1Iog2ZOs2Cr1jSaKfMRPXOM+zIUkCLrCn8oFBQNQ+0m0lI8tbbhR06QnIKBuqTTZGth6Hvr6TN55ptJlZMDG49/1ArEEgEgaz9qhRBiNnY6lOocTQeN1KuFjPZZV+NSQumfWXzlzH19FnDrhY9Ncu8b6sDYkatX20ysyFVtdss922mFmI3Hu+2BsQd1RaZGR0iVPHP30ssbamU+NXosBrIHeaM8Q/eJ7aOkQjtr7aEynDbtmy8eP2GkkmZ7/1lyrG7dVWPhX0abzPeKaJ0tiUi+quaktfA1u6lkiUC0eI7cRy9lfSJL3BwjYo6vsozIxu8SnLsnDTlCegCo3E3+1ucxfX9cVoukc4MLddR7eP2+kaMsouOi+w/rToyMVYWI/YAzDUSO7lGvOtGcy6QXOQVbAbvsJ9HlMjEC4Jve4p0ZDZhatF0XEMbjLsj51YclhWEXvYXoIo1Kvsp4Y3HSUHjtqTQNsbeDfrX0aa9sBoaJOex7xQ0KfcB40NAl3oPH+VD+z32utD+z98n/H+dD+z02u3soaBFtLnxFNoEZHRLKeOdSaJKmdsQ3rn9orFWDKbEVBMJUuMiOsPt5oUlWza9jbRUsLxNhYdx2H9jikdG6Gs7Nd6haVlvIoXx+X1yAcjnXNpYDCMtX7HJBHJ1lF94yNDQIwblmI3UsEQFgi+IvTaJA3Zt6ptTf2evZcjvzptAlGoq3uptHmXWjeGfwogjI5ckWjSSC4Fhvah/Z77WWh/Z++T/j/OodEETYg7fI/sEkayLhYXFTaLIjWALqdRAv7a5mXzH/hNEEawR3/bAEmwFzUWgu2bnAN22ooUjFlGfnbf8jKg6wD30dGhJvgX+uH7Pa+ujDEdaKfCjokB7Nu4mjoEexmHvo/2eey/tFHQZhqwt3Gjosw1ofDP4UmiTN2cPrZUn9njtv4L+tfQoLWse+9N/Z6dl2HfnTaA/ZZT35U2iTjs37jUWgu2chwDdtqOGOMdBbcdv/Xv/8QAPxEAAgEBBQUEBwcDBAIDAAAAAQIRABIhMUFRAyIyYXEQMJGhIEJSgbHB0QQTQGJyguEzUPAjQ2CykvEUg6L/2gAIAQMBAT8A/wCVwYJyHogD7snMMPD/AIMGgMPaHoQLE52o91LwOOh8+xVmfyia2XGDoCfKseyDExcP+A7KLazffFG4kdqYP+j5igpIJ9nGgSJjO6tlxjwpLmU/mFNxN1NA/wCk3NhQBJAGdEQY/vUCyDN84UFlWaeGLutBZRm9kjzq0LAXRiaMfdqYzIoGCDpW0X/UIGZu99Aj7tgcbQr/AGh+s/Ctn636DSkBXBzAjxoKSCRgMa2fGv6hRxNBd0tOBF1f7X7/AJVsv6i9aOPaV3A04mIpVLE8hPh2Itox7z7v7hZFi1mGg9gWVYzwxd27Lis+2LP0rZ4lT6ws+/LsF+zYeywPjd2MYdGPsqaOJof0zyceYrZ8X7W+HYGIBHtfKtnxr+oUcT17MEWbxbPyrZn/AFFOG9QuTadQOwkEAREedFiQBkvzpbkc6wvYCRMZiOwqQAfawraQCFHqiPfn2EAKPaa/oKAJIAzoiCRj/aIONbO+U9sR78qvB6UGDbQ5DaCD1P8ANG67sEgg1tLntD1oce+r2bUsaT1x+U+XYSTjSAENI9WRS8D/ALTWy4+ob4dhs3Rpf1pVsuk5lT403EetbQANAyUfCm4EH6jWzB+8UH2hR/p/qf4VZNm1lMVZFi1najsjcVfbaflUANBN0waYgsSBAm6gCSAM6LKNoPZS4e76mhebzjiaGOtRKl2zMDrS7qlszur8/wCxQYmLj6AClT7Qv6jsQAsATANEkCycjUEQcJvFFbTKcPvPjnWB6VteK0PXFqtobVl9RB6iixIUezTX7NT7JK/OlkENkD8KiNqRra8xSqWMCkW00TFbPiI1Vh5UmDj8s+BpLIbZkYzveNIBaYHJW7EJLrPtLRvY9a2v9RutEgsgF8BRQM7Ynm3lTf00HNjUmIyFPcqLytePY5KsAPUAH1pVLGK2SghnbBBhrWzutP7Iu6nDsUA4mLqUEkAZ05BMDBbh9aZpgAQFECisKqgbzb307NmBezcK+ZyHY26tjNr2+Q/GoQQUJibxyPokARBm7sfeAf3N1/mpJAGQwpb0IzQ2h86aTvn1iax2f6D5GkvVl/cOootIAjhpb0caQ1Lfs3GhDfKtmf8AUWdQK2d20HWKvB6VsuNetbPFh+VqXiHUUTZdv3CtmASZ9kmtnxr+oUeP91MtraODdFo+FbPjX9QpONjoGNKpYwOxltbWxMQI8BWzEuBznwq93/UfjRcAvA4t0dKF2zY+0QPC+rMqiDF94/KgstA8as7to5m750NxZ9Z8OQ/ns2agmTwreatsXtDFrvGipDWcTMXU5AhBguPM0gA32wGA1NEkkk5/j2UrE5iaK7oYGcjy7GUqYNFiVC6YVsyJsnBrvoaVRaKtzX30hhxPQ9DjTSJQ5GkNmQ1wZf8A1SmywOhpxZYgdR0pXDbTS2LJrZg2mTVSPeKUwwOhp7toeTVtRDt1nxoiwy3zg1KI2xHMjs2kF2IvkzWzxP6W+FbPjX9QoteVj15r/df93wrZDfXrSYbQ/l+JrZXWjohrZiXUfmFA37RuR/8A0a2frHRD9K2eJb2VJ7GF6bMf4Wp23yRhwjpXCk5v/wBf5pBaN/Cok9KBknaNlgOeQoNCn2nx5D+abdQLm283yFJugv7l6/xQYgyKGN9MxYz4DSmUqYP4kAmYGF57GWAGBkH46dhUWQw6N17F3ls5i9fmKRgDB4WuP1plIMGjvLOaXHpRXdDDWDyoqCgYYgw3yp7wr63HqKe+H9rHrnW0vsv7Qv6imIKL7S7vuy7GvRW9ndPyqyQA2p+FbQ7wdTxC1dkc+za8ZOt/jW0ILSNB8K2mCH8g8q2hIcMMwGFAAq7HEEedbMgMCefwrZcY99bPjX9Qr/c/d86H9Y9WrZcYOgJ8qXgf9vxpSBs31JArZcY5SfCg0KyxxR5UCAjDNiPCluRzrZFItpgKDi075+r76RbTAHDPpTtaYnw6U26gXNt49MhRJIAyFAwZ0q925sadgTA4VuH1oqREjiwplKmDQFiGbi9VfmaM4nO+mWLpm78QrFTI7FaAVOB+OXYpg38LY0RZa+8fEUSA0rdfdTWSpaOLyYfI0pLC7iS9elW9+1HUfGgArFTwbQXH4H3Uu6xDYcLUZW0hvvn+RSXhk1vHUUm8rJ+4e7HsUzs3XSGrZXkp7Q8xhVo2bOUz2MAdmpAiCVNPeEOqx4XVZFkzcwOFN/TQ/qFILTAEmp7Nl/UXrQJBkXRSkW1i68TJpf6xzvagWW8XTIoNCldT8KAJmMr62frHRD2gkiyM2mlFlXY4jd95x7F3ULZtur86RQWvwF56UJd77pvPIUd5oUch2KpAgce08l/mlUCWbBfM0HNq0d486W7fa/2eZoNvS29rTMWMmlSQSTZUZ/hysQfaE9iNZN4kG49KZbJ1BvB5URBiZoG0tnNeHpmKXeFg4+qflQuN4wN4qAGK+ptBcfh4VeraFTTWSwIwN5GmtFbimMbyHUUSGWSd5buoriSc0/6/xVvdWOJWu6UdzaBhgYYdDQK25jdnDlWzItxk277jTAgkHLttGyV1M0b9mp9liPG+ttxk6gHyoX7I/lYHxqLKqwN5ntRrLAnI0ACHOl48as7tr81mgSLxdUmAMh2IYRzrC+NJwOeg8TQAsMTjIApAIZiJhfM0pIIIyotuBedo0WJjkIokmJOFwoMQCNca4U5v/wBaVoDatdPLOkAvY8K+egoFmY38WJ5U7TcOFcKVQZZuEefKmYsfICmUrAOOmlKoi01y/HkKLWiBwjADIU1mYXLPX8Ob1DLcVuaPI05DQ3resPnSGRYOfCdDSmy14wuINGA26c7qYSLY/cNDTsGg+t631pd5SuY3l+YqS7CTjdNEQYOVK27jDIZX6VtAJDDB7/qKj7t77wR4g1Y37E44H4VFpCPW2fw/jsCkgkerj6ABNw7JJxpWhWWOKPKj/SH6z6AJAIBxxqTEZeha3bPOaw2Q/M3w7MNmPzN5ClW0wGtBQbUnhF3PSgYVh7UVu2TdvT5Uq2mAq53JNyqPIUBJgU5AhBguPNqbdWz6zcX0orgg6sf80p2BuHCuH1q8EHDMUF9dzd5mr3kndVfAcqgxOVItrkBiaMTdhzoiDEz+DRrJ1BuIplg6g3g8qEoQTmPEGmWDIwN4oISpYerjrTb62vWHF9ewiwQy7yt56g06xBF6nD6UIs2hcyGeopwLmGDeRzFM1oznF/Oioshgc4NIQQUJ5rOtFpUKRgbjy0riSfW2f/X+KJJJOuNLejLpvD51szvQcG3T76AEOp4h8se1WssDp2R2Wt2z+a13B7CxIAyGFEghQMhfW0xCD1QB762sWrI9UWfQUgKxzO6PnV9JugvngvXX3UgjfOWHM1ex1JpiFFgfuOtIoi23CPM6VxG2/DpryFCXMm4DHQCmeYAEKMBV7mTuqvgKJtQqi7Ia8zREGDlVkxay/BlSDBpSCLB9x0NGRccsqUiLLYHPQ1vISMDhSsVM/wCRTqAZHC14pWAuPCcfrQ3TYa9Wz+BFFDaIF8aVs75Q4Nh+qlNlobDBqsG1Zz7BZgzjl6LtaMxFMtkxjnPd5+mrD7y22s3UBIdjl8TQ7ACTA7C0qFwA8zRa0VB3QLqdpMC5VuFK1kGMTdOgpEm83KMT8hUhzJ3VXActBV7nRR4KKdgd1blHnzNIoMkmAMf4okuQqiFGXzNFgohPe30oY60QTDbQwMlHyFMZMgBeQogjHrQUwWyHeqJMTFEEGDlRXdDAzryNEk40FBWRiMRy1rjH5l8x/FABhA4h5j60N8WfWHDzGlIoJsm6cOtAgBkb3cm7Fgiw13snQ0CUbQqaJvkXU5DQ2ZG91riWRxbP4fxRMkk5+hPazAqozW7vbBs2jcMufpSYjtUgBj62A9+J7Fs32jl59jFboGV/WlEkCY5mnaYUXKuH1rdICi7VjTsOFeEeZ1plCiCd7QYClWb8AMTRYHdW5eefWoB3EFrVv8wFSqYbza5D60AW3mMDX6U0TuiBShbyxuGWZpnLcgMAMB3zNaAkXjPWlaydQbiOVMtkgi9TeKJCsGQ8+nKiMHS4f9TRvFtbvaAyOtNeLYuPrddabfFscQ4h86O+s+suPMa1ZhQ4P8Gma0Zi/Oma0BI3hidaYgqMmF3UdisVMj08vQPcLsHZbQFxpNiBL7SQq6iJraOXacBkNB+BsmLWVKs3tco/yBTNN2AGAoqQASInCgTgJvyqFXi3m9n60zFsatCzAHU59jBRcDJ1y76JwoY6VEE7Njc2B+BoggkHEUrWTqDiNRRlDKm5hdzFI1k6g3EcqdbJuvBvB5ULS2XFEydJOFEFTBxFEgkkCOVQIBm/T0m2TqoYjdImewKWMAFjyraqERUI3uI+/wBDZ/ZpQs1xxHZ9o2QUKy3SPQVrJBAmMedbNdmzgi9bEwb75pyQhIyE1ttudoAIiMevdbEKXFqLPOj9m2TXiR0NN9kb1WB63UdPRBBO+TCi4fKmYsdAMBpQMGYnrQDOSSepNWgLk/8ALP3aUyhRvcRy069isFFw3tdKKGLTGCb4OJ75SAbxaFHdMqZzBphaFsfuHPWlNoWD+0/KotiDxp5gfMUGhSpEg+RpSOFuE+R1pgAtkjeBx1FWjZs43yOVASCZiL417GYMoJ4hd1HcbHbpYCvlu9RQ+zbIHD3TQsruiByrbbQs0GN0kXdovIFQIjKl2aLgoFfaVnZn8t/arFTaGVbAh9qZAAZTcMKH+htYPC2dESCNRR7sMReDHSvv9pINo3egi2jHvJ5UyFccxPj2MFEQZ1pb1v3VBvOZ5UzTcLlGX17LAF7/APjmfpRl8AFVfAVaVeHePtH5CrybzjrTAA3Ge7jOlCkgMYFMpUkGhEiTAogo2vwIplEWl4T5HSuMT66jxH1pVBDXwwvFFiVAPq4GiwKwcVwPLSiBAg9aVWYwoJ6UylTBEdfRXZsyFhfZxGfX0fvHkGTK4U+0ZjJN/oIQGBOAM0ftZNyL41a+0tgCPKjsducfjTAgkHLt+zkDagkxW2CM6gkQQwnTSk252YKNvWeEijeZ7D3yEqMJDXnoDW0JKg+1f78+wIALT3DIZmmYtyAwAypQCCACzHDQc6kLcu82uQ6VZAv2hk+zn763nwEKPcooKSYAmmAEAGTnGFWIvc2eWfh3aNFxvU4ijyoEMLJxHCflS2QSHB+YpWEWWwyOlDcaGvVseY1FTBkZYUwtC2v7hofpSML1bhbyOtHHXs+zsq7KQLyY5k5Vt9oDcd5vJeQ9H7KoCFqN9D0IJr7trJbIZ9v2dA7wcMaCqtwAHTt+1JDz7Q73askKqRcLzESe5VyuHgbxTGeySTJM9iyVsqI9o0DG7s95va+lbq477eX81Ibedrhgo/y6ryL42afH61bC8Aj8xx/ioJFrzoiM56d3BiYuqBEz7qG9utc3qk/A0qiSrbpy686UjgbDI6Gl3GhhIwPSpiYJg/D0EcqDGeenT0D2W/u9gF9Z/gc/R2f2YEBi2Im4UfuExhjz3jW2+0bNkKLJnw7dltChkY0v2gRLKRzF4o/aFyDN7oHiaf7S5wNnp9abaO1zEmPTFFeEFdntLWEXHyr7pbwV2gIMGLxTbNReHnkVI/ALZnemOVGWEtuJkB8taL3QosjzPWgBBJMcs6AkwM6IIMHKlIBki1TMWx8Mu8UMZC+FAEmBiam1uPcwuUn4GuLca5hcCfgasiCG3WGvwq3u2SJjhOnpojOSFvIE0QQYNx7BE30zFjJ9H7x4C2jAy9IMwwJHSmZjiSevdByMgeopdvGRWfZb5GabbhlItm+6GX5ijEkDIns2WxZ5IuGpp1AYgG0Bn6KfZyQCxCWsNTW22J2Z1BwPpKVAmLTc8B9aDAkl5Y5aUTJnsVSxgUQAYmelQWGSJ/njTWZ3Z9/dhipkGDVozIuON1EWxaHEOIfMUxJ3iZm7ndXGI9ceY+tEs15vjuPsxjajmCK+0tsohhL5RiPwey2RcmIu1Nf/ABG1B8q2qhTZAI1kzQSb5UUdmRz8fTZmVFUEhWEmthsy9qIujE69KdSrEHI+h9lUs1o3hMOpplVhDAHrR2ezUFrK3X4V9oAbZK9myfrQ9AtIgCyP8z7VVjMYZ6VKLhvnnh4VvOc2NEQY7tbODZ56VejAkA+YNMIh0uHwNEBhaXEcQ+YoAFZW5kvPMa0Gxcf/AGL8/RRSzBRnTqVMHp2AkEEGCO7JGtFhGtWjqBVozeaLaX0C3OrRAq01BjrX3kUNu4Mgt40dqXMm81s9rs1EOrH30dtsCICfKmgm4R6W02luzdZsiLqV2WbJiakn0NklhAuefXs+0vZSzm3wpto7CGMj04ONFiQATcMqsECW3dBmaUOQQsxn/JqEXHfPLDxomTOHcrEi1hyphBIBnnVowFyFKIUsL8mXlRFmHQ3f5caZQRbXDMaH6ULSWXGf+QaYySQI5dgvupfs7sA0izrNfZ1QA2ZnU/KvtOzkWxlj9fTJAxouKtdBVo6+FSOZqeVGpPbJ1qak61aOpokmhU6SKnmD1q/LyqdaB0PjUtlHdK1khtDWz26PdwnQ0TF5ra7S25OWA7g3gWjZXJFoKThhqaJUGT/qNqcP5pmY4n3ZUqEicBqcKIANxnuwwICNdGDadaIZSRh9KViuI3WxGooyh3TKuPL0fszWlZff440oYPYUWVBnlEVtdoqKSc8Br6bi6e9g6VZOnpKJNDvPvXs2bV3bsVDbRQRIrawSWs2OXaLN9onlFAEmAJqyq8W83sj5mmn1zZ0UfTKghaSBC6nCmCjA2tbqsmJi7vCpADZGgZAV8PVbT+Ka0BYOV4/j0vs+0CPJwIitp9pBIsDhOJ+lMzMZYz3BAOIqyulWV0qyulFJwqw2lBDnVkaCoGgqytWBqasxhUczUUVBqwKsLzqxoasamgAMPwGy2thWAxaL+VbRgYvtc+0rAkkX5Z0jXEWrION1/ShMbgsD22+v0qUXDfOpwpmLYnsZixkme7UrBVrpwbT+KKkGDdzy61Nnce9ciMuYokqLLC0PVP0qDExdr2XVsvs+4wYXt76dbLEaHL+2DsKowm0o2YA687te0RN+FWJv4F/NVpV4RPNvpRYkyTNSMEWSczefCrKrxGT7K/XsZixnyHdytmI3pxpWEWWvHmOlMscwcCKO7uneRrwR8RRLKCOJW8P/AHSoCJBkjFeXKnUcS8J8jpX320s2bVw/uEdgMGYB60zFjJM0CLgqy3O/wFWFXjP7Vxo7QxCiyOXzNKjG/Aam4URBjGmduGLHIfPulQtMZUSTF3CKVgVsNgMG0oShgiVOWR5igoZiE91rGlaDZbhOI+dTDSpwNxoMwmDxY9xaXWrS0X0FB9RUzhf+GJAxouchQMgd+LMCAXY+AqwF4z+0Y0doYhRZHL5mgVAgC84k5dBUoLlFs6nDwpgcdo37Rj/FKSbtmsc8/GiFF7NaOi/WmacAF6dyCQZFK8mZsvr6rdaiGhx1q0Bu8afCmQrDAyuR9NpwFAMMKM59xJ17ASMKBkT30jWi+gq2aJJx7ASMDUnWg5m/t61I1q3yq2NPOrZq02tWjrQY0GB7FYkWVhLrz/PYApEAMzHwFWAOM+4XmhIF0bJdfWNOsQRgcJxpUZsMMzlS2BJaTyGHjTGTIAXkO6IV+EWW0yPSixgKfV8elFSAGyPoEgY0WFFzldQYzGPae7TA92SBjRfS6p7lXgQaLnK7ulabj2QFYTDdDRtReRs10/jOrYXgH7mxqwzbzGBq1CzMKDtG54eFMR65mPUXCjtCRAAVdB3bFSJAstoMKDBrmxyb60Syys8jp6BAONWRUA431ZHY5gd2BB1Pl6RYg3irZ0FAk3+gRNWBzoADkKMTd+EVsjQJBkZVxHePvNWlXhEn2m+lEAX7RrR0HzNFzEDdGg+ZoqoHFJ0X61uRiSfLviwHOiScaVsvwCC/p6ZvqBoPSJgTRYn8MrZE9hs2QADazNKwAuW03P6VYi9zZ5Z+FWkwswNcWpiCbhApjJuEdO9fL0AxHOgwPLtIg9xZOlWNaHSB3zGT0/EK2RpWKmRSFzIXO8n+ahBxEufy4eNM04AKBp3pYCrZ9MEaeFFuXogUF5D331HelgKtnSi5yuq02tFiRHoR3ogX41INxuoqR6atIjvzhR78MOlSNey6rS61I1qR3DcR7QCcqsNpRBGNQdKkDKrRq0MxRjL8KrT179lmiI/CAy09wymTRBGNKuZ9GKIBoppVk6VZOlWDVg1YOtWOdWBrVgVYFFSO9Bkd+RNER+DBIwoTmPxZANWBUDSrIqxzqwasnTtCk1YNWOdBYz/AETRUioOn4AIc6AA/slkafiIGlWRVgVY51YNWTpQU0E1NWRVgVYNWTQQ50AB/z3//2Q==" /> + <img alt="Welcome" class="welcome" src="data:image/jpg;base64,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" /> <p class="version"> <strong>Rails version:</strong> <%= Rails.version %><br /> diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 5fda160012..76c28ac85e 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -1,8 +1,9 @@ +# frozen_string_literal: true + # Make double-sure the RAILS_ENV is not set to production, # so fixtures aren't loaded into that environment abort("Abort testing: Your Rails environment is running in production mode!") if Rails.env.production? -require "rails/test_unit/minitest_plugin" require "active_support/test_case" require "action_controller" require "action_controller/test_case" @@ -12,21 +13,26 @@ require "rails/generators/test_case" require "active_support/testing/autorun" if defined?(ActiveRecord::Base) - ActiveRecord::Migration.maintain_test_schema! + begin + ActiveRecord::Migration.maintain_test_schema! + rescue ActiveRecord::PendingMigrationError => e + puts e.to_s.strip + exit 1 + end - class ActiveSupport::TestCase - include ActiveRecord::TestFixtures - self.fixture_path = "#{Rails.root}/test/fixtures/" - self.file_fixture_path = fixture_path + "files" + module ActiveSupport + class TestCase + include ActiveRecord::TestFixtures + self.fixture_path = "#{Rails.root}/test/fixtures/" + self.file_fixture_path = fixture_path + "files" + end end ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path - - def create_fixtures(*fixture_set_names, &block) - FixtureSet.create_fixtures(ActiveSupport::TestCase.fixture_path, fixture_set_names, {}, &block) - end end +# :enddoc: + class ActionController::TestCase def before_setup # :nodoc: @routes = Rails.application.routes diff --git a/railties/lib/rails/test_unit/line_filtering.rb b/railties/lib/rails/test_unit/line_filtering.rb index 32ba744701..f8ca77fe4a 100644 --- a/railties/lib/rails/test_unit/line_filtering.rb +++ b/railties/lib/rails/test_unit/line_filtering.rb @@ -1,78 +1,13 @@ -require "method_source" +# frozen_string_literal: true + +require "rails/test_unit/runner" module Rails module LineFiltering # :nodoc: def run(reporter, options = {}) - if options[:patterns] && options[:patterns].any? { |p| p =~ /:\d+/ } - options[:filter] = \ - CompositeFilter.new(self, options[:filter], options[:patterns]) - end + options[:filter] = Rails::TestUnit::Runner.compose_filter(self, options[:filter]) super end end - - class CompositeFilter # :nodoc: - attr_reader :named_filter - - def initialize(runnable, filter, patterns) - @runnable = runnable - @named_filter = derive_named_filter(filter) - @filters = [ @named_filter, *derive_line_filters(patterns) ].compact - end - - # Minitest uses === to find matching filters. - def ===(method) - @filters.any? { |filter| filter === method } - end - - private - def derive_named_filter(filter) - if filter.respond_to?(:named_filter) - filter.named_filter - elsif filter =~ %r%/(.*)/% # Regexp filtering copied from Minitest. - Regexp.new $1 - elsif filter.is_a?(String) - filter - end - end - - def derive_line_filters(patterns) - patterns.flat_map do |file_and_line| - file, *lines = file_and_line.split(":") - - if lines.empty? - Filter.new(@runnable, file, nil) if file - else - lines.map { |line| Filter.new(@runnable, file, line) } - end - end - end - end - - class Filter # :nodoc: - def initialize(runnable, file, line) - @runnable, @file = runnable, File.expand_path(file) - @line = line.to_i if line - end - - def ===(method) - return unless @runnable.method_defined?(method) - - if @line - test_file, test_range = definition_for(@runnable.instance_method(method)) - test_file == @file && test_range.include?(@line) - else - @runnable.instance_method(method).source_location.first == @file - end - end - - private - def definition_for(method) - file, start_line = method.source_location - end_line = method.source.count("\n") + start_line - 1 - - return file, start_line..end_line - end - end end diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb deleted file mode 100644 index 4df3e7f0f2..0000000000 --- a/railties/lib/rails/test_unit/minitest_plugin.rb +++ /dev/null @@ -1,113 +0,0 @@ -require "active_support/core_ext/module/attribute_accessors" -require "rails/test_unit/reporter" -require "rails/test_unit/test_requirer" -require "shellwords" - -module Minitest - class SuppressedSummaryReporter < SummaryReporter - # Disable extra failure output after a run if output is inline. - def aggregated_results - super unless options[:output_inline] - end - end - - def self.plugin_rails_options(opts, options) - executable = ::Rails::TestUnitReporter.executable - opts.separator "" - opts.separator "Usage: #{executable} [options] [files or directories]" - opts.separator "You can run a single test by appending a line number to a filename:" - opts.separator "" - opts.separator " #{executable} test/models/user_test.rb:27" - opts.separator "" - opts.separator "You can run multiple files and directories at the same time:" - opts.separator "" - opts.separator " #{executable} test/controllers test/integration/login_test.rb" - opts.separator "" - opts.separator "By default test failures and errors are reported inline during a run." - opts.separator "" - - opts.separator "Rails options:" - opts.on("-e", "--environment ENV", - "Run tests in the ENV environment") do |env| - options[:environment] = env.strip - end - - opts.on("-b", "--backtrace", - "Show the complete backtrace") do - options[:full_backtrace] = true - end - - opts.on("-d", "--defer-output", - "Output test failures and errors after the test run") do - options[:output_inline] = false - end - - opts.on("-f", "--fail-fast", - "Abort test run on first failure or error") do - options[:fail_fast] = true - end - - opts.on("-c", "--[no-]color", - "Enable color in the output") do |value| - options[:color] = value - end - - opts.on("-w", "--warnings", - "Enable ruby warnings") do - $VERBOSE = true - end - - options[:color] = true - options[:output_inline] = true - options[:patterns] = opts.order! unless run_via[:rake] - end - - def self.rake_run(patterns) # :nodoc: - run_via[:rake] = true - ::Rails::TestRequirer.require_files(patterns) - autorun - end - - module RunRespectingRakeTestopts - def run(args = []) - if run_via[:rake] - args = Shellwords.split(ENV["TESTOPTS"] || "") - end - - super - end - end - - singleton_class.prepend RunRespectingRakeTestopts - - # Owes great inspiration to test runner trailblazers like RSpec, - # minitest-reporters, maxitest and others. - def self.plugin_rails_init(options) - ENV["RAILS_ENV"] = options[:environment] || "test" - - # If run via `ruby` we've been passed the files to run directly, or if run - # via `rake` then they have already been eagerly required. - unless run_via[:ruby] || run_via[:rake] - ::Rails::TestRequirer.require_files(options[:patterns]) - end - - unless options[:full_backtrace] || ENV["BACKTRACE"] - # Plugin can run without Rails loaded, check before filtering. - Minitest.backtrace_filter = ::Rails.backtrace_cleaner if ::Rails.respond_to?(:backtrace_cleaner) - end - - # Replace progress reporter for colors. - reporter.reporters.delete_if { |reporter| reporter.kind_of?(SummaryReporter) || reporter.kind_of?(ProgressReporter) } - reporter << SuppressedSummaryReporter.new(options[:io], options) - reporter << ::Rails::TestUnitReporter.new(options[:io], options) - end - - mattr_accessor(:run_via) { Hash.new } -end - -# Put Rails as the first plugin minitest initializes so other plugins -# can override or replace our default reporter setup. -# Since minitest only loads plugins if its extensions are empty we have -# to call `load_plugins` first. -Minitest.load_plugins -Minitest.extensions.unshift "rails" diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb index 746120e6a1..42b6daa3d1 100644 --- a/railties/lib/rails/test_unit/railtie.rb +++ b/railties/lib/rails/test_unit/railtie.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + require "rails/test_unit/line_filtering" if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any? - ENV["RAILS_ENV"] ||= "test" + ENV["RAILS_ENV"] ||= Rake.application.options.show_tasks ? "development" : "test" end module Rails @@ -11,6 +13,7 @@ module Rails fixture_replacement: nil c.integration_tool :test_unit + c.system_tests :test_unit end initializer "test_unit.line_filtering" do diff --git a/railties/lib/rails/test_unit/reporter.rb b/railties/lib/rails/test_unit/reporter.rb index fe11664d5e..7d3164f1eb 100644 --- a/railties/lib/rails/test_unit/reporter.rb +++ b/railties/lib/rails/test_unit/reporter.rb @@ -1,10 +1,11 @@ +# frozen_string_literal: true + require "active_support/core_ext/class/attribute" require "minitest" module Rails class TestUnitReporter < Minitest::StatisticsReporter - class_attribute :executable - self.executable = "bin/rails test" + class_attribute :executable, default: "bin/rails test" def record(result) super @@ -72,7 +73,12 @@ module Rails end def app_root - @app_root ||= defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root + @app_root ||= + if defined?(ENGINE_ROOT) + ENGINE_ROOT + elsif Rails.respond_to?(:root) + Rails.root + end end def colored_output? diff --git a/railties/lib/rails/test_unit/runner.rb b/railties/lib/rails/test_unit/runner.rb new file mode 100644 index 0000000000..de5744c662 --- /dev/null +++ b/railties/lib/rails/test_unit/runner.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +require "shellwords" +require "method_source" +require "rake/file_list" +require "active_support/core_ext/module/attribute_accessors" + +module Rails + module TestUnit + class Runner + mattr_reader :filters, default: [] + + class << self + def attach_before_load_options(opts) + opts.on("--warnings", "-w", "Run with Ruby warnings enabled") {} + opts.on("-e", "--environment ENV", "Run tests in the ENV environment") {} + end + + def parse_options(argv) + # Perform manual parsing and cleanup since option parser raises on unknown options. + env_index = argv.index("--environment") || argv.index("-e") + if env_index + argv.delete_at(env_index) + environment = argv.delete_at(env_index).strip + end + ENV["RAILS_ENV"] = environment || "test" + + w_index = argv.index("--warnings") || argv.index("-w") + $VERBOSE = argv.delete_at(w_index) if w_index + end + + def rake_run(argv = []) + ARGV.replace Shellwords.split(ENV["TESTOPTS"] || "") + + run(argv) + end + + def run(argv = []) + load_tests(argv) + + require "active_support/testing/autorun" + end + + def load_tests(argv) + patterns = extract_filters(argv) + + tests = Rake::FileList[patterns.any? ? patterns : "test/**/*_test.rb"] + tests.exclude("test/system/**/*") if patterns.empty? + + tests.to_a.each { |path| require File.expand_path(path) } + end + + def compose_filter(runnable, filter) + if filters.any? { |_, lines| lines.any? } + CompositeFilter.new(runnable, filter, filters) + else + filter + end + end + + private + def extract_filters(argv) + # Extract absolute and relative paths but skip -n /.*/ regexp filters. + argv.select { |arg| arg =~ %r%^/?\w+/% && !arg.end_with?("/") }.map do |path| + case + when path =~ /(:\d+)+$/ + file, *lines = path.split(":") + filters << [ file, lines ] + file + when Dir.exist?(path) + "#{path}/**/*_test.rb" + else + filters << [ path, [] ] + path + end + end + end + end + end + + class CompositeFilter # :nodoc: + attr_reader :named_filter + + def initialize(runnable, filter, patterns) + @runnable = runnable + @named_filter = derive_named_filter(filter) + @filters = [ @named_filter, *derive_line_filters(patterns) ].compact + end + + # Minitest uses === to find matching filters. + def ===(method) + @filters.any? { |filter| filter === method } + end + + private + def derive_named_filter(filter) + if filter.respond_to?(:named_filter) + filter.named_filter + elsif filter =~ %r%/(.*)/% # Regexp filtering copied from Minitest. + Regexp.new $1 + elsif filter.is_a?(String) + filter + end + end + + def derive_line_filters(patterns) + patterns.flat_map do |file, lines| + if lines.empty? + Filter.new(@runnable, file, nil) if file + else + lines.map { |line| Filter.new(@runnable, file, line) } + end + end + end + end + + class Filter # :nodoc: + def initialize(runnable, file, line) + @runnable, @file = runnable, File.expand_path(file) + @line = line.to_i if line + end + + def ===(method) + return unless @runnable.method_defined?(method) + + if @line + test_file, test_range = definition_for(@runnable.instance_method(method)) + test_file == @file && test_range.include?(@line) + else + @runnable.instance_method(method).source_location.first == @file + end + end + + private + def definition_for(method) + file, start_line = method.source_location + end_line = method.source.count("\n") + start_line - 1 + + return file, start_line..end_line + end + end + end +end diff --git a/railties/lib/rails/test_unit/test_requirer.rb b/railties/lib/rails/test_unit/test_requirer.rb deleted file mode 100644 index fe35934abc..0000000000 --- a/railties/lib/rails/test_unit/test_requirer.rb +++ /dev/null @@ -1,28 +0,0 @@ -require "active_support/core_ext/object/blank" -require "rake/file_list" - -module Rails - class TestRequirer # :nodoc: - class << self - def require_files(patterns) - patterns = expand_patterns(patterns) - - Rake::FileList[patterns.compact.presence || "test/**/*_test.rb"].to_a.each do |file| - require File.expand_path(file) - end - end - - private - def expand_patterns(patterns) - patterns.map do |arg| - arg = arg.gsub(/(:\d+)+?$/, "") - if Dir.exist?(arg) - "#{arg}/**/*_test.rb" - else - arg - end - end - end - end - end -end diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index 4c157c1262..32ac27a135 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -1,18 +1,20 @@ +# frozen_string_literal: true + gem "minitest" require "minitest" -require "rails/test_unit/minitest_plugin" +require "rails/test_unit/runner" task default: :test -desc "Runs all tests in test folder" +desc "Runs all tests in test folder except system ones" task :test do $: << "test" - pattern = if ENV.key?("TEST") - ENV["TEST"] + + if ENV.key?("TEST") + Rails::TestUnit::Runner.rake_run([ENV["TEST"]]) else - "test" + Rails::TestUnit::Runner.rake_run end - Minitest.rake_run([pattern]) end namespace :test do @@ -29,22 +31,28 @@ namespace :test do ["models", "helpers", "controllers", "mailers", "integration", "jobs"].each do |name| task name => "test:prepare" do $: << "test" - Minitest.rake_run(["test/#{name}"]) + Rails::TestUnit::Runner.rake_run(["test/#{name}"]) end end task generators: "test:prepare" do $: << "test" - Minitest.rake_run(["test/lib/generators"]) + Rails::TestUnit::Runner.rake_run(["test/lib/generators"]) end task units: "test:prepare" do $: << "test" - Minitest.rake_run(["test/models", "test/helpers", "test/unit"]) + Rails::TestUnit::Runner.rake_run(["test/models", "test/helpers", "test/unit"]) end task functionals: "test:prepare" do $: << "test" - Minitest.rake_run(["test/controllers", "test/mailers", "test/functional"]) + Rails::TestUnit::Runner.rake_run(["test/controllers", "test/mailers", "test/functional"]) + end + + desc "Run system tests only" + task system: "test:prepare" do + $: << "test" + Rails::TestUnit::Runner.rake_run(["test/system"]) end end diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb index 3d8e8291d1..ba6763a572 100644 --- a/railties/lib/rails/version.rb +++ b/railties/lib/rails/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "gem_version" module Rails diff --git a/railties/lib/rails/welcome_controller.rb b/railties/lib/rails/welcome_controller.rb index b757dc72ef..5b84b57679 100644 --- a/railties/lib/rails/welcome_controller.rb +++ b/railties/lib/rails/welcome_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/application_controller" class Rails::WelcomeController < Rails::ApplicationController # :nodoc: diff --git a/railties/railties.gemspec b/railties/railties.gemspec index 76de2b4639..4c665cd546 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -1,4 +1,6 @@ -version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip +# frozen_string_literal: true + +version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY @@ -23,6 +25,11 @@ Gem::Specification.new do |s| s.rdoc_options << "--exclude" << "." + s.metadata = { + "source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/railties", + "changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/railties/CHANGELOG.md" + } + s.add_dependency "activesupport", version s.add_dependency "actionpack", version diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb index e4b2d0457d..b42f37d6b9 100644 --- a/railties/test/abstract_unit.rb +++ b/railties/test/abstract_unit.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + ENV["RAILS_ENV"] ||= "test" require "stringio" @@ -12,8 +14,7 @@ require "rails/all" module TestApp class Application < Rails::Application - config.root = File.dirname(__FILE__) - secrets.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33" + config.root = __dir__ end end diff --git a/railties/test/app_loader_test.rb b/railties/test/app_loader_test.rb index 85f5502b4d..bb556f1968 100644 --- a/railties/test/app_loader_test.rb +++ b/railties/test/app_loader_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "tmpdir" require "abstract_unit" require "rails/app_loader" diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb index 3e17a1efa5..e56c7b958e 100644 --- a/railties/test/application/asset_debugging_test.rb +++ b/railties/test/application/asset_debugging_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" require "rack/test" @@ -7,10 +9,7 @@ module ApplicationTests include Rack::Test::Methods def setup - # FIXME: shush Sass warning spam, not relevant to testing Railties - Kernel.silence_warnings do - build_app(initializers: true) - end + build_app(initializers: true) app_file "app/assets/javascripts/application.js", "//= require_tree ." app_file "app/assets/javascripts/xmlhr.js", "function f1() { alert(); }" @@ -34,16 +33,10 @@ module ApplicationTests teardown_app end - # FIXME: shush Sass warning spam, not relevant to testing Railties - def get(*) - Kernel.silence_warnings { super } - end - test "assets are concatenated when debug is off and compile is off either if debug_assets param is provided" do # config.assets.debug and config.assets.compile are false for production environment ENV["RAILS_ENV"] = "production" - output = Dir.chdir(app_path) { `bin/rails assets:precompile --trace 2>&1` } - assert $?.success?, output + rails "assets:precompile", "--trace" # Load app env app "production" diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index f38cacd6da..0d3262d6f6 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" require "rack/test" require "active_support/json" @@ -60,10 +62,7 @@ module ApplicationTests add_to_env_config "development", "config.assets.digest = false" - # FIXME: shush Sass warning spam, not relevant to testing Railties - Kernel.silence_warnings do - require "#{app_path}/config/environment" - end + require "#{app_path}/config/environment" get "/assets/demo.js" assert_equal 'a = "/assets/rails.png";', last_response.body.strip @@ -475,9 +474,9 @@ module ApplicationTests class ::PostsController < ActionController::Base; end - get "/posts", {}, "HTTPS" => "off" + get "/posts", {}, { "HTTPS" => "off" } assert_match('src="http://example.com/assets/application.self.js', last_response.body) - get "/posts", {}, "HTTPS" => "on" + get "/posts", {}, { "HTTPS" => "on" } assert_match('src="https://example.com/assets/application.self.js', last_response.body) end diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb index 0fb995900f..54934dbe24 100644 --- a/railties/test/application/bin_setup_test.rb +++ b/railties/test/application/bin_setup_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests @@ -20,7 +22,7 @@ module ApplicationTests end RUBY - list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip } + list_tables = lambda { rails("runner", "p ActiveRecord::Base.connection.tables").strip } File.write("log/test.log", "zomg!") assert_equal "[]", list_tables.call diff --git a/railties/test/application/configuration/custom_test.rb b/railties/test/application/configuration/custom_test.rb index 8360b7bf4b..05b17b4a7a 100644 --- a/railties/test/application/configuration/custom_test.rb +++ b/railties/test/application/configuration/custom_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 14433fbba0..907eb4fa58 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" require "rack/test" require "env_helpers" @@ -38,10 +40,7 @@ module ApplicationTests @app ||= begin ENV["RAILS_ENV"] = env - # FIXME: shush Sass warning spam, not relevant to testing Railties - Kernel.silence_warnings do - require "#{app_path}/config/environment" - end + require "#{app_path}/config/environment" Rails.application ensure @@ -176,13 +175,11 @@ module ApplicationTests test "Rails.application responds to all instance methods" do app "development" - assert_respond_to Rails.application, :routes_reloader assert_equal Rails.application.routes_reloader, AppTemplate::Application.routes_reloader end test "Rails::Application responds to paths" do app "development" - assert_respond_to AppTemplate::Application, :paths assert_equal ["#{app_path}/app/views"], AppTemplate::Application.paths["app/views"].expanded end @@ -240,6 +237,66 @@ module ApplicationTests assert_instance_of Pathname, Rails.public_path end + test "does not eager load controller actions in development" do + app_file "app/controllers/posts_controller.rb", <<-RUBY + class PostsController < ActionController::Base + def index;end + def show;end + end + RUBY + + app "development" + + assert_nil PostsController.instance_variable_get(:@action_methods) + end + + test "eager loads controller actions in production" do + app_file "app/controllers/posts_controller.rb", <<-RUBY + class PostsController < ActionController::Base + def index;end + def show;end + end + RUBY + + add_to_config <<-RUBY + config.eager_load = true + config.cache_classes = true + RUBY + + app "production" + + assert_equal %w(index show).to_set, PostsController.instance_variable_get(:@action_methods) + end + + test "does not eager load mailer actions in development" do + app_file "app/mailers/posts_mailer.rb", <<-RUBY + class PostsMailer < ActionMailer::Base + def noop_email;end + end + RUBY + + app "development" + + assert_nil PostsMailer.instance_variable_get(:@action_methods) + end + + test "eager loads mailer actions in production" do + app_file "app/mailers/posts_mailer.rb", <<-RUBY + class PostsMailer < ActionMailer::Base + def noop_email;end + end + RUBY + + add_to_config <<-RUBY + config.eager_load = true + config.cache_classes = true + RUBY + + app "production" + + assert_equal %w(noop_email).to_set, PostsMailer.instance_variable_get(:@action_methods) + end + test "initialize an eager loaded, cache classes app" do add_to_config <<-RUBY config.eager_load = true @@ -257,6 +314,7 @@ module ApplicationTests end test "the application can be eager loaded even when there are no frameworks" do + FileUtils.rm_rf("#{app_path}/app/jobs/application_job.rb") FileUtils.rm_rf("#{app_path}/app/models/application_record.rb") FileUtils.rm_rf("#{app_path}/app/mailers/application_mailer.rb") FileUtils.rm_rf("#{app_path}/config/environments") @@ -418,47 +476,61 @@ module ApplicationTests test "application message verifier can be used when the key_generator is ActiveSupport::LegacyKeyGenerator" do app_file "config/initializers/secret_token.rb", <<-RUBY + Rails.application.credentials.secret_key_base = nil Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" RUBY - app_file "config/secrets.yml", <<-YAML - development: - secret_key_base: - YAML - app "development" + app "production" - assert_equal app.env_config["action_dispatch.key_generator"], Rails.application.key_generator - assert_equal app.env_config["action_dispatch.key_generator"].class, ActiveSupport::LegacyKeyGenerator + assert_kind_of ActiveSupport::LegacyKeyGenerator, Rails.application.key_generator message = app.message_verifier(:sensitive_value).generate("some_value") assert_equal "some_value", Rails.application.message_verifier(:sensitive_value).verify(message) end - test "warns when secrets.secret_key_base is blank and config.secret_token is set" do + test "config.secret_token is deprecated" do app_file "config/initializers/secret_token.rb", <<-RUBY Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" RUBY - app_file "config/secrets.yml", <<-YAML - development: - secret_key_base: - YAML - app "development" + app "production" - assert_deprecated(/You didn't set `secret_key_base`./) do - app.env_config + assert_deprecated(/secret_token/) do + app.secrets end end - test "raise when secrets.secret_key_base is not a type of string" do + test "secrets.secret_token is deprecated" do app_file "config/secrets.yml", <<-YAML - development: - secret_key_base: 123 + production: + secret_token: "b3c631c314c0bbca50c1b2843150fe33" YAML - app "development" + app "production" + + assert_deprecated(/secret_token/) do + app.secrets + end + end + + + test "raises when secret_key_base is blank" do + app_file "config/initializers/secret_token.rb", <<-RUBY + Rails.application.credentials.secret_key_base = nil + RUBY + + error = assert_raise(ArgumentError) do + app "production" + end + assert_match(/Missing `secret_key_base`./, error.message) + end + + test "raise when secret_key_base is not a type of string" do + add_to_config <<-RUBY + Rails.application.credentials.secret_key_base = 123 + RUBY assert_raise(ArgumentError) do - app.key_generator + app "production" end end @@ -478,7 +550,7 @@ module ApplicationTests test "application verifier can build different verifiers" do make_basic_app do |application| - application.secrets.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33" + application.credentials.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33" application.config.session_store :disabled end @@ -596,37 +668,15 @@ module ApplicationTests test "uses ActiveSupport::LegacyKeyGenerator as app.key_generator when secrets.secret_key_base is blank" do app_file "config/initializers/secret_token.rb", <<-RUBY + Rails.application.credentials.secret_key_base = nil Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" RUBY - app_file "config/secrets.yml", <<-YAML - development: - secret_key_base: - YAML - app "development" + app "production" assert_equal "b3c631c314c0bbca50c1b2843150fe33", app.config.secret_token - assert_nil app.secrets.secret_key_base - assert_equal app.key_generator.class, ActiveSupport::LegacyKeyGenerator - end - - test "uses ActiveSupport::LegacyKeyGenerator with config.secret_token as app.key_generator when secrets.secret_key_base is blank" do - app_file "config/initializers/secret_token.rb", <<-RUBY - Rails.application.config.secret_token = "" - RUBY - app_file "config/secrets.yml", <<-YAML - development: - secret_key_base: - YAML - - app "development" - - assert_equal "", app.config.secret_token - assert_nil app.secrets.secret_key_base - e = assert_raise ArgumentError do - app.key_generator - end - assert_match(/\AA secret is required/, e.message) + assert_nil app.credentials.secret_key_base + assert_kind_of ActiveSupport::LegacyKeyGenerator, app.key_generator end test "that nested keys are symbolized the same as parents for hashes more than one level deep" do @@ -643,6 +693,28 @@ module ApplicationTests assert_equal "697361616320736c6f616e2028656c6f7265737429", app.secrets.smtp_settings[:password] end + test "require_master_key aborts app boot when missing key" do + skip "can't run without fork" unless Process.respond_to?(:fork) + + remove_file "config/master.key" + add_to_config "config.require_master_key = true" + + error = capture(:stderr) do + Process.wait(Process.fork { app "development" }) + end + + assert_equal 1, $?.exitstatus + assert_match(/Missing.*RAILS_MASTER_KEY/, error) + end + + test "credentials does not raise error when require_master_key is false and master key does not exist" do + remove_file "config/master.key" + add_to_config "config.require_master_key = false" + app "development" + + assert_not app.credentials.secret_key_base + end + test "protect from forgery is the default in a new app" do make_basic_app @@ -693,6 +765,128 @@ module ApplicationTests assert_match(/label/, last_response.body) end + test "form_with can be configured with form_with_generates_ids" do + app_file "config/initializers/form_builder.rb", <<-RUBY + Rails.configuration.action_view.form_with_generates_ids = false + RUBY + + app_file "app/models/post.rb", <<-RUBY + class Post + include ActiveModel::Model + attr_accessor :name + end + RUBY + + app_file "app/controllers/posts_controller.rb", <<-RUBY + class PostsController < ApplicationController + def index + render inline: "<%= begin; form_with(model: Post.new) {|f| f.text_field(:name)}; rescue => e; e.to_s; end %>" + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + RUBY + + app "development" + + get "/posts" + + assert_no_match(/id=('|")post_name('|")/, last_response.body) + end + + test "form_with outputs ids by default" do + app_file "app/models/post.rb", <<-RUBY + class Post + include ActiveModel::Model + attr_accessor :name + end + RUBY + + app_file "app/controllers/posts_controller.rb", <<-RUBY + class PostsController < ApplicationController + def index + render inline: "<%= begin; form_with(model: Post.new) {|f| f.text_field(:name)}; rescue => e; e.to_s; end %>" + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + RUBY + + app "development" + + get "/posts" + + assert_match(/id=('|")post_name('|")/, last_response.body) + end + + test "form_with can be configured with form_with_generates_remote_forms" do + app_file "config/initializers/form_builder.rb", <<-RUBY + Rails.configuration.action_view.form_with_generates_remote_forms = false + RUBY + + app_file "app/models/post.rb", <<-RUBY + class Post + include ActiveModel::Model + attr_accessor :name + end + RUBY + + app_file "app/controllers/posts_controller.rb", <<-RUBY + class PostsController < ApplicationController + def index + render inline: "<%= begin; form_with(model: Post.new) {|f| f.text_field(:name)}; rescue => e; e.to_s; end %>" + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + RUBY + + app "development" + + get "/posts" + assert_no_match(/data-remote/, last_response.body) + end + + test "form_with generates remote forms by default" do + app_file "app/models/post.rb", <<-RUBY + class Post + include ActiveModel::Model + attr_accessor :name + end + RUBY + + app_file "app/controllers/posts_controller.rb", <<-RUBY + class PostsController < ApplicationController + def index + render inline: "<%= begin; form_with(model: Post.new) {|f| f.text_field(:name)}; rescue => e; e.to_s; end %>" + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + RUBY + + app "development" + + get "/posts" + assert_match(/data-remote/, last_response.body) + end + test "default method for update can be changed" do app_file "app/models/post.rb", <<-RUBY class Post @@ -1072,6 +1266,9 @@ module ApplicationTests app "development" + force_lazy_load_hooks { ActionController::Base } + force_lazy_load_hooks { ActionController::API } + assert_equal :raise, ActionController::Parameters.action_on_unpermitted_parameters post "/posts", post: { "title" => "zomg" } @@ -1080,6 +1277,10 @@ module ApplicationTests test "config.action_controller.always_permitted_parameters are: controller, action by default" do app "development" + + force_lazy_load_hooks { ActionController::Base } + force_lazy_load_hooks { ActionController::API } + assert_equal %w(controller action), ActionController::Parameters.always_permitted_parameters end @@ -1090,6 +1291,9 @@ module ApplicationTests app "development" + force_lazy_load_hooks { ActionController::Base } + force_lazy_load_hooks { ActionController::API } + assert_equal %w( controller action format ), ActionController::Parameters.always_permitted_parameters end @@ -1112,30 +1316,85 @@ module ApplicationTests app "development" + force_lazy_load_hooks { ActionController::Base } + force_lazy_load_hooks { ActionController::API } + assert_equal :raise, ActionController::Parameters.action_on_unpermitted_parameters post "/posts", post: { "title" => "zomg" }, format: "json" assert_equal 200, last_response.status end - test "config.action_controller.action_on_unpermitted_parameters is :log by default on development" do + test "config.action_controller.action_on_unpermitted_parameters is :log by default in development" do app "development" + force_lazy_load_hooks { ActionController::Base } + force_lazy_load_hooks { ActionController::API } + assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters end - test "config.action_controller.action_on_unpermitted_parameters is :log by default on test" do + test "config.action_controller.action_on_unpermitted_parameters is :log by default in test" do app "test" + force_lazy_load_hooks { ActionController::Base } + force_lazy_load_hooks { ActionController::API } + assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters end - test "config.action_controller.action_on_unpermitted_parameters is false by default on production" do + test "config.action_controller.action_on_unpermitted_parameters is false by default in production" do app "production" + force_lazy_load_hooks { ActionController::Base } + force_lazy_load_hooks { ActionController::API } + assert_equal false, ActionController::Parameters.action_on_unpermitted_parameters end + test "config.action_controller.default_protect_from_forgery is true by default" do + app "development" + + assert_equal true, ActionController::Base.default_protect_from_forgery + assert_includes ActionController::Base.__callbacks[:process_action].map(&:filter), :verify_authenticity_token + end + + test "config.action_controller.permit_all_parameters can be configured in an initializer" do + app_file "config/initializers/permit_all_parameters.rb", <<-RUBY + Rails.application.config.action_controller.permit_all_parameters = true + RUBY + + app "development" + + force_lazy_load_hooks { ActionController::Base } + force_lazy_load_hooks { ActionController::API } + assert_equal true, ActionController::Parameters.permit_all_parameters + end + + test "config.action_controller.always_permitted_parameters can be configured in an initializer" do + app_file "config/initializers/always_permitted_parameters.rb", <<-RUBY + Rails.application.config.action_controller.always_permitted_parameters = [] + RUBY + + app "development" + + force_lazy_load_hooks { ActionController::Base } + force_lazy_load_hooks { ActionController::API } + assert_equal [], ActionController::Parameters.always_permitted_parameters + end + + test "config.action_controller.action_on_unpermitted_parameters can be configured in an initializer" do + app_file "config/initializers/action_on_unpermitted_parameters.rb", <<-RUBY + Rails.application.config.action_controller.action_on_unpermitted_parameters = :raise + RUBY + + app "development" + + force_lazy_load_hooks { ActionController::Base } + force_lazy_load_hooks { ActionController::API } + assert_equal :raise, ActionController::Parameters.action_on_unpermitted_parameters + end + test "config.action_dispatch.ignore_accept_header" do make_basic_app do |application| application.config.action_dispatch.ignore_accept_header = true @@ -1150,17 +1409,16 @@ module ApplicationTests end end - get "/", {}, "HTTP_ACCEPT" => "application/xml" + get "/", {}, { "HTTP_ACCEPT" => "application/xml" } assert_equal "HTML", last_response.body - get "/", { format: :xml }, "HTTP_ACCEPT" => "application/xml" + get "/", { format: :xml }, { "HTTP_ACCEPT" => "application/xml" } assert_equal "XML", last_response.body end test "Rails.application#env_config exists and include some existing parameters" do make_basic_app - assert_respond_to app, :env_config assert_equal app.env_config["action_dispatch.parameter_filter"], app.config.filter_parameters assert_equal app.env_config["action_dispatch.show_exceptions"], app.config.action_dispatch.show_exceptions assert_equal app.env_config["action_dispatch.logger"], Rails.logger @@ -1232,12 +1490,18 @@ module ApplicationTests assert_not ActiveRecord::Base.dump_schema_after_migration end - test "config.active_record.dump_schema_after_migration is true by default on development" do + test "config.active_record.dump_schema_after_migration is true by default in development" do app "development" assert ActiveRecord::Base.dump_schema_after_migration end + test "config.active_record.verbose_query_logs is false by default in development" do + app "development" + + assert_not ActiveRecord::Base.verbose_query_logs + end + test "config.annotations wrapping SourceAnnotationExtractor::Annotation class" do make_basic_app do |application| application.config.annotations.register_extensions("coffee") do |tag| @@ -1340,13 +1604,47 @@ module ApplicationTests test "raises with proper error message if no database configuration found" do FileUtils.rm("#{app_path}/config/database.yml") - app "development" err = assert_raises RuntimeError do + app "development" Rails.application.config.database_configuration end assert_match "config/database", err.message end + test "loads database.yml using shared keys" do + app_file "config/database.yml", <<-YAML + shared: + username: bobby + adapter: sqlite3 + + development: + database: 'dev_db' + YAML + + app "development" + + ar_config = Rails.application.config.database_configuration + assert_equal "sqlite3", ar_config["development"]["adapter"] + assert_equal "bobby", ar_config["development"]["username"] + assert_equal "dev_db", ar_config["development"]["database"] + end + + test "loads database.yml using shared keys for undefined environments" do + app_file "config/database.yml", <<-YAML + shared: + username: bobby + adapter: sqlite3 + database: 'dev_db' + YAML + + app "development" + + ar_config = Rails.application.config.database_configuration + assert_equal "sqlite3", ar_config["development"]["adapter"] + assert_equal "bobby", ar_config["development"]["username"] + assert_equal "dev_db", ar_config["development"]["database"] + end + test "config.action_mailer.show_previews defaults to true in development" do app "development" @@ -1439,6 +1737,52 @@ module ApplicationTests assert_equal({}, Rails.application.config.my_custom_config) end + test "default SQLite3Adapter.represent_boolean_as_integer for 5.1 is false" do + remove_from_config '.*config\.load_defaults.*\n' + add_to_top_of_config <<-RUBY + config.load_defaults 5.1 + RUBY + app_file "app/models/post.rb", <<-RUBY + class Post < ActiveRecord::Base + end + RUBY + + app "development" + force_lazy_load_hooks { Post } + + assert_not ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer + end + + test "default SQLite3Adapter.represent_boolean_as_integer for new installs is true" do + app_file "app/models/post.rb", <<-RUBY + class Post < ActiveRecord::Base + end + RUBY + + app "development" + force_lazy_load_hooks { Post } + + assert ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer + end + + test "represent_boolean_as_integer should be able to set via config.active_record.sqlite3.represent_boolean_as_integer" do + remove_from_config '.*config\.load_defaults.*\n' + + app_file "config/initializers/new_framework_defaults_5_2.rb", <<-RUBY + Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true + RUBY + + app_file "app/models/post.rb", <<-RUBY + class Post < ActiveRecord::Base + end + RUBY + + app "development" + force_lazy_load_hooks { Post } + + assert ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer + end + test "config_for containing ERB tags should evaluate" do app_file "config/custom.yml", <<-RUBY development: @@ -1545,5 +1889,28 @@ module ApplicationTests assert_equal 301, last_response.status assert_equal "https://example.org/", last_response.location end + + test "config.active_support.hash_digest_class is Digest::MD5 by default" do + app "development" + + assert_equal Digest::MD5, ActiveSupport::Digest.hash_digest_class + end + + test "config.active_support.hash_digest_class can be configured" do + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.active_support.hash_digest_class = Digest::SHA1 + end + RUBY + + app "development" + + assert_equal Digest::SHA1, ActiveSupport::Digest.hash_digest_class + end + + private + def force_lazy_load_hooks + yield # Tasty clarifying sugar, homie! We only need to reference a constant to load it. + end end end diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb index 72f340df34..13164f49c2 100644 --- a/railties/test/application/console_test.rb +++ b/railties/test/application/console_test.rb @@ -1,4 +1,7 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" +require "console_helpers" class ConsoleTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation @@ -93,14 +96,11 @@ class ConsoleTest < ActiveSupport::TestCase end end -begin - require "pty" -rescue LoadError -end - class FullStackConsoleTest < ActiveSupport::TestCase + include ConsoleHelpers + def setup - skip "PTY unavailable" unless defined?(PTY) && PTY.respond_to?(:open) + skip "PTY unavailable" unless available_pty? build_app app_file "app/models/post.rb", <<-CODE @@ -116,48 +116,43 @@ class FullStackConsoleTest < ActiveSupport::TestCase teardown_app end - def assert_output(expected, timeout = 1) - timeout = Time.now + timeout - - output = "" - until output.include?(expected) || Time.now > timeout - if IO.select([@master], [], [], 0.1) - output << @master.read(1) - end - end - - assert_includes output, expected, "#{expected.inspect} expected, but got:\n\n#{output}" - end - def write_prompt(command, expected_output = nil) @master.puts command - assert_output command - assert_output expected_output if expected_output - assert_output "> " + assert_output command, @master + assert_output expected_output, @master if expected_output + assert_output "> ", @master end - def spawn_console + def spawn_console(options) Process.spawn( - "#{app_path}/bin/rails console --sandbox", + "#{app_path}/bin/rails console #{options}", in: @slave, out: @slave, err: @slave ) - assert_output "> ", 30 + assert_output "> ", @master, 30 end def test_sandbox - spawn_console + spawn_console("--sandbox") write_prompt "Post.count", "=> 0" write_prompt "Post.create" write_prompt "Post.count", "=> 1" @master.puts "quit" - spawn_console + spawn_console("--sandbox") write_prompt "Post.count", "=> 0" write_prompt "Post.transaction { Post.create; raise }" write_prompt "Post.count", "=> 0" @master.puts "quit" end + + def test_environment_option_and_irb_option + spawn_console("test -- --verbose") + + write_prompt "a = 1", "a = 1" + write_prompt "puts Rails.env", "puts Rails.env\r\ntest" + @master.puts "quit" + end end diff --git a/railties/test/application/content_security_policy_test.rb b/railties/test/application/content_security_policy_test.rb new file mode 100644 index 0000000000..97f2957c33 --- /dev/null +++ b/railties/test/application/content_security_policy_test.rb @@ -0,0 +1,197 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "rack/test" + +module ApplicationTests + class ContentSecurityPolicyTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + end + + def teardown + teardown_app + end + + test "default content security policy is empty" do + controller :pages, <<-RUBY + class PagesController < ApplicationController + def index + render html: "<h1>Welcome to Rails!</h1>" + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + root to: "pages#index" + end + RUBY + + app("development") + + get "/" + assert_equal ";", last_response.headers["Content-Security-Policy"] + end + + test "global content security policy in an initializer" do + controller :pages, <<-RUBY + class PagesController < ApplicationController + def index + render html: "<h1>Welcome to Rails!</h1>" + end + end + RUBY + + app_file "config/initializers/content_security_policy.rb", <<-RUBY + Rails.application.config.content_security_policy do |p| + p.default_src :self, :https + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + root to: "pages#index" + end + RUBY + + app("development") + + get "/" + assert_policy "default-src 'self' https:;" + end + + test "global report only content security policy in an initializer" do + controller :pages, <<-RUBY + class PagesController < ApplicationController + def index + render html: "<h1>Welcome to Rails!</h1>" + end + end + RUBY + + app_file "config/initializers/content_security_policy.rb", <<-RUBY + Rails.application.config.content_security_policy do |p| + p.default_src :self, :https + end + + Rails.application.config.content_security_policy_report_only = true + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + root to: "pages#index" + end + RUBY + + app("development") + + get "/" + assert_policy "default-src 'self' https:;", report_only: true + end + + test "override content security policy in a controller" do + controller :pages, <<-RUBY + class PagesController < ApplicationController + content_security_policy do |p| + p.default_src "https://example.com" + end + + def index + render html: "<h1>Welcome to Rails!</h1>" + end + end + RUBY + + app_file "config/initializers/content_security_policy.rb", <<-RUBY + Rails.application.config.content_security_policy do |p| + p.default_src :self, :https + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + root to: "pages#index" + end + RUBY + + app("development") + + get "/" + assert_policy "default-src https://example.com;" + end + + test "override content security policy to report only in a controller" do + controller :pages, <<-RUBY + class PagesController < ApplicationController + content_security_policy_report_only + + def index + render html: "<h1>Welcome to Rails!</h1>" + end + end + RUBY + + app_file "config/initializers/content_security_policy.rb", <<-RUBY + Rails.application.config.content_security_policy do |p| + p.default_src :self, :https + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + root to: "pages#index" + end + RUBY + + app("development") + + get "/" + assert_policy "default-src 'self' https:;", report_only: true + end + + test "global content security policy added to rack app" do + app_file "config/initializers/content_security_policy.rb", <<-RUBY + Rails.application.config.content_security_policy do |p| + p.default_src :self, :https + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + + app = ->(env) { + [200, { "Content-Type" => "text/html" }, ["<p>Hello, World!</p>"]] + } + + root to: app + end + RUBY + + app("development") + + get "/" + assert_policy "default-src 'self' https:;" + end + + private + + def assert_policy(expected, report_only: false) + assert_equal 200, last_response.status + + if report_only + expected_header = "Content-Security-Policy-Report-Only" + unexpected_header = "Content-Security-Policy" + else + expected_header = "Content-Security-Policy" + unexpected_header = "Content-Security-Policy-Report-Only" + end + + assert_nil last_response.headers[unexpected_header] + assert_equal expected, last_response.headers[expected_header] + end + end +end diff --git a/railties/test/application/current_attributes_integration_test.rb b/railties/test/application/current_attributes_integration_test.rb new file mode 100644 index 0000000000..146e96facc --- /dev/null +++ b/railties/test/application/current_attributes_integration_test.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "rack/test" + +class CurrentAttributesIntegrationTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + setup do + build_app + + app_file "app/models/current.rb", <<-RUBY + class Current < ActiveSupport::CurrentAttributes + attribute :customer + + resets { Time.zone = "UTC" } + + def customer=(customer) + super + Time.zone = customer.try(:time_zone) + end + end + RUBY + + app_file "app/models/customer.rb", <<-RUBY + class Customer < Struct.new(:name) + def time_zone + "Copenhagen" + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get "/customers/:action", controller: :customers + end + RUBY + + app_file "app/controllers/customers_controller.rb", <<-RUBY + class CustomersController < ApplicationController + layout false + + def set_current_customer + Current.customer = Customer.new("david") + render :index + end + + def set_no_customer + render :index + end + end + RUBY + + app_file "app/views/customers/index.html.erb", <<-RUBY + <%= Current.customer.try(:name) || 'noone' %>,<%= Time.zone.name %> + RUBY + + require "#{app_path}/config/environment" + end + + teardown :teardown_app + + test "current customer is assigned and cleared" do + get "/customers/set_current_customer" + assert_equal 200, last_response.status + assert_match(/david,Copenhagen/, last_response.body) + + get "/customers/set_no_customer" + assert_equal 200, last_response.status + assert_match(/noone,UTC/, last_response.body) + end + + test "resets after execution" do + assert_nil Current.customer + assert_equal "UTC", Time.zone.name + + Rails.application.executor.wrap do + Current.customer = Customer.new("david") + + assert_equal "david", Current.customer.name + assert_equal "Copenhagen", Time.zone.name + end + + assert_nil Current.customer + assert_equal "UTC", Time.zone.name + end +end diff --git a/railties/test/application/dbconsole_test.rb b/railties/test/application/dbconsole_test.rb new file mode 100644 index 0000000000..8eb293c179 --- /dev/null +++ b/railties/test/application/dbconsole_test.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "console_helpers" + +module ApplicationTests + class DBConsoleTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include ConsoleHelpers + + def setup + skip "PTY unavailable" unless available_pty? + + build_app + end + + def teardown + teardown_app + end + + def test_use_value_defined_in_environment_file_in_database_yml + app_file "config/database.yml", <<-YAML + development: + database: <%= Rails.application.config.database %> + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + YAML + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.database = "db/development.sqlite3" + end + RUBY + + master, slave = PTY.open + spawn_dbconsole(slave) + assert_output("sqlite>", master) + ensure + master.puts ".exit" + end + + def test_respect_environment_option + app_file "config/database.yml", <<-YAML + default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + + development: + <<: *default + database: db/development.sqlite3 + + production: + <<: *default + database: db/production.sqlite3 + YAML + + master, slave = PTY.open + spawn_dbconsole(slave, "-e production") + assert_output("sqlite>", master) + + master.puts "pragma database_list;" + assert_output("production.sqlite3", master) + ensure + master.puts ".exit" + end + + private + def spawn_dbconsole(fd, options = nil) + Process.spawn("#{app_path}/bin/rails dbconsole #{options}", in: fd, out: fd, err: fd) + end + end +end diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index d2ce14f594..e5e557d204 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests @@ -29,7 +31,7 @@ module ApplicationTests end test "allow running plugin new generator inside Rails app directory" do - FileUtils.cd(rails_root) { `ruby bin/rails plugin new vendor/plugins/bukkits` } + rails "plugin", "new", "vendor/plugins/bukkits" assert File.exist?(File.join(rails_root, "vendor/plugins/bukkits/test/dummy/config/application.rb")) end @@ -165,13 +167,14 @@ module ApplicationTests config.api_only = true RUBY - FileUtils.cd(rails_root) { `bin/rails generate mailer notifier foo` } + rails "generate", "mailer", "notifier", "foo" assert File.exist?(File.join(rails_root, "app/views/notifier_mailer/foo.text.erb")) assert File.exist?(File.join(rails_root, "app/views/notifier_mailer/foo.html.erb")) end test "ARGV is mutated as expected" do require "#{app_path}/config/environment" + require "rails/command" Rails::Command.const_set("APP_PATH", "rails/all") FileUtils.cd(rails_root) do @@ -184,5 +187,16 @@ module ApplicationTests Rails::Command.send(:remove_const, "APP_PATH") end + + test "help does not show hidden namespaces and hidden commands" do + FileUtils.cd(rails_root) do + output = rails("generate", "--help") + assert_no_match "active_record:migration", output + assert_no_match "credentials", output + + output = rails("destroy", "--help") + assert_no_match "active_record:migration", output + end + end end end diff --git a/railties/test/application/help_test.rb b/railties/test/application/help_test.rb new file mode 100644 index 0000000000..f728fc3b85 --- /dev/null +++ b/railties/test/application/help_test.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" + +class HelpTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test "command works" do + output = rails("help") + assert_match "The most common rails commands are", output + end + + test "short-cut alias works" do + output = rails("-h") + assert_match "The most common rails commands are", output + end +end diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 90927159dd..d2b77bd015 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests @@ -209,24 +211,20 @@ module ApplicationTests test "database middleware doesn't initialize when activerecord is not in frameworks" do use_frameworks [] require "#{app_path}/config/environment" - assert_nil defined?(ActiveRecord::Base) + assert !defined?(ActiveRecord::Base) || ActiveRecord.autoload?(:Base) end test "use schema cache dump" do - Dir.chdir(app_path) do - `rails generate model post title:string; - bin/rails db:migrate db:schema:cache:dump` - end + rails %w(generate model post title:string) + rails %w(db:migrate db:schema:cache:dump) require "#{app_path}/config/environment" ActiveRecord::Base.connection.drop_table("posts") # force drop posts table for test. assert ActiveRecord::Base.connection.schema_cache.data_sources("posts") end test "expire schema cache dump" do - Dir.chdir(app_path) do - `rails generate model post title:string; - bin/rails db:migrate db:schema:cache:dump db:rollback` - end + rails %w(generate model post title:string) + rails %w(db:migrate db:schema:cache:dump db:rollback) require "#{app_path}/config/environment" assert !ActiveRecord::Base.connection.schema_cache.data_sources("posts") end @@ -262,5 +260,13 @@ module ApplicationTests Rails.env = orig_rails_env if orig_rails_env end end + + test "connections checked out during initialization are returned to the pool" do + app_file "config/initializers/active_record.rb", <<-RUBY + ActiveRecord::Base.connection + RUBY + require "#{app_path}/config/environment" + assert !ActiveRecord::Base.connection_pool.active_connection? + end end end diff --git a/railties/test/application/initializers/hooks_test.rb b/railties/test/application/initializers/hooks_test.rb index 36926c50ff..1e130c2f9e 100644 --- a/railties/test/application/initializers/hooks_test.rb +++ b/railties/test/application/initializers/hooks_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb index 206e42703b..8058052771 100644 --- a/railties/test/application/initializers/i18n_test.rb +++ b/railties/test/application/initializers/i18n_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests @@ -245,7 +247,7 @@ fr: end test "[shortcut] config.i18n.fallbacks = [{ :ca => :'es-ES' }] initializes fallbacks with a mapping ca => es-ES" do - I18n::Railtie.config.i18n.fallbacks.map = { ca: :'es-ES' } + I18n::Railtie.config.i18n.fallbacks = [{ ca: :'es-ES' }] load_app assert_fallbacks ca: [:ca, :"es-ES", :es, :en] end diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb index dbefb22837..78cd4776d6 100644 --- a/railties/test/application/initializers/load_path_test.rb +++ b/railties/test/application/initializers/load_path_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests diff --git a/railties/test/application/initializers/notifications_test.rb b/railties/test/application/initializers/notifications_test.rb index b847ac6b36..c65c955734 100644 --- a/railties/test/application/initializers/notifications_test.rb +++ b/railties/test/application/initializers/notifications_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests @@ -30,6 +32,7 @@ module ApplicationTests logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new ActiveRecord::Base.logger = logger + ActiveRecord::Base.verbose_query_logs = false # Mimic Active Record notifications instrument "sql.active_record", name: "SQL", sql: "SHOW tables" diff --git a/railties/test/application/integration_test_case_test.rb b/railties/test/application/integration_test_case_test.rb index 1118e5037a..c08761092b 100644 --- a/railties/test/application/integration_test_case_test.rb +++ b/railties/test/application/integration_test_case_test.rb @@ -1,8 +1,11 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" +require "env_helpers" module ApplicationTests class IntegrationTestCaseTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation + include ActiveSupport::Testing::Isolation, EnvHelpers setup do build_app @@ -13,7 +16,7 @@ module ApplicationTests end test "resets Action Mailer test deliveries" do - script("generate mailer BaseMailer welcome") + rails "generate", "mailer", "BaseMailer", "welcome" app_file "test/integration/mailer_integration_test.rb", <<-RUBY require 'test_helper' @@ -37,14 +40,14 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path) { `bin/rails test 2>&1` } - assert_equal 0, $?.to_i, output + with_rails_env("test") { rails("db:migrate") } + output = rails("test") assert_match(/0 failures, 0 errors/, output) end end class IntegrationTestDefaultApp < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation + include ActiveSupport::Testing::Isolation, EnvHelpers setup do build_app @@ -65,8 +68,8 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path) { `bin/rails test 2>&1` } - assert_equal 0, $?.to_i, output + with_rails_env("test") { rails("db:migrate") } + output = rails("test") assert_match(/0 failures, 0 errors/, output) end end diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index c75a25bc6f..de1e240fd3 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" class LoadingTest < ActiveSupport::TestCase @@ -115,11 +117,11 @@ class LoadingTest < ActiveSupport::TestCase require "#{rails_root}/config/environment" setup_ar! - assert_equal [ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata], ActiveRecord::Base.descendants + assert_equal [ActiveStorage::Blob, ActiveStorage::Attachment, ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata].collect(&:to_s).sort, ActiveRecord::Base.descendants.collect(&:to_s).sort get "/load" - assert_equal [ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata, Post], ActiveRecord::Base.descendants + assert_equal [ActiveStorage::Blob, ActiveStorage::Attachment, ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata, Post].collect(&:to_s).sort, ActiveRecord::Base.descendants.collect(&:to_s).sort get "/unload" - assert_equal [ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata], ActiveRecord::Base.descendants + assert_equal [ActiveStorage::Blob, ActiveStorage::Attachment, ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata].collect(&:to_s).sort, ActiveRecord::Base.descendants.collect(&:to_s).sort end test "initialize cant be called twice" do @@ -298,7 +300,7 @@ class LoadingTest < ActiveSupport::TestCase end MIGRATION - Dir.chdir(app_path) { `rake db:migrate` } + rails("db:migrate") require "#{rails_root}/config/environment" get "/title" @@ -312,7 +314,7 @@ class LoadingTest < ActiveSupport::TestCase end MIGRATION - Dir.chdir(app_path) { `rake db:migrate` } + rails("db:migrate") get "/body" assert_equal "BODY", last_response.body diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb index c3a360e5d4..4e77cece1b 100644 --- a/railties/test/application/mailer_previews_test.rb +++ b/railties/test/application/mailer_previews_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" require "rack/test" require "base64" @@ -30,7 +32,7 @@ module ApplicationTests test "/rails/mailers is accessible with correct configuration" do add_to_config "config.action_mailer.show_previews = true" app("production") - get "/rails/mailers", {}, "REMOTE_ADDR" => "4.2.42.42" + get "/rails/mailers", {}, { "REMOTE_ADDR" => "4.2.42.42" } assert_equal 200, last_response.status end @@ -480,11 +482,62 @@ module ApplicationTests app("development") - get "/rails/mailers", {}, "SCRIPT_NAME" => "/my_app" + get "/rails/mailers", {}, { "SCRIPT_NAME" => "/my_app" } assert_match '<h3><a href="/my_app/rails/mailers/notifier">Notifier</a></h3>', last_response.body assert_match '<li><a href="/my_app/rails/mailers/notifier/foo">foo</a></li>', last_response.body end + test "mailer preview receives query params" do + mailer "notifier", <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo(name) + @name = name + mail to: "to@example.org" + end + end + RUBY + + html_template "notifier/foo", <<-RUBY + <p>Hello, <%= @name %>!</p> + RUBY + + text_template "notifier/foo", <<-RUBY + Hello, <%= @name %>! + RUBY + + mailer_preview "notifier", <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo(params[:name] || "World") + end + end + RUBY + + app("development") + + get "/rails/mailers/notifier/foo.txt" + assert_equal 200, last_response.status + assert_match '<iframe seamless name="messageBody" src="?part=text%2Fplain">', last_response.body + assert_match '<option selected value="?part=text%2Fplain">', last_response.body + assert_match '<option value="?part=text%2Fhtml">', last_response.body + + get "/rails/mailers/notifier/foo?part=text%2Fplain" + assert_equal 200, last_response.status + assert_match %r[Hello, World!], last_response.body + + get "/rails/mailers/notifier/foo.html?name=Ruby" + assert_equal 200, last_response.status + assert_match '<iframe seamless name="messageBody" src="?name=Ruby&part=text%2Fhtml">', last_response.body + assert_match '<option selected value="?name=Ruby&part=text%2Fhtml">', last_response.body + assert_match '<option value="?name=Ruby&part=text%2Fplain">', last_response.body + + get "/rails/mailers/notifier/foo?name=Ruby&part=text%2Fhtml" + assert_equal 200, last_response.status + assert_match %r[<p>Hello, Ruby!</p>], last_response.body + end + test "plain text mailer preview with attachment" do image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo=" diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb index dc1d816dc5..9822ec563d 100644 --- a/railties/test/application/middleware/cache_test.rb +++ b/railties/test/application/middleware/cache_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests @@ -54,7 +56,7 @@ module ApplicationTests simple_controller expected = "Wed, 30 May 1984 19:43:31 GMT" - get "/expires/keeps_if_modified_since", {}, "HTTP_IF_MODIFIED_SINCE" => expected + get "/expires/keeps_if_modified_since", {}, { "HTTP_IF_MODIFIED_SINCE" => expected } assert_equal 200, last_response.status assert_equal expected, last_response.body, "cache should have kept If-Modified-Since" @@ -117,12 +119,12 @@ module ApplicationTests assert_equal "miss, store", last_response.headers["X-Rack-Cache"] assert_equal "public", last_response.headers["Cache-Control"] - body = last_response.body etag = last_response.headers["ETag"] - get "/expires/expires_etag", {}, "If-None-Match" => etag + get "/expires/expires_etag", {}, { "HTTP_IF_NONE_MATCH" => etag } assert_equal "stale, valid, store", last_response.headers["X-Rack-Cache"] - assert_equal body, last_response.body + assert_equal 304, last_response.status + assert_equal "", last_response.body end def test_cache_works_with_etags_private @@ -137,7 +139,7 @@ module ApplicationTests body = last_response.body etag = last_response.headers["ETag"] - get "/expires/expires_etag", { private: true }, "If-None-Match" => etag + get "/expires/expires_etag", { private: true }, { "HTTP_IF_NONE_MATCH" => etag } assert_equal "miss", last_response.headers["X-Rack-Cache"] assert_not_equal body, last_response.body end @@ -151,12 +153,12 @@ module ApplicationTests assert_equal "miss, store", last_response.headers["X-Rack-Cache"] assert_equal "public", last_response.headers["Cache-Control"] - body = last_response.body last = last_response.headers["Last-Modified"] - get "/expires/expires_last_modified", {}, "If-Modified-Since" => last + get "/expires/expires_last_modified", {}, { "HTTP_IF_MODIFIED_SINCE" => last } assert_equal "stale, valid, store", last_response.headers["X-Rack-Cache"] - assert_equal body, last_response.body + assert_equal 304, last_response.status + assert_equal "", last_response.body end def test_cache_works_with_last_modified_private @@ -171,7 +173,7 @@ module ApplicationTests body = last_response.body last = last_response.headers["Last-Modified"] - get "/expires/expires_last_modified", { private: true }, "If-Modified-Since" => last + get "/expires/expires_last_modified", { private: true }, { "HTTP_IF_MODIFIED_SINCE" => last } assert_equal "miss", last_response.headers["X-Rack-Cache"] assert_not_equal body, last_response.body end diff --git a/railties/test/application/middleware/cookies_test.rb b/railties/test/application/middleware/cookies_test.rb index 1e4b5d086c..ecb4ee3446 100644 --- a/railties/test/application/middleware/cookies_test.rb +++ b/railties/test/application/middleware/cookies_test.rb @@ -1,8 +1,12 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" +require "rack/test" module ApplicationTests class CookiesTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation + include Rack::Test::Methods def new_app File.expand_path("#{app_path}/../new_app") @@ -13,6 +17,10 @@ module ApplicationTests FileUtils.rm_rf("#{app_path}/config/environments") end + def app + Rails.application + end + def teardown teardown_app FileUtils.rm_rf(new_app) if File.directory?(new_app) @@ -42,5 +50,144 @@ module ApplicationTests require "#{app_path}/config/environment" assert_equal false, ActionDispatch::Cookies::CookieJar.always_write_cookie end + + test "signed cookies with SHA512 digest and rotated out SHA256 and SHA1 digests" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + post ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + protect_from_forgery with: :null_session + + def write_raw_cookie_sha1 + cookies[:signed_cookie] = TestVerifiers.sha1.generate("signed cookie") + head :ok + end + + def write_raw_cookie_sha256 + cookies[:signed_cookie] = TestVerifiers.sha256.generate("signed cookie") + head :ok + end + + def read_signed + render plain: cookies.signed[:signed_cookie].inspect + end + + def read_raw_cookie + render plain: cookies[:signed_cookie] + end + end + RUBY + + add_to_config <<-RUBY + sha1_secret = Rails.application.key_generator.generate_key("sha1") + sha256_secret = Rails.application.key_generator.generate_key("sha256") + + ::TestVerifiers = Class.new do + class_attribute :sha1, default: ActiveSupport::MessageVerifier.new(sha1_secret, digest: "SHA1") + class_attribute :sha256, default: ActiveSupport::MessageVerifier.new(sha256_secret, digest: "SHA256") + end + + config.action_dispatch.signed_cookie_digest = "SHA512" + config.action_dispatch.signed_cookie_salt = "sha512 salt" + + config.action_dispatch.cookies_rotations.tap do |cookies| + cookies.rotate :signed, sha1_secret, digest: "SHA1" + cookies.rotate :signed, sha256_secret, digest: "SHA256" + end + RUBY + + require "#{app_path}/config/environment" + + verifier_sha512 = ActiveSupport::MessageVerifier.new(app.key_generator.generate_key("sha512 salt"), digest: :SHA512) + + get "/foo/write_raw_cookie_sha1" + get "/foo/read_signed" + assert_equal "signed cookie".inspect, last_response.body + + get "/foo/read_raw_cookie" + assert_equal "signed cookie", verifier_sha512.verify(last_response.body) + + get "/foo/write_raw_cookie_sha256" + get "/foo/read_signed" + assert_equal "signed cookie".inspect, last_response.body + + get "/foo/read_raw_cookie" + assert_equal "signed cookie", verifier_sha512.verify(last_response.body) + end + + test "encrypted cookies rotating multiple encryption keys" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + post ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + protect_from_forgery with: :null_session + + def write_raw_cookie_one + cookies[:encrypted_cookie] = TestEncryptors.first_gcm.encrypt_and_sign("encrypted cookie") + head :ok + end + + def write_raw_cookie_two + cookies[:encrypted_cookie] = TestEncryptors.second_gcm.encrypt_and_sign("encrypted cookie") + head :ok + end + + def read_encrypted + render plain: cookies.encrypted[:encrypted_cookie].inspect + end + + def read_raw_cookie + render plain: cookies[:encrypted_cookie] + end + end + RUBY + + add_to_config <<-RUBY + first_secret = Rails.application.key_generator.generate_key("first", 32) + second_secret = Rails.application.key_generator.generate_key("second", 32) + + ::TestEncryptors = Class.new do + class_attribute :first_gcm, default: ActiveSupport::MessageEncryptor.new(first_secret, cipher: "aes-256-gcm") + class_attribute :second_gcm, default: ActiveSupport::MessageEncryptor.new(second_secret, cipher: "aes-256-gcm") + end + + config.action_dispatch.use_authenticated_cookie_encryption = true + config.action_dispatch.encrypted_cookie_cipher = "aes-256-gcm" + config.action_dispatch.authenticated_encrypted_cookie_salt = "salt" + + config.action_dispatch.cookies_rotations.tap do |cookies| + cookies.rotate :encrypted, first_secret + cookies.rotate :encrypted, second_secret + end + RUBY + + require "#{app_path}/config/environment" + + encryptor = ActiveSupport::MessageEncryptor.new(app.key_generator.generate_key("salt", 32), cipher: "aes-256-gcm") + + get "/foo/write_raw_cookie_one" + get "/foo/read_encrypted" + assert_equal "encrypted cookie".inspect, last_response.body + + get "/foo/read_raw_cookie" + assert_equal "encrypted cookie", encryptor.decrypt_and_verify(last_response.body) + + get "/foo/write_raw_cookie_sha256" + get "/foo/read_encrypted" + assert_equal "encrypted cookie".inspect, last_response.body + + get "/foo/read_raw_cookie" + assert_equal "encrypted cookie", encryptor.decrypt_and_verify(last_response.body) + end end end diff --git a/railties/test/application/middleware/exceptions_test.rb b/railties/test/application/middleware/exceptions_test.rb index cbb990f13b..2d659ade8d 100644 --- a/railties/test/application/middleware/exceptions_test.rb +++ b/railties/test/application/middleware/exceptions_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" require "rack/test" @@ -100,6 +102,20 @@ module ApplicationTests end end + test "routing to a nonexistent controller when action_dispatch.show_exceptions and consider_all_requests_local are set shows diagnostics" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + resources :articles + end + RUBY + + app.config.action_dispatch.show_exceptions = true + app.config.consider_all_requests_local = true + + get "/articles" + assert_match "<title>Action Controller: Exception caught</title>", last_response.body + end + test "displays diagnostics message when exception raised in template that contains UTF-8" do controller :foo, <<-RUBY class FooController < ActionController::Base diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb index c34d9d6ee7..83cf8a27f7 100644 --- a/railties/test/application/middleware/remote_ip_test.rb +++ b/railties/test/application/middleware/remote_ip_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "ipaddr" require "isolation/abstract_unit" require "active_support/key_generator" diff --git a/railties/test/application/middleware/sendfile_test.rb b/railties/test/application/middleware/sendfile_test.rb index 4938402fdc..9def3a0ce7 100644 --- a/railties/test/application/middleware/sendfile_test.rb +++ b/railties/test/application/middleware/sendfile_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests @@ -13,10 +15,6 @@ module ApplicationTests teardown_app end - def app - @app ||= Rails.application - end - define_method :simple_controller do class ::OmgController < ActionController::Base def index diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb index 959a629ede..a17988235a 100644 --- a/railties/test/application/middleware/session_test.rb +++ b/railties/test/application/middleware/session_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" require "rack/test" @@ -162,6 +164,11 @@ module ApplicationTests end RUBY + add_to_config <<-RUBY + # Enable AEAD cookies + config.action_dispatch.use_authenticated_cookie_encryption = true + RUBY + require "#{app_path}/config/environment" get "/foo/write_session" @@ -171,9 +178,9 @@ module ApplicationTests get "/foo/read_encrypted_cookie" assert_equal "1", last_response.body - secret = app.key_generator.generate_key("encrypted cookie") - sign_secret = app.key_generator.generate_key("signed encrypted cookie") - encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret) + cipher = "aes-256-gcm" + secret = app.key_generator.generate_key("authenticated encrypted cookie") + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) get "/foo/read_raw_cookie" assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"] @@ -209,6 +216,9 @@ module ApplicationTests add_to_config <<-RUBY secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + + # Enable AEAD cookies + config.action_dispatch.use_authenticated_cookie_encryption = true RUBY require "#{app_path}/config/environment" @@ -220,9 +230,9 @@ module ApplicationTests get "/foo/read_encrypted_cookie" assert_equal "1", last_response.body - secret = app.key_generator.generate_key("encrypted cookie") - sign_secret = app.key_generator.generate_key("signed encrypted cookie") - encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret) + cipher = "aes-256-gcm" + secret = app.key_generator.generate_key("authenticated encrypted cookie") + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) get "/foo/read_raw_cookie" assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"] @@ -264,6 +274,9 @@ module ApplicationTests add_to_config <<-RUBY secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + + # Enable AEAD cookies + config.action_dispatch.use_authenticated_cookie_encryption = true RUBY require "#{app_path}/config/environment" @@ -279,14 +292,84 @@ module ApplicationTests get "/foo/read_encrypted_cookie" assert_equal "2", last_response.body - secret = app.key_generator.generate_key("encrypted cookie") - sign_secret = app.key_generator.generate_key("signed encrypted cookie") - encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret) + cipher = "aes-256-gcm" + secret = app.key_generator.generate_key("authenticated encrypted cookie") + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) get "/foo/read_raw_cookie" assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"] end + test "session upgrading from AES-CBC-HMAC encryption to AES-GCM encryption" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + def write_raw_session + # AES-256-CBC with SHA1 HMAC + # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1} + cookies[:_myapp_session] = "TlgrdS85aUpDd1R2cDlPWlR6K0FJeGExckwySjZ2Z0pkR3d2QnRObGxZT25aalJWYWVvbFVLcHF4d0VQVDdSaFF2QjFPbG9MVjJzeWp3YjcyRUlKUUU2ZlR4bXlSNG9ZUkJPRUtld0E3dVU9LS0xNDZXbGpRZ3NjdW43N2haUEZJSUNRPT0=--3639b5ce54c09495cfeaae928cd5634e0c4b2e96" + head :ok + end + + def write_session + session[:foo] = session[:foo] + 1 + head :ok + end + + def read_session + render plain: session[:foo] + end + + def read_encrypted_cookie + render plain: cookies.encrypted[:_myapp_session]['foo'] + end + + def read_raw_cookie + render plain: cookies[:_myapp_session] + end + end + RUBY + + add_to_config <<-RUBY + # Use a static key + Rails.application.credentials.secret_key_base = "known key base" + + # Enable AEAD cookies + config.action_dispatch.use_authenticated_cookie_encryption = true + RUBY + + begin + old_rails_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "production" + + require "#{app_path}/config/environment" + + get "/foo/write_raw_session" + get "/foo/read_session" + assert_equal "1", last_response.body + + get "/foo/write_session" + get "/foo/read_session" + assert_equal "2", last_response.body + + get "/foo/read_encrypted_cookie" + assert_equal "2", last_response.body + + cipher = "aes-256-gcm" + secret = app.key_generator.generate_key("authenticated encrypted cookie") + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) + + get "/foo/read_raw_cookie" + assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"] + ensure + ENV["RAILS_ENV"] = old_rails_env + end + end + test "session upgrading legacy signed cookies to new signed cookies" do app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do @@ -323,26 +406,32 @@ module ApplicationTests add_to_config <<-RUBY secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" - secrets.secret_key_base = nil + Rails.application.credentials.secret_key_base = nil RUBY - require "#{app_path}/config/environment" + begin + old_rails_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "production" - get "/foo/write_raw_session" - get "/foo/read_session" - assert_equal "1", last_response.body + require "#{app_path}/config/environment" - get "/foo/write_session" - get "/foo/read_session" - assert_equal "2", last_response.body + get "/foo/write_raw_session" + get "/foo/read_session" + assert_equal "1", last_response.body - get "/foo/read_signed_cookie" - assert_equal "2", last_response.body + get "/foo/write_session" + get "/foo/read_session" + assert_equal "2", last_response.body - verifier = ActiveSupport::MessageVerifier.new(app.secrets.secret_token) + get "/foo/read_signed_cookie" + assert_equal "2", last_response.body - get "/foo/read_raw_cookie" - assert_equal 2, verifier.verify(last_response.body)["foo"] + verifier = ActiveSupport::MessageVerifier.new(app.secrets.secret_token) + + get "/foo/read_raw_cookie" + assert_equal 2, verifier.verify(last_response.body)["foo"] + ensure + ENV["RAILS_ENV"] = old_rails_env + end end test "calling reset_session on request does not trigger an error for API apps" do diff --git a/railties/test/application/middleware/static_test.rb b/railties/test/application/middleware/static_test.rb index 5cd3e4325e..0977042cfe 100644 --- a/railties/test/application/middleware/static_test.rb +++ b/railties/test/application/middleware/static_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" require "rack/test" diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 0a6e5b52e9..d59384e982 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests @@ -40,6 +42,7 @@ module ApplicationTests "ActionDispatch::Cookies", "ActionDispatch::Session::CookieStore", "ActionDispatch::Flash", + "ActionDispatch::ContentSecurityPolicy::Middleware", "Rack::Head", "Rack::ConditionalGet", "Rack::ETag" @@ -246,7 +249,7 @@ module ApplicationTests test "can't change middleware after it's built" do boot! - assert_raise RuntimeError do + assert_raise frozen_error_class do app.config.middleware.use Rack::Config end end @@ -274,7 +277,7 @@ module ApplicationTests assert_equal "max-age=0, private, must-revalidate", last_response.headers["Cache-Control"] assert_equal etag, last_response.headers["Etag"] - get "/", {}, "HTTP_IF_NONE_MATCH" => etag + get "/", {}, { "HTTP_IF_NONE_MATCH" => etag } assert_equal 304, last_response.status assert_equal "", last_response.body assert_nil last_response.headers["Content-Type"] diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb index 26b810af73..d6c81c1fe2 100644 --- a/railties/test/application/multiple_applications_test.rb +++ b/railties/test/application/multiple_applications_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb index 515205296c..0abc5cc9aa 100644 --- a/railties/test/application/paths_test.rb +++ b/railties/test/application/paths_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests diff --git a/railties/test/application/per_request_digest_cache_test.rb b/railties/test/application/per_request_digest_cache_test.rb index 6c003e9bcc..e9bc91785c 100644 --- a/railties/test/application/per_request_digest_cache_test.rb +++ b/railties/test/application/per_request_digest_cache_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" require "rack/test" require "minitest/mock" @@ -18,6 +20,10 @@ class PerRequestDigestCacheTest < ActiveSupport::TestCase class Customer < Struct.new(:name, :id) extend ActiveModel::Naming include ActiveModel::Conversion + + def cache_key + [ name, id ].join("/") + end end RUBY diff --git a/railties/test/application/rack/logger_test.rb b/railties/test/application/rack/logger_test.rb index e71bcbc536..d949a48366 100644 --- a/railties/test/application/rack/logger_test.rb +++ b/railties/test/application/rack/logger_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" require "active_support/log_subscriber/test_helper" require "rack/test" diff --git a/railties/test/application/rackup_test.rb b/railties/test/application/rackup_test.rb index 2943e9ee5d..383f18a7da 100644 --- a/railties/test/application/rackup_test.rb +++ b/railties/test/application/rackup_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 8bbae64d5e..5b4c42c189 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -1,5 +1,6 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" -require "active_support/core_ext/string/strip" module ApplicationTests module RakeTests @@ -25,13 +26,13 @@ module ApplicationTests FileUtils.rm_rf("#{app_path}/config/database.yml") end - def db_create_and_drop(expected_database) + def db_create_and_drop(expected_database, environment_loaded: true) Dir.chdir(app_path) do - output = `bin/rails db:create` + output = rails("db:create") assert_match(/Created database/, output) assert File.exist?(expected_database) - assert_equal expected_database, ActiveRecord::Base.connection_config[:database] - output = `bin/rails db:drop` + assert_equal expected_database, ActiveRecord::Base.connection_config[:database] if environment_loaded + output = rails("db:drop") assert_match(/Dropped database/, output) assert !File.exist?(expected_database) end @@ -48,20 +49,35 @@ module ApplicationTests db_create_and_drop database_url_db_name end + test "db:create and db:drop respect environment setting" do + app_file "config/database.yml", <<-YAML + development: + database: <%= Rails.application.config.database %> + adapter: sqlite3 + YAML + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.database = "db/development.sqlite3" + end + RUBY + + db_create_and_drop "db/development.sqlite3", environment_loaded: false + end + def with_database_existing Dir.chdir(app_path) do set_database_url - `bin/rails db:create` + rails "db:create" yield - `bin/rails db:drop` + rails "db:drop" end end test "db:create failure because database exists" do with_database_existing do - output = `bin/rails db:create 2>&1` + output = rails("db:create") assert_match(/already exists/, output) - assert_equal 0, $?.exitstatus end end @@ -76,24 +92,35 @@ module ApplicationTests test "db:create failure because bad permissions" do with_bad_permissions do - output = `bin/rails db:create 2>&1` + output = rails("db:create", allow_failure: true) assert_match(/Couldn't create database/, output) assert_equal 1, $?.exitstatus end end - test "db:drop failure because database does not exist" do - Dir.chdir(app_path) do - output = `bin/rails db:drop:_unsafe --trace 2>&1` - assert_match(/does not exist/, output) + test "db:create works when schema cache exists and database does not exist" do + use_postgresql + + begin + rails %w(db:create db:migrate db:schema:cache:dump) + + rails "db:drop" + rails "db:create" assert_equal 0, $?.exitstatus + ensure + rails "db:drop" rescue nil end end + test "db:drop failure because database does not exist" do + output = rails("db:drop:_unsafe", "--trace") + assert_match(/does not exist/, output) + end + test "db:drop failure because bad permissions" do with_database_existing do with_bad_permissions do - output = `bin/rails db:drop 2>&1` + output = rails("db:drop", allow_failure: true) assert_match(/Couldn't drop/, output) assert_equal 1, $?.exitstatus end @@ -101,13 +128,11 @@ module ApplicationTests end def db_migrate_and_status(expected_database) - Dir.chdir(app_path) do - `bin/rails generate model book title:string; - bin/rails db:migrate` - output = `bin/rails db:migrate:status` - assert_match(%r{database:\s+\S*#{Regexp.escape(expected_database)}}, output) - assert_match(/up\s+\d{14}\s+Create books/, output) - end + rails "generate", "model", "book", "title:string" + rails "db:migrate" + output = rails("db:migrate:status") + assert_match(%r{database:\s+\S*#{Regexp.escape(expected_database)}}, output) + assert_match(/up\s+\d{14}\s+Create books/, output) end test "db:migrate and db:migrate:status without database_url" do @@ -123,8 +148,8 @@ module ApplicationTests def db_schema_dump Dir.chdir(app_path) do - `bin/rails generate model book title:string; - bin/rails db:migrate db:schema:dump` + rails "generate", "model", "book", "title:string" + rails "db:migrate", "db:schema:dump" schema_dump = File.read("db/schema.rb") assert_match(/create_table \"books\"/, schema_dump) end @@ -141,8 +166,8 @@ module ApplicationTests def db_fixtures_load(expected_database) Dir.chdir(app_path) do - `bin/rails generate model book title:string; - bin/rails db:migrate db:fixtures:load` + rails "generate", "model", "book", "title:string" + rails "db:migrate", "db:fixtures:load" assert_match expected_database, ActiveRecord::Base.connection_config[:database] require "#{app_path}/app/models/book" assert_equal 2, Book.count @@ -162,24 +187,23 @@ module ApplicationTests test "db:fixtures:load with namespaced fixture" do require "#{app_path}/config/environment" - Dir.chdir(app_path) do - `bin/rails generate model admin::book title:string; - bin/rails db:migrate db:fixtures:load` - require "#{app_path}/app/models/admin/book" - assert_equal 2, Admin::Book.count - end + + rails "generate", "model", "admin::book", "title:string" + rails "db:migrate", "db:fixtures:load" + require "#{app_path}/app/models/admin/book" + assert_equal 2, Admin::Book.count end def db_structure_dump_and_load(expected_database) Dir.chdir(app_path) do - `bin/rails generate model book title:string; - bin/rails db:migrate db:structure:dump` + rails "generate", "model", "book", "title:string" + rails "db:migrate", "db:structure:dump" structure_dump = File.read("db/structure.sql") assert_match(/CREATE TABLE (?:IF NOT EXISTS )?\"books\"/, structure_dump) - `bin/rails environment db:drop db:structure:load` + rails "environment", "db:drop", "db:structure:load" assert_match expected_database, ActiveRecord::Base.connection_config[:database] require "#{app_path}/app/models/book" - #if structure is not loaded correctly, exception would be raised + # if structure is not loaded correctly, exception would be raised assert_equal 0, Book.count end end @@ -195,79 +219,86 @@ module ApplicationTests db_structure_dump_and_load database_url_db_name end + test "db:structure:dump and db:structure:load set ar_internal_metadata" do + require "#{app_path}/config/environment" + db_structure_dump_and_load ActiveRecord::Base.configurations[Rails.env]["database"] + + assert_equal "test", rails("runner", "-e", "test", "puts ActiveRecord::InternalMetadata[:environment]").strip + assert_equal "development", rails("runner", "puts ActiveRecord::InternalMetadata[:environment]").strip + end + test "db:structure:dump does not dump schema information when no migrations are used" do - Dir.chdir(app_path) do - # create table without migrations - `bin/rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'` + # create table without migrations + rails "runner", "ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }" - stderr_output = capture(:stderr) { `bin/rails db:structure:dump` } - assert_empty stderr_output - structure_dump = File.read("db/structure.sql") - assert_match(/CREATE TABLE (?:IF NOT EXISTS )?\"posts\"/, structure_dump) - end + stderr_output = capture(:stderr) { rails("db:structure:dump", stderr: true, allow_failure: true) } + assert_empty stderr_output + structure_dump = File.read("#{app_path}/db/structure.sql") + assert_match(/CREATE TABLE (?:IF NOT EXISTS )?\"posts\"/, structure_dump) end test "db:schema:load and db:structure:load do not purge the existing database" do - Dir.chdir(app_path) do - `bin/rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'` + rails "runner", "ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }" - app_file "db/schema.rb", <<-RUBY - ActiveRecord::Schema.define(version: 20140423102712) do - create_table(:comments) {} - end - RUBY + app_file "db/schema.rb", <<-RUBY + ActiveRecord::Schema.define(version: 20140423102712) do + create_table(:comments) {} + end + RUBY - list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip } + list_tables = lambda { rails("runner", "p ActiveRecord::Base.connection.tables").strip } - assert_equal '["posts"]', list_tables[] - `bin/rails db:schema:load` - assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata"]', list_tables[] + assert_equal '["posts"]', list_tables[] + rails "db:schema:load" + assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata"]', list_tables[] - app_file "db/structure.sql", <<-SQL - CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); - SQL + app_file "db/structure.sql", <<-SQL + CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); + SQL - `bin/rails db:structure:load` - assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata", "users"]', list_tables[] - end + rails "db:structure:load" + assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata", "users"]', list_tables[] end test "db:schema:load with inflections" do - Dir.chdir(app_path) do - app_file "config/initializers/inflection.rb", <<-RUBY - ActiveSupport::Inflector.inflections do |inflect| - inflect.irregular 'goose', 'geese' - end - RUBY - app_file "config/initializers/primary_key_table_name.rb", <<-RUBY - ActiveRecord::Base.primary_key_prefix_type = :table_name - RUBY - app_file "db/schema.rb", <<-RUBY - ActiveRecord::Schema.define(version: 20140423102712) do - create_table("goose".pluralize) do |t| - t.string :name - end + app_file "config/initializers/inflection.rb", <<-RUBY + ActiveSupport::Inflector.inflections do |inflect| + inflect.irregular 'goose', 'geese' + end + RUBY + app_file "config/initializers/primary_key_table_name.rb", <<-RUBY + ActiveRecord::Base.primary_key_prefix_type = :table_name + RUBY + app_file "db/schema.rb", <<-RUBY + ActiveRecord::Schema.define(version: 20140423102712) do + create_table("goose".pluralize) do |t| + t.string :name end - RUBY + end + RUBY - `bin/rails db:schema:load` + rails "db:schema:load" - tables = `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip - assert_match(/"geese"/, tables) + tables = rails("runner", "p ActiveRecord::Base.connection.tables").strip + assert_match(/"geese"/, tables) - columns = `bin/rails runner 'p ActiveRecord::Base.connection.columns("geese").map(&:name)'`.strip - assert_equal columns, '["gooseid", "name"]' - end + columns = rails("runner", "p ActiveRecord::Base.connection.columns('geese').map(&:name)").strip + assert_equal columns, '["gooseid", "name"]' + end + + test "db:schema:load fails if schema.rb doesn't exist yet" do + stderr_output = capture(:stderr) { rails("db:schema:load", stderr: true, allow_failure: true) } + assert_match(/Run `rails db:migrate` to create it/, stderr_output) end def db_test_load_structure Dir.chdir(app_path) do - `bin/rails generate model book title:string; - bin/rails db:migrate db:structure:dump db:test:load_structure` + rails "generate", "model", "book", "title:string" + rails "db:migrate", "db:structure:dump", "db:test:load_structure" ActiveRecord::Base.configurations = Rails.application.config.database_configuration ActiveRecord::Base.establish_connection :test require "#{app_path}/app/models/book" - #if structure is not loaded correctly, exception would be raised + # if structure is not loaded correctly, exception would be raised assert_equal 0, Book.count assert_match ActiveRecord::Base.configurations["test"]["database"], ActiveRecord::Base.connection_config[:database] @@ -298,15 +329,52 @@ module ApplicationTests puts ActiveRecord::Base.connection_config[:database] RUBY - Dir.chdir(app_path) do - database_path = `bin/rails db:setup` - assert_equal "development.sqlite3", File.basename(database_path.strip) - end + database_path = rails("db:setup") + assert_equal "development.sqlite3", File.basename(database_path.strip) ensure ENV["RAILS_ENV"] = @old_rails_env ENV["RACK_ENV"] = @old_rack_env end end + + test "db:setup sets ar_internal_metadata" do + app_file "db/schema.rb", "" + rails "db:setup" + + test_environment = lambda { rails("runner", "-e", "test", "puts ActiveRecord::InternalMetadata[:environment]").strip } + development_environment = lambda { rails("runner", "puts ActiveRecord::InternalMetadata[:environment]").strip } + + assert_equal "test", test_environment.call + assert_equal "development", development_environment.call + + app_file "db/structure.sql", "" + app_file "config/initializers/enable_sql_schema_format.rb", <<-RUBY + Rails.application.config.active_record.schema_format = :sql + RUBY + + rails "db:setup" + + assert_equal "test", test_environment.call + assert_equal "development", development_environment.call + end + + test "db:test:prepare sets test ar_internal_metadata" do + app_file "db/schema.rb", "" + rails "db:test:prepare" + + test_environment = lambda { rails("runner", "-e", "test", "puts ActiveRecord::InternalMetadata[:environment]").strip } + + assert_equal "test", test_environment.call + + app_file "db/structure.sql", "" + app_file "config/initializers/enable_sql_schema_format.rb", <<-RUBY + Rails.application.config.active_record.schema_format = :sql + RUBY + + rails "db:test:prepare" + + assert_equal "test", test_environment.call + end end end end diff --git a/railties/test/application/rake/dev_test.rb b/railties/test/application/rake/dev_test.rb index 4f992d9c8d..66e1ac9d99 100644 --- a/railties/test/application/rake/dev_test.rb +++ b/railties/test/application/rake/dev_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests @@ -15,7 +17,7 @@ module ApplicationTests test "dev:cache creates file and outputs message" do Dir.chdir(app_path) do - output = `rails dev:cache` + output = rails("dev:cache") assert File.exist?("tmp/caching-dev.txt") assert_match(/Development mode is now being cached/, output) end @@ -23,19 +25,23 @@ module ApplicationTests test "dev:cache deletes file and outputs message" do Dir.chdir(app_path) do - `rails dev:cache` # Create caching file. - output = `rails dev:cache` # Delete caching file. + rails "dev:cache" # Create caching file. + output = rails("dev:cache") # Delete caching file. assert_not File.exist?("tmp/caching-dev.txt") assert_match(/Development mode is no longer being cached/, output) end end - test "dev:cache removes server.pid also" do + test "dev:cache touches tmp/restart.txt" do Dir.chdir(app_path) do - FileUtils.mkdir_p("tmp/pids") - FileUtils.touch("tmp/pids/server.pid") - `rails dev:cache` - assert_not File.exist?("tmp/pids/server.pid") + rails "dev:cache" + assert File.exist?("tmp/restart.txt") + + prev_mtime = File.mtime("tmp/restart.txt") + sleep(1) + rails "dev:cache" + curr_mtime = File.mtime("tmp/restart.txt") + assert_not_equal prev_mtime, curr_mtime end end end diff --git a/railties/test/application/rake/framework_test.rb b/railties/test/application/rake/framework_test.rb index 7ac37b7700..644b1924b5 100644 --- a/railties/test/application/rake/framework_test.rb +++ b/railties/test/application/rake/framework_test.rb @@ -1,5 +1,6 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" -require "active_support/core_ext/string/strip" module ApplicationTests module RakeTests diff --git a/railties/test/application/rake/log_test.rb b/railties/test/application/rake/log_test.rb index fdd3c71fe8..678f26db26 100644 --- a/railties/test/application/rake/log_test.rb +++ b/railties/test/application/rake/log_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests @@ -21,7 +23,7 @@ module ApplicationTests File.write("log/test.log", "test") File.write("log/dummy.log", "dummy") - `rails log:clear` + rails "log:clear" assert_equal 0, File.size("log/test.log") assert_equal 0, File.size("log/staging.log") diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb index 76cb302c62..788f9160d6 100644 --- a/railties/test/application/rake/migrations_test.rb +++ b/railties/test/application/rake/migrations_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests @@ -13,185 +15,396 @@ module ApplicationTests end test "running migrations with given scope" do - Dir.chdir(app_path) do - `bin/rails generate model user username:string password:string` + rails "generate", "model", "user", "username:string", "password:string" - app_file "db/migrate/01_a_migration.bukkits.rb", <<-MIGRATION - class AMigration < ActiveRecord::Migration::Current - end - MIGRATION + app_file "db/migrate/01_a_migration.bukkits.rb", <<-MIGRATION + class AMigration < ActiveRecord::Migration::Current + end + MIGRATION - output = `bin/rails db:migrate SCOPE=bukkits` - assert_no_match(/create_table\(:users\)/, output) - assert_no_match(/CreateUsers/, output) - assert_no_match(/add_column\(:users, :email, :string\)/, output) + output = rails("db:migrate", "SCOPE=bukkits") + assert_no_match(/create_table\(:users\)/, output) + assert_no_match(/CreateUsers/, output) + assert_no_match(/add_column\(:users, :email, :string\)/, output) - assert_match(/AMigration: migrated/, output) + assert_match(/AMigration: migrated/, output) - output = `bin/rails db:migrate SCOPE=bukkits VERSION=0` - assert_no_match(/drop_table\(:users\)/, output) - assert_no_match(/CreateUsers/, output) - assert_no_match(/remove_column\(:users, :email\)/, output) + output = rails("db:migrate", "SCOPE=bukkits", "VERSION=0") + assert_no_match(/drop_table\(:users\)/, output) + assert_no_match(/CreateUsers/, output) + assert_no_match(/remove_column\(:users, :email\)/, output) - assert_match(/AMigration: reverted/, output) - end + assert_match(/AMigration: reverted/, output) + end + + test "migrate with specified VERSION in different formats" do + app_file "db/migrate/01_one_migration.rb", <<-MIGRATION + class OneMigration < ActiveRecord::Migration::Current + end + MIGRATION + + app_file "db/migrate/02_two_migration.rb", <<-MIGRATION + class TwoMigration < ActiveRecord::Migration::Current + end + MIGRATION + + app_file "db/migrate/03_three_migration.rb", <<-MIGRATION + class ThreeMigration < ActiveRecord::Migration::Current + end + MIGRATION + + rails "db:migrate" + + output = rails("db:migrate:status") + assert_match(/up\s+001\s+One migration/, output) + assert_match(/up\s+002\s+Two migration/, output) + assert_match(/up\s+003\s+Three migration/, output) + + rails "db:migrate", "VERSION=01_one_migration.rb" + output = rails("db:migrate:status") + assert_match(/up\s+001\s+One migration/, output) + assert_match(/down\s+002\s+Two migration/, output) + assert_match(/down\s+003\s+Three migration/, output) + + rails "db:migrate", "VERSION=3" + output = rails("db:migrate:status") + assert_match(/up\s+001\s+One migration/, output) + assert_match(/up\s+002\s+Two migration/, output) + assert_match(/up\s+003\s+Three migration/, output) + + rails "db:migrate", "VERSION=001" + output = rails("db:migrate:status") + assert_match(/up\s+001\s+One migration/, output) + assert_match(/down\s+002\s+Two migration/, output) + assert_match(/down\s+003\s+Three migration/, output) + end + + test "migration with empty version" do + app_file "db/migrate/01_one_migration.rb", <<-MIGRATION + class OneMigration < ActiveRecord::Migration::Current + end + MIGRATION + + app_file "db/migrate/02_two_migration.rb", <<-MIGRATION + class TwoMigration < ActiveRecord::Migration::Current + end + MIGRATION + + rails("db:migrate", "VERSION=") + + output = rails("db:migrate:status") + assert_match(/up\s+001\s+One migration/, output) + assert_match(/up\s+002\s+Two migration/, output) + + output = rails("db:migrate:redo", "VERSION=", allow_failure: true) + assert_match(/Empty VERSION provided/, output) + + output = rails("db:migrate:up", "VERSION=", allow_failure: true) + assert_match(/VERSION is required/, output) + + output = rails("db:migrate:up", allow_failure: true) + assert_match(/VERSION is required/, output) + + output = rails("db:migrate:down", "VERSION=", allow_failure: true) + assert_match(/VERSION is required - To go down one migration, use db:rollback/, output) + + output = rails("db:migrate:down", allow_failure: true) + assert_match(/VERSION is required - To go down one migration, use db:rollback/, output) + + output = rails("db:migrate:status") + assert_match(/up\s+001\s+One migration/, output) + assert_match(/up\s+002\s+Two migration/, output) + end + + test "migration with 0 version" do + app_file "db/migrate/01_one_migration.rb", <<-MIGRATION + class OneMigration < ActiveRecord::Migration::Current + end + MIGRATION + + app_file "db/migrate/02_two_migration.rb", <<-MIGRATION + class TwoMigration < ActiveRecord::Migration::Current + end + MIGRATION + + rails "db:migrate" + + output = rails("db:migrate:status") + assert_match(/up\s+001\s+One migration/, output) + assert_match(/up\s+002\s+Two migration/, output) + + rails "db:migrate", "VERSION=0" + + output = rails("db:migrate:status") + assert_match(/down\s+001\s+One migration/, output) + assert_match(/down\s+002\s+Two migration/, output) end test "model and migration generator with change syntax" do - Dir.chdir(app_path) do - `bin/rails generate model user username:string password:string; - bin/rails generate migration add_email_to_users email:string` - - output = `bin/rails db:migrate` - assert_match(/create_table\(:users\)/, output) - assert_match(/CreateUsers: migrated/, output) - assert_match(/add_column\(:users, :email, :string\)/, output) - assert_match(/AddEmailToUsers: migrated/, output) - - output = `bin/rails db:rollback STEP=2` - assert_match(/drop_table\(:users\)/, output) - assert_match(/CreateUsers: reverted/, output) - assert_match(/remove_column\(:users, :email, :string\)/, output) - assert_match(/AddEmailToUsers: reverted/, output) - end + rails "generate", "model", "user", "username:string", "password:string" + rails "generate", "migration", "add_email_to_users", "email:string" + + output = rails("db:migrate") + assert_match(/create_table\(:users\)/, output) + assert_match(/CreateUsers: migrated/, output) + assert_match(/add_column\(:users, :email, :string\)/, output) + assert_match(/AddEmailToUsers: migrated/, output) + + output = rails("db:rollback", "STEP=2") + assert_match(/drop_table\(:users\)/, output) + assert_match(/CreateUsers: reverted/, output) + assert_match(/remove_column\(:users, :email, :string\)/, output) + assert_match(/AddEmailToUsers: reverted/, output) end test "migration status when schema migrations table is not present" do - output = Dir.chdir(app_path) { `bin/rails db:migrate:status 2>&1` } + output = rails("db:migrate:status", allow_failure: true) assert_equal "Schema migrations table does not exist yet.\n", output end - test "test migration status" do - Dir.chdir(app_path) do - `bin/rails generate model user username:string password:string; - bin/rails generate migration add_email_to_users email:string; - bin/rails db:migrate` + test "migration status" do + rails "generate", "model", "user", "username:string", "password:string" + rails "generate", "migration", "add_email_to_users", "email:string" + rails "db:migrate" - output = `bin/rails db:migrate:status` + output = rails("db:migrate:status") - assert_match(/up\s+\d{14}\s+Create users/, output) - assert_match(/up\s+\d{14}\s+Add email to users/, output) + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) - `bin/rails db:rollback STEP=1` - output = `bin/rails db:migrate:status` + rails "db:rollback", "STEP=1" + output = rails("db:migrate:status") - assert_match(/up\s+\d{14}\s+Create users/, output) - assert_match(/down\s+\d{14}\s+Add email to users/, output) - end + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/down\s+\d{14}\s+Add email to users/, output) end test "migration status without timestamps" do add_to_config("config.active_record.timestamped_migrations = false") - Dir.chdir(app_path) do - `bin/rails generate model user username:string password:string; - bin/rails generate migration add_email_to_users email:string; - bin/rails db:migrate` + rails "generate", "model", "user", "username:string", "password:string" + rails "generate", "migration", "add_email_to_users", "email:string" + rails "db:migrate" - output = `bin/rails db:migrate:status` + output = rails("db:migrate:status") - assert_match(/up\s+\d{3,}\s+Create users/, output) - assert_match(/up\s+\d{3,}\s+Add email to users/, output) + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/up\s+\d{3,}\s+Add email to users/, output) - `bin/rails db:rollback STEP=1` - output = `bin/rails db:migrate:status` + rails "db:rollback", "STEP=1" + output = rails("db:migrate:status") - assert_match(/up\s+\d{3,}\s+Create users/, output) - assert_match(/down\s+\d{3,}\s+Add email to users/, output) - end + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/down\s+\d{3,}\s+Add email to users/, output) end - test "test migration status after rollback and redo" do - Dir.chdir(app_path) do - `bin/rails generate model user username:string password:string; - bin/rails generate migration add_email_to_users email:string; - bin/rails db:migrate` + test "migration status after rollback and redo" do + rails "generate", "model", "user", "username:string", "password:string" + rails "generate", "migration", "add_email_to_users", "email:string" + rails "db:migrate" + + output = rails("db:migrate:status") + + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) - output = `bin/rails db:migrate:status` + rails "db:rollback", "STEP=2" + output = rails("db:migrate:status") + + assert_match(/down\s+\d{14}\s+Create users/, output) + assert_match(/down\s+\d{14}\s+Add email to users/, output) + + rails "db:migrate:redo" + output = rails("db:migrate:status") + + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) + end + test "migration status after rollback and forward" do + rails "generate", "model", "user", "username:string", "password:string" + rails "generate", "migration", "add_email_to_users", "email:string" + rails "db:migrate" + + output = rails("db:migrate:status") + + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) + + rails "db:rollback", "STEP=2" + output = rails("db:migrate:status") + + assert_match(/down\s+\d{14}\s+Create users/, output) + assert_match(/down\s+\d{14}\s+Add email to users/, output) + + rails "db:forward", "STEP=2" + output = rails("db:migrate:status") + + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) + end + + test "raise error on any move when current migration does not exist" do + Dir.chdir(app_path) do + rails "generate", "model", "user", "username:string", "password:string" + rails "generate", "migration", "add_email_to_users", "email:string" + rails "db:migrate" + `rm db/migrate/*email*.rb` + + output = rails("db:migrate:status") assert_match(/up\s+\d{14}\s+Create users/, output) - assert_match(/up\s+\d{14}\s+Add email to users/, output) + assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output) - `bin/rails db:rollback STEP=2` - output = `bin/rails db:migrate:status` + output = rails("db:rollback", allow_failure: true) + assert_match(/rails aborted!/, output) + assert_match(/ActiveRecord::UnknownMigrationVersionError:/, output) + assert_match(/No migration with version number\s\d{14}\./, output) - assert_match(/down\s+\d{14}\s+Create users/, output) - assert_match(/down\s+\d{14}\s+Add email to users/, output) + output = rails("db:migrate:status") + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output) - `bin/rails db:migrate:redo` - output = `bin/rails db:migrate:status` + output = rails("db:forward", allow_failure: true) + assert_match(/rails aborted!/, output) + assert_match(/ActiveRecord::UnknownMigrationVersionError:/, output) + assert_match(/No migration with version number\s\d{14}\./, output) + output = rails("db:migrate:status") assert_match(/up\s+\d{14}\s+Create users/, output) - assert_match(/up\s+\d{14}\s+Add email to users/, output) + assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output) end end + test "raise error on any move when target migration does not exist" do + app_file "db/migrate/01_one_migration.rb", <<-MIGRATION + class OneMigration < ActiveRecord::Migration::Current + end + MIGRATION + + app_file "db/migrate/02_two_migration.rb", <<-MIGRATION + class TwoMigration < ActiveRecord::Migration::Current + end + MIGRATION + + rails "db:migrate" + + output = rails("db:migrate:status") + assert_match(/up\s+001\s+One migration/, output) + assert_match(/up\s+002\s+Two migration/, output) + + output = rails("db:migrate", "VERSION=3", allow_failure: true) + assert_match(/rails aborted!/, output) + assert_match(/ActiveRecord::UnknownMigrationVersionError:/, output) + assert_match(/No migration with version number 3/, output) + + output = rails("db:migrate:status") + assert_match(/up\s+001\s+One migration/, output) + assert_match(/up\s+002\s+Two migration/, output) + end + + test "raise error on any move when VERSION has invalid format" do + output = rails("db:migrate", "VERSION=unknown", allow_failure: true) + assert_match(/rails aborted!/, output) + assert_match(/Invalid format of target version/, output) + + output = rails("db:migrate", "VERSION=0.1.11", allow_failure: true) + assert_match(/rails aborted!/, output) + assert_match(/Invalid format of target version/, output) + + output = rails("db:migrate", "VERSION=1.1.11", allow_failure: true) + assert_match(/rails aborted!/, output) + assert_match(/Invalid format of target version/, output) + + output = rails("db:migrate", "VERSION='0 '", allow_failure: true) + assert_match(/rails aborted!/, output) + assert_match(/Invalid format of target version/, output) + + output = rails("db:migrate", "VERSION=1.", allow_failure: true) + assert_match(/rails aborted!/, output) + assert_match(/Invalid format of target version/, output) + + output = rails("db:migrate", "VERSION=1_", allow_failure: true) + assert_match(/rails aborted!/, output) + assert_match(/Invalid format of target version/, output) + + output = rails("db:migrate", "VERSION=1_name", allow_failure: true) + assert_match(/rails aborted!/, output) + assert_match(/Invalid format of target version/, output) + + output = rails("db:migrate:redo", "VERSION=unknown", allow_failure: true) + assert_match(/rails aborted!/, output) + assert_match(/Invalid format of target version/, output) + + output = rails("db:migrate:up", "VERSION=unknown", allow_failure: true) + assert_match(/rails aborted!/, output) + assert_match(/Invalid format of target version/, output) + + output = rails("db:migrate:down", "VERSION=unknown", allow_failure: true) + assert_match(/rails aborted!/, output) + assert_match(/Invalid format of target version/, output) + end + test "migration status after rollback and redo without timestamps" do add_to_config("config.active_record.timestamped_migrations = false") - Dir.chdir(app_path) do - `bin/rails generate model user username:string password:string; - bin/rails generate migration add_email_to_users email:string; - bin/rails db:migrate` + rails "generate", "model", "user", "username:string", "password:string" + rails "generate", "migration", "add_email_to_users", "email:string" + rails "db:migrate" - output = `bin/rails db:migrate:status` + output = rails("db:migrate:status") - assert_match(/up\s+\d{3,}\s+Create users/, output) - assert_match(/up\s+\d{3,}\s+Add email to users/, output) + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/up\s+\d{3,}\s+Add email to users/, output) - `bin/rails db:rollback STEP=2` - output = `bin/rails db:migrate:status` + rails "db:rollback", "STEP=2" + output = rails("db:migrate:status") - assert_match(/down\s+\d{3,}\s+Create users/, output) - assert_match(/down\s+\d{3,}\s+Add email to users/, output) + assert_match(/down\s+\d{3,}\s+Create users/, output) + assert_match(/down\s+\d{3,}\s+Add email to users/, output) - `bin/rails db:migrate:redo` - output = `bin/rails db:migrate:status` + rails "db:migrate:redo" + output = rails("db:migrate:status") - assert_match(/up\s+\d{3,}\s+Create users/, output) - assert_match(/up\s+\d{3,}\s+Add email to users/, output) - end + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/up\s+\d{3,}\s+Add email to users/, output) end test "running migrations with not timestamp head migration files" do - Dir.chdir(app_path) do + app_file "db/migrate/1_one_migration.rb", <<-MIGRATION + class OneMigration < ActiveRecord::Migration::Current + end + MIGRATION - app_file "db/migrate/1_one_migration.rb", <<-MIGRATION - class OneMigration < ActiveRecord::Migration::Current - end - MIGRATION + app_file "db/migrate/02_two_migration.rb", <<-MIGRATION + class TwoMigration < ActiveRecord::Migration::Current + end + MIGRATION - app_file "db/migrate/02_two_migration.rb", <<-MIGRATION - class TwoMigration < ActiveRecord::Migration::Current - end - MIGRATION + rails "db:migrate" - `bin/rails db:migrate` + output = rails("db:migrate:status") - output = `bin/rails db:migrate:status` - - assert_match(/up\s+001\s+One migration/, output) - assert_match(/up\s+002\s+Two migration/, output) - end + assert_match(/up\s+001\s+One migration/, output) + assert_match(/up\s+002\s+Two migration/, output) end test "schema generation when dump_schema_after_migration is set" do add_to_config("config.active_record.dump_schema_after_migration = false") Dir.chdir(app_path) do - `bin/rails generate model book title:string` - output = `bin/rails generate model author name:string` + rails "generate", "model", "book", "title:string" + output = rails("generate", "model", "author", "name:string") version = output =~ %r{[^/]+db/migrate/(\d+)_create_authors\.rb} && $1 - `bin/rails db:migrate db:rollback db:forward db:migrate:up db:migrate:down VERSION=#{version}` + rails "db:migrate", "db:rollback", "db:forward", "db:migrate:up", "db:migrate:down", "VERSION=#{version}" assert !File.exist?("db/schema.rb"), "should not dump schema when configured not to" end add_to_config("config.active_record.dump_schema_after_migration = true") Dir.chdir(app_path) do - `bin/rails generate model reviews book_id:integer` - `bin/rails db:migrate` + rails "generate", "model", "reviews", "book_id:integer" + rails "db:migrate" structure_dump = File.read("db/schema.rb") assert_match(/create_table "reviews"/, structure_dump) @@ -200,23 +413,22 @@ module ApplicationTests test "default schema generation after migration" do Dir.chdir(app_path) do - `bin/rails generate model book title:string; - bin/rails db:migrate` + rails "generate", "model", "book", "title:string" + rails "db:migrate" structure_dump = File.read("db/schema.rb") assert_match(/create_table "books"/, structure_dump) end end - test "test migration status migrated file is deleted" do + test "migration status migrated file is deleted" do Dir.chdir(app_path) do - `bin/rails generate model user username:string password:string; - bin/rails generate migration add_email_to_users email:string; - bin/rails db:migrate - rm db/migrate/*email*.rb` + rails "generate", "model", "user", "username:string", "password:string" + rails "generate", "migration", "add_email_to_users", "email:string" + rails "db:migrate" + `rm db/migrate/*email*.rb` - output = `bin/rails db:migrate:status` - File.write("test.txt", output) + output = rails("db:migrate:status") assert_match(/up\s+\d{14}\s+Create users/, output) assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output) diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb index e7ffea2e71..8e9fe9b6b4 100644 --- a/railties/test/application/rake/notes_test.rb +++ b/railties/test/application/rake/notes_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" require "rails/source_annotation_extractor" @@ -90,7 +92,8 @@ module ApplicationTests test "custom rake task finds specific notes in specific directories" do app_file "app/controllers/some_controller.rb", "# TODO: note in app directory" - app_file "lib/some_file.rb", "# OPTIMIZE: note in lib directory\n" << "# FIXME: note in lib directory" + app_file "lib/some_file.rb", "# OPTIMIZE: note in lib directory\n" \ + "# FIXME: note in lib directory" app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory" app_file "lib/tasks/notes_custom.rake", <<-EOS diff --git a/railties/test/application/rake/restart_test.rb b/railties/test/application/rake/restart_test.rb index 6ebd2d5461..8614560bf2 100644 --- a/railties/test/application/rake/restart_test.rb +++ b/railties/test/application/rake/restart_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests @@ -15,12 +17,12 @@ module ApplicationTests test "rails restart touches tmp/restart.txt" do Dir.chdir(app_path) do - `bin/rails restart` + rails "restart" assert File.exist?("tmp/restart.txt") prev_mtime = File.mtime("tmp/restart.txt") sleep(1) - `bin/rails restart` + rails "restart" curr_mtime = File.mtime("tmp/restart.txt") assert_not_equal prev_mtime, curr_mtime end @@ -29,19 +31,10 @@ module ApplicationTests test "rails restart should work even if tmp folder does not exist" do Dir.chdir(app_path) do FileUtils.remove_dir("tmp") - `bin/rails restart` + rails "restart" assert File.exist?("tmp/restart.txt") end end - - test "rails restart removes server.pid also" do - Dir.chdir(app_path) do - FileUtils.mkdir_p("tmp/pids") - FileUtils.touch("tmp/pids/server.pid") - `bin/rails restart` - assert_not File.exist?("tmp/pids/server.pid") - end - end end end end diff --git a/railties/test/application/rake/tmp_test.rb b/railties/test/application/rake/tmp_test.rb new file mode 100644 index 0000000000..048fd7adcc --- /dev/null +++ b/railties/test/application/rake/tmp_test.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class TmpTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test "tmp:clear clear cache, socket and screenshot files" do + Dir.chdir(app_path) do + FileUtils.mkdir_p("tmp/cache") + FileUtils.touch("tmp/cache/cache_file") + + FileUtils.mkdir_p("tmp/sockets") + FileUtils.touch("tmp/sockets/socket_file") + + FileUtils.mkdir_p("tmp/screenshots") + FileUtils.touch("tmp/screenshots/fail.png") + + rails "tmp:clear" + + assert_not File.exist?("tmp/cache/cache_file") + assert_not File.exist?("tmp/sockets/socket_file") + assert_not File.exist?("tmp/screenshots/fail.png") + end + end + + test "tmp:clear should work if folder missing" do + FileUtils.remove_dir("#{app_path}/tmp") + rails "tmp:clear" + end + end + end +end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 1b64a0a1ca..5a6404bd0a 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -1,9 +1,12 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" +require "env_helpers" require "active_support/core_ext/string/strip" module ApplicationTests class RakeTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation + include ActiveSupport::Testing::Isolation, EnvHelpers def setup build_app @@ -24,20 +27,20 @@ module ApplicationTests end test "task is protected when previous migration was production" do - Dir.chdir(app_path) do - output = `bin/rails generate model product name:string; - env RAILS_ENV=production bin/rails db:create db:migrate; - env RAILS_ENV=production bin/rails db:test:prepare test 2>&1` + with_rails_env "production" do + rails "generate", "model", "product", "name:string" + rails "db:create", "db:migrate" + output = rails("db:test:prepare", allow_failure: true) assert_match(/ActiveRecord::ProtectedEnvironmentError/, output) end end def test_not_protected_when_previous_migration_was_not_production - Dir.chdir(app_path) do - output = `bin/rails generate model product name:string; - env RAILS_ENV=test bin/rails db:create db:migrate; - env RAILS_ENV=test bin/rails db:test:prepare test 2>&1` + with_rails_env "test" do + rails "generate", "model", "product", "name:string" + rails "db:create", "db:migrate" + output = rails("db:test:prepare", "test") refute_match(/ActiveRecord::ProtectedEnvironmentError/, output) end @@ -54,7 +57,7 @@ module ApplicationTests Rails.application.initialize! RUBY - assert_match("SuperMiddleware", Dir.chdir(app_path) { `bin/rails middleware` }) + assert_match("SuperMiddleware", rails("middleware")) end def test_initializers_are_executed_in_rake_tasks @@ -69,7 +72,7 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path) { `bin/rails do_nothing` } + output = rails("do_nothing") assert_match "Doing something...", output end @@ -90,7 +93,7 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path) { `bin/rails do_nothing` } + output = rails("do_nothing") assert_match "Hello world", output end @@ -98,6 +101,7 @@ module ApplicationTests add_to_config <<-RUBY rake_tasks do task do_nothing: :environment do + puts 'There is nothing' end end RUBY @@ -110,15 +114,13 @@ module ApplicationTests raise 'should not be pre-required for rake even eager_load=true' RUBY - Dir.chdir(app_path) do - assert system("bin/rails do_nothing RAILS_ENV=production"), - "should not be pre-required for rake even eager_load=true" - end + output = rails("do_nothing", "RAILS_ENV=production") + assert_match "There is nothing", output end def test_code_statistics_sanity - assert_match "Code LOC: 26 Test LOC: 0 Code to Test Ratio: 1:0.0", - Dir.chdir(app_path) { `bin/rails stats` } + assert_match "Code LOC: 25 Test LOC: 0 Code to Test Ratio: 1:0.0", + rails("stats") end def test_rails_routes_calls_the_route_inspector @@ -128,8 +130,17 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path) { `bin/rails routes` } - assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + output = rails("routes") + assert_equal <<-MESSAGE.strip_heredoc, output + Prefix Verb URI Pattern Controller#Action + cart GET /cart(.:format) cart#show + rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show + rails_blob_variation GET /rails/active_storage/variants/:signed_blob_id/:variation_key/*filename(.:format) active_storage/variants#show + rails_blob_preview GET /rails/active_storage/previews/:signed_blob_id/:variation_key/*filename(.:format) active_storage/previews#show + rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show + update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update + rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create + MESSAGE end def test_singular_resource_output_in_rake_routes @@ -148,7 +159,7 @@ module ApplicationTests " DELETE /post(.:format) posts#destroy", " POST /post(.:format) posts#create\n"].join("\n") - output = Dir.chdir(app_path) { `bin/rails routes -c PostController` } + output = rails("routes", "-c", "PostController") assert_equal expected_output, output end @@ -161,13 +172,24 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path) { `bin/rails routes -g show` } - assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + output = rails("routes", "-g", "show") + assert_equal <<-MESSAGE.strip_heredoc, output + Prefix Verb URI Pattern Controller#Action + cart GET /cart(.:format) cart#show + rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show + rails_blob_variation GET /rails/active_storage/variants/:signed_blob_id/:variation_key/*filename(.:format) active_storage/variants#show + rails_blob_preview GET /rails/active_storage/previews/:signed_blob_id/:variation_key/*filename(.:format) active_storage/previews#show + rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show + MESSAGE - output = Dir.chdir(app_path) { `bin/rails routes -g POST` } - assert_equal "Prefix Verb URI Pattern Controller#Action\n POST /cart(.:format) cart#create\n", output + output = rails("routes", "-g", "POST") + assert_equal <<-MESSAGE.strip_heredoc, output + Prefix Verb URI Pattern Controller#Action + POST /cart(.:format) cart#create + rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create + MESSAGE - output = Dir.chdir(app_path) { `bin/rails routes -g basketballs` } + output = rails("routes", "-g", "basketballs") assert_equal " Prefix Verb URI Pattern Controller#Action\n" \ "basketballs GET /basketballs(.:format) basketball#index\n", output end @@ -180,13 +202,13 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path) { `bin/rails routes -c cart` } + output = rails("routes", "-c", "cart") assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output - output = Dir.chdir(app_path) { `bin/rails routes -c Cart` } + output = rails("routes", "-c", "Cart") assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output - output = Dir.chdir(app_path) { `bin/rails routes -c CartController` } + output = rails("routes", "-c", "CartController") assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output end @@ -207,10 +229,10 @@ module ApplicationTests " DELETE /admin/post(.:format) admin/posts#destroy", " POST /admin/post(.:format) admin/posts#create\n"].join("\n") - output = Dir.chdir(app_path) { `bin/rails routes -c Admin::PostController` } + output = rails("routes", "-c", "Admin::PostController") assert_equal expected_output, output - output = Dir.chdir(app_path) { `bin/rails routes -c PostController` } + output = rails("routes", "-c", "PostController") assert_equal expected_output, output end @@ -220,12 +242,14 @@ module ApplicationTests end RUBY - assert_equal <<-MESSAGE.strip_heredoc, Dir.chdir(app_path) { `bin/rails routes` } - You don't have any routes defined! - - Please add some routes in config/routes.rb. - - For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html. + assert_equal <<-MESSAGE.strip_heredoc, rails("routes") + Prefix Verb URI Pattern Controller#Action + rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show + rails_blob_variation GET /rails/active_storage/variants/:signed_blob_id/:variation_key/*filename(.:format) active_storage/variants#show + rails_blob_preview GET /rails/active_storage/previews/:signed_blob_id/:variation_key/*filename(.:format) active_storage/previews#show + rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show + update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update + rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create MESSAGE end @@ -237,7 +261,17 @@ module ApplicationTests RUBY output = Dir.chdir(app_path) { `bin/rake --rakefile Rakefile routes` } - assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + + assert_equal <<-MESSAGE.strip_heredoc, output + Prefix Verb URI Pattern Controller#Action + cart GET /cart(.:format) cart#show + rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show + rails_blob_variation GET /rails/active_storage/variants/:signed_blob_id/:variation_key/*filename(.:format) active_storage/variants#show + rails_blob_preview GET /rails/active_storage/previews/:signed_blob_id/:variation_key/*filename(.:format) active_storage/previews#show + rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show + update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update + rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create + MESSAGE end def test_logger_is_flushed_when_exiting_production_rake_tasks @@ -249,44 +283,37 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path) { `bin/rails log_something RAILS_ENV=production && cat log/production.log` } - assert_match "Sample log message", output + rails "log_something", "RAILS_ENV=production" + assert_match "Sample log message", File.read("#{app_path}/log/production.log") end def test_loading_specific_fixtures - Dir.chdir(app_path) do - `bin/rails generate model user username:string password:string; - bin/rails generate model product name:string; - bin/rails db:migrate` - end + rails "generate", "model", "user", "username:string", "password:string" + rails "generate", "model", "product", "name:string" + rails "db:migrate" require "#{rails_root}/config/environment" # loading a specific fixture - errormsg = Dir.chdir(app_path) { `bin/rails db:fixtures:load FIXTURES=products` } - assert $?.success?, errormsg + rails "db:fixtures:load", "FIXTURES=products" assert_equal 2, ::AppTemplate::Application::Product.count assert_equal 0, ::AppTemplate::Application::User.count end def test_loading_only_yml_fixtures - Dir.chdir(app_path) do - `bin/rails db:migrate` - end + rails "db:migrate" app_file "test/fixtures/products.csv", "" require "#{rails_root}/config/environment" - errormsg = Dir.chdir(app_path) { `bin/rails db:fixtures:load` } - assert $?.success?, errormsg + rails "db:fixtures:load" end def test_scaffold_tests_pass_by_default - output = Dir.chdir(app_path) do - `bin/rails generate scaffold user username:string password:string; - RAILS_ENV=test bin/rails db:migrate test` - end + rails "generate", "scaffold", "user", "username:string", "password:string" + with_rails_env("test") { rails("db:migrate") } + output = rails("test") assert_match(/7 runs, 9 assertions, 0 failures, 0 errors/, output) assert_no_match(/Errors running/, output) @@ -302,22 +329,20 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path) do - `bin/rails generate scaffold user username:string password:string; - RAILS_ENV=test bin/rails db:migrate test` - end + rails "generate", "scaffold", "user", "username:string", "password:string" + with_rails_env("test") { rails("db:migrate") } + output = rails("test") assert_match(/5 runs, 7 assertions, 0 failures, 0 errors/, output) assert_no_match(/Errors running/, output) end def test_scaffold_with_references_columns_tests_pass_by_default - output = Dir.chdir(app_path) do - `bin/rails generate model Product; - bin/rails generate model Cart; - bin/rails generate scaffold LineItems product:references cart:belongs_to; - RAILS_ENV=test bin/rails db:migrate test` - end + rails "generate", "model", "Product" + rails "generate", "model", "Cart" + rails "generate", "scaffold", "LineItems", "product:references", "cart:belongs_to" + with_rails_env("test") { rails("db:migrate") } + output = rails("test") assert_match(/7 runs, 9 assertions, 0 failures, 0 errors/, output) assert_no_match(/Errors running/, output) @@ -325,59 +350,47 @@ module ApplicationTests def test_db_test_prepare_when_using_sql_format add_to_config "config.active_record.schema_format = :sql" - output = Dir.chdir(app_path) do - `bin/rails generate scaffold user username:string; - bin/rails db:migrate; - bin/rails db:test:prepare 2>&1 --trace` - end + rails "generate", "scaffold", "user", "username:string" + rails "db:migrate" + output = rails("db:test:prepare", "--trace") assert_match(/Execute db:test:load_structure/, output) end def test_rake_dump_structure_should_respect_db_structure_env_variable - Dir.chdir(app_path) do - # ensure we have a schema_migrations table to dump - `bin/rails db:migrate db:structure:dump SCHEMA=db/my_structure.sql` - end + # ensure we have a schema_migrations table to dump + rails "db:migrate", "db:structure:dump", "SCHEMA=db/my_structure.sql" assert File.exist?(File.join(app_path, "db", "my_structure.sql")) end def test_rake_dump_structure_should_be_called_twice_when_migrate_redo add_to_config "config.active_record.schema_format = :sql" - output = Dir.chdir(app_path) do - `bin/rails g model post title:string; - bin/rails db:migrate:redo 2>&1 --trace;` - end + rails "g", "model", "post", "title:string" + output = rails("db:migrate:redo", "--trace") # expect only Invoke db:structure:dump (first_time) assert_no_match(/^\*\* Invoke db:structure:dump\s+$/, output) end def test_rake_dump_schema_cache - Dir.chdir(app_path) do - `bin/rails generate model post title:string; - bin/rails generate model product name:string; - bin/rails db:migrate db:schema:cache:dump` - end + rails "generate", "model", "post", "title:string" + rails "generate", "model", "product", "name:string" + rails "db:migrate", "db:schema:cache:dump" assert File.exist?(File.join(app_path, "db", "schema_cache.yml")) end def test_rake_clear_schema_cache - Dir.chdir(app_path) do - `bin/rails db:schema:cache:dump db:schema:cache:clear` - end + rails "db:schema:cache:dump", "db:schema:cache:clear" assert !File.exist?(File.join(app_path, "db", "schema_cache.yml")) end def test_copy_templates - Dir.chdir(app_path) do - `bin/rails app:templates:copy` - %w(controller mailer scaffold).each do |dir| - assert File.exist?(File.join(app_path, "lib", "templates", "erb", dir)) - end - %w(controller helper scaffold_controller assets).each do |dir| - assert File.exist?(File.join(app_path, "lib", "templates", "rails", dir)) - end + rails "app:templates:copy" + %w(controller mailer scaffold).each do |dir| + assert File.exist?(File.join(app_path, "lib", "templates", "erb", dir)) + end + %w(controller helper scaffold_controller assets).each do |dir| + assert File.exist?(File.join(app_path, "lib", "templates", "rails", dir)) end end @@ -385,18 +398,8 @@ module ApplicationTests app_file "config/initializers/dummy.rb", "puts 'Hello, World!'" app_file "template.rb", "" - output = Dir.chdir(app_path) do - `bin/rails app:template LOCATION=template.rb` - end - + output = rails("app:template", "LOCATION=template.rb") assert_match(/Hello, World!/, output) end - - def test_tmp_clear_should_work_if_folder_missing - FileUtils.remove_dir("#{app_path}/tmp") - errormsg = Dir.chdir(app_path) { `bin/rails tmp:clear` } - assert_predicate $?, :success? - assert_empty errormsg - end end end diff --git a/railties/test/application/rendering_test.rb b/railties/test/application/rendering_test.rb index ccafc5b6f1..3724886c54 100644 --- a/railties/test/application/rendering_test.rb +++ b/railties/test/application/rendering_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" require "rack/test" diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index c515e2b270..bec038fb51 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" require "rack/test" @@ -263,7 +265,10 @@ module ApplicationTests assert_equal "WIN", last_response.body end - { "development" => "baz", "production" => "bar" }.each do |mode, expected| + { + "development" => ["baz", "http://www.apple.com", "/dashboard"], + "production" => ["bar", "http://www.microsoft.com", "/profile"] + }.each do |mode, (expected_action, expected_url, expected_mapping)| test "reloads routes when configuration is changed in #{mode}" do controller :foo, <<-RUBY class FooController < ApplicationController @@ -274,12 +279,40 @@ module ApplicationTests def baz render plain: "baz" end + + def custom + render plain: custom_url + end + + def mapping + render plain: url_for(User.new) + end + end + RUBY + + app_file "app/models/user.rb", <<-RUBY + class User + extend ActiveModel::Naming + include ActiveModel::Conversion + + def self.model_name + @_model_name ||= ActiveModel::Name.new(self.class, nil, "User") + end + + def persisted? + false + end end RUBY app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get 'foo', to: 'foo#bar' + get 'custom', to: 'foo#custom' + get 'mapping', to: 'foo#mapping' + + direct(:custom) { "http://www.microsoft.com" } + resolve("User") { "/profile" } end RUBY @@ -288,16 +321,33 @@ module ApplicationTests get "/foo" assert_equal "bar", last_response.body + get "/custom" + assert_equal "http://www.microsoft.com", last_response.body + + get "/mapping" + assert_equal "/profile", last_response.body + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get 'foo', to: 'foo#baz' + get 'custom', to: 'foo#custom' + get 'mapping', to: 'foo#mapping' + + direct(:custom) { "http://www.apple.com" } + resolve("User") { "/dashboard" } end RUBY sleep 0.1 get "/foo" - assert_equal expected, last_response.body + assert_equal expected_action, last_response.body + + get "/custom" + assert_equal expected_url, last_response.body + + get "/mapping" + assert_equal expected_mapping, last_response.body end end @@ -358,6 +408,14 @@ module ApplicationTests def index render plain: "foo" end + + def custom + render plain: custom_url + end + + def mapping + render plain: url_for(User.new) + end end RUBY @@ -369,6 +427,21 @@ module ApplicationTests end RUBY + app_file "app/models/user.rb", <<-RUBY + class User + extend ActiveModel::Naming + include ActiveModel::Conversion + + def self.model_name + @_model_name ||= ActiveModel::Name.new(self.class, nil, "User") + end + + def persisted? + false + end + end + RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get 'foo', to: 'foo#index' @@ -389,6 +462,12 @@ module ApplicationTests Rails.application.routes.draw do get 'foo', to: 'foo#index' get 'bar', to: 'bar#index' + + get 'custom', to: 'foo#custom' + direct(:custom) { 'http://www.apple.com' } + + get 'mapping', to: 'foo#mapping' + resolve('User') { '/profile' } end RUBY @@ -402,6 +481,14 @@ module ApplicationTests assert_equal "bar", last_response.body assert_equal "/bar", Rails.application.routes.url_helpers.bar_path + get "/custom" + assert_equal "http://www.apple.com", last_response.body + assert_equal "http://www.apple.com", Rails.application.routes.url_helpers.custom_url + + get "/mapping" + assert_equal "/profile", last_response.body + assert_equal "/profile", Rails.application.routes.url_helpers.polymorphic_path(User.new) + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get 'foo', to: 'foo#index' @@ -419,6 +506,18 @@ module ApplicationTests assert_raises NoMethodError do assert_equal "/bar", Rails.application.routes.url_helpers.bar_path end + + get "/custom" + assert_equal 404, last_response.status + assert_raises NoMethodError do + assert_equal "http://www.apple.com", Rails.application.routes.url_helpers.custom_url + end + + get "/mapping" + assert_equal 404, last_response.status + assert_raises NoMethodError do + assert_equal "/profile", Rails.application.routes.url_helpers.polymorphic_path(User.new) + end end test "named routes are cleared when reloading" do @@ -440,19 +539,41 @@ module ApplicationTests end RUBY + app_file "app/models/user.rb", <<-RUBY + class User + extend ActiveModel::Naming + include ActiveModel::Conversion + + def self.model_name + @_model_name ||= ActiveModel::Name.new(self.class, nil, "User") + end + + def persisted? + false + end + end + RUBY + app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':locale/foo', to: 'foo#index', as: 'foo' + get 'users', to: 'foo#users', as: 'users' + direct(:microsoft) { 'http://www.microsoft.com' } + resolve('User') { '/profile' } end RUBY get "/en/foo" assert_equal "foo", last_response.body assert_equal "/en/foo", Rails.application.routes.url_helpers.foo_path(locale: "en") + assert_equal "http://www.microsoft.com", Rails.application.routes.url_helpers.microsoft_url + assert_equal "/profile", Rails.application.routes.url_helpers.polymorphic_path(User.new) app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get ':locale/bar', to: 'bar#index', as: 'foo' + get 'users', to: 'foo#users', as: 'users' + direct(:apple) { 'http://www.apple.com' } end RUBY @@ -464,6 +585,12 @@ module ApplicationTests get "/en/bar" assert_equal "bar", last_response.body assert_equal "/en/bar", Rails.application.routes.url_helpers.foo_path(locale: "en") + assert_equal "http://www.apple.com", Rails.application.routes.url_helpers.apple_url + assert_equal "/users", Rails.application.routes.url_helpers.polymorphic_path(User.new) + + assert_raises NoMethodError do + assert_equal "http://www.microsoft.com", Rails.application.routes.url_helpers.microsoft_url + end end test "resource routing with irregular inflection" do @@ -493,5 +620,63 @@ module ApplicationTests get "/yazilar" assert_equal 200, last_response.status end + + test "reloading routes removes methods and doesn't undefine them" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/url', to: 'url#index' + end + RUBY + + app_file "app/models/url_helpers.rb", <<-RUBY + module UrlHelpers + def foo_path + "/foo" + end + end + RUBY + + app_file "app/models/context.rb", <<-RUBY + class Context + include UrlHelpers + include Rails.application.routes.url_helpers + end + RUBY + + controller "url", <<-RUBY + class UrlController < ApplicationController + def index + context = Context.new + render plain: context.foo_path + end + end + RUBY + + get "/url" + assert_equal "/foo", last_response.body + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/url', to: 'url#index' + get '/bar', to: 'foo#index', as: 'foo' + end + RUBY + + Rails.application.reload_routes! + + get "/url" + assert_equal "/bar", last_response.body + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/url', to: 'url#index' + end + RUBY + + Rails.application.reload_routes! + + get "/url" + assert_equal "/foo", last_response.body + end end end diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb index 7d058f6ee6..aa5d495c97 100644 --- a/railties/test/application/runner_test.rb +++ b/railties/test/application/runner_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" require "env_helpers" @@ -24,15 +26,20 @@ module ApplicationTests end def test_should_include_runner_in_shebang_line_in_help_without_option - assert_match "/rails runner", Dir.chdir(app_path) { `bin/rails runner` } + assert_match "/rails runner", rails("runner", allow_failure: true) end def test_should_include_runner_in_shebang_line_in_help - assert_match "/rails runner", Dir.chdir(app_path) { `bin/rails runner --help` } + assert_match "/rails runner", rails("runner", "--help") end def test_should_run_ruby_statement - assert_match "42", Dir.chdir(app_path) { `bin/rails runner "puts User.count"` } + assert_match "42", rails("runner", "puts User.count") + end + + def test_should_set_argv_when_running_code + output = rails("runner", "puts ARGV.join(',')", "--foo", "a1", "-b", "a2", "a3", "--moo") + assert_equal "--foo,a1,-b,a2,a3,--moo", output.chomp end def test_should_run_file @@ -40,7 +47,7 @@ module ApplicationTests puts User.count SCRIPT - assert_match "42", Dir.chdir(app_path) { `bin/rails runner "bin/count_users.rb"` } + assert_match "42", rails("runner", "bin/count_users.rb") end def test_no_minitest_loaded_in_production_mode @@ -57,7 +64,7 @@ module ApplicationTests puts $0 SCRIPT - assert_match "bin/dollar0.rb", Dir.chdir(app_path) { `bin/rails runner "bin/dollar0.rb"` } + assert_match "bin/dollar0.rb", rails("runner", "bin/dollar0.rb") end def test_should_set_dollar_program_name_to_file @@ -65,7 +72,7 @@ module ApplicationTests puts $PROGRAM_NAME SCRIPT - assert_match "bin/program_name.rb", Dir.chdir(app_path) { `bin/rails runner "bin/program_name.rb"` } + assert_match "bin/program_name.rb", rails("runner", "bin/program_name.rb") end def test_passes_extra_args_to_file @@ -73,7 +80,15 @@ module ApplicationTests p ARGV SCRIPT - assert_match %w( a b ).to_s, Dir.chdir(app_path) { `bin/rails runner "bin/program_name.rb" a b` } + assert_match %w( a b ).to_s, rails("runner", "bin/program_name.rb", "a", "b") + end + + def test_should_run_stdin + app_file "bin/count_users.rb", <<-SCRIPT + puts User.count + SCRIPT + + assert_match "42", Dir.chdir(app_path) { `cat bin/count_users.rb | bin/rails runner -` } end def test_with_hook @@ -83,35 +98,47 @@ module ApplicationTests end RUBY - assert_match "true", Dir.chdir(app_path) { `bin/rails runner "puts Rails.application.config.ran"` } + assert_match "true", rails("runner", "puts Rails.application.config.ran") end def test_default_environment - assert_match "development", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` } + assert_match "development", rails("runner", "puts Rails.env") end def test_runner_detects_syntax_errors - output = Dir.chdir(app_path) { `bin/rails runner "puts 'hello world" 2>&1` } + output = rails("runner", "puts 'hello world", allow_failure: true) assert_not $?.success? assert_match "unterminated string meets end of file", output end def test_runner_detects_bad_script_name - output = Dir.chdir(app_path) { `bin/rails runner "iuiqwiourowe" 2>&1` } + output = rails("runner", "iuiqwiourowe", allow_failure: true) assert_not $?.success? assert_match "undefined local variable or method `iuiqwiourowe' for", output end def test_environment_with_rails_env with_rails_env "production" do - assert_match "production", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` } + assert_match "production", rails("runner", "puts Rails.env") end end def test_environment_with_rack_env with_rack_env "production" do - assert_match "production", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` } + assert_match "production", rails("runner", "puts Rails.env") end end + + def test_can_call_same_name_class_as_defined_in_thor + app_file "app/models/task.rb", <<-MODEL + class Task + def self.count + 42 + end + end + MODEL + + assert_match "42", rails("runner", "puts Task.count") + end end end diff --git a/railties/test/application/server_test.rb b/railties/test/application/server_test.rb new file mode 100644 index 0000000000..a6093b5733 --- /dev/null +++ b/railties/test/application/server_test.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "console_helpers" +require "rails/command" +require "rails/commands/server/server_command" + +module ApplicationTests + class ServerTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include ConsoleHelpers + + def setup + build_app + end + + def teardown + teardown_app + end + + test "deprecate support of older `config.ru`" do + remove_file "config.ru" + app_file "config.ru", <<-RUBY + require_relative 'config/environment' + run AppTemplate::Application + RUBY + + server = Rails::Server.new(config: "#{app_path}/config.ru") + server.app + + log = File.read(Rails.application.config.paths["log"].first) + assert_match(/DEPRECATION WARNING: Use `Rails::Application` subclass to start the server is deprecated/, log) + end + + test "restart rails server with custom pid file path" do + skip "PTY unavailable" unless available_pty? + + File.open("#{app_path}/config/boot.rb", "w") do |f| + f.puts "ENV['BUNDLE_GEMFILE'] = '#{Bundler.default_gemfile.to_s}'" + f.puts "require 'bundler/setup'" + end + + master, slave = PTY.open + pid = nil + + begin + pid = Process.spawn("#{app_path}/bin/rails server -P tmp/dummy.pid", in: slave, out: slave, err: slave) + assert_output("Listening", master) + + rails("restart") + + assert_output("Restarting", master) + assert_output("Inherited", master) + ensure + kill(pid) if pid + end + end + + private + def kill(pid) + Process.kill("TERM", pid) + Process.wait(pid) + rescue Errno::ESRCH + end + end +end diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index d3d5b6d6dd..a01325fdb8 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" require "active_support/core_ext/string/strip" require "env_helpers" @@ -15,25 +17,49 @@ module ApplicationTests teardown_app end + def test_run_via_backwardscompatibility + require "minitest/rails_plugin" + + assert_nothing_raised do + Minitest.run_via[:ruby] = true + end + + assert Minitest.run_via[:ruby] + end + def test_run_single_file create_test_file :models, "foo" create_test_file :models, "bar" assert_match "1 runs, 1 assertions, 0 failures", run_test_command("test/models/foo_test.rb") end + def test_run_single_file_with_absolute_path + create_test_file :models, "foo" + create_test_file :models, "bar" + assert_match "1 runs, 1 assertions, 0 failures", run_test_command("#{app_path}/test/models/foo_test.rb") + end + def test_run_multiple_files create_test_file :models, "foo" create_test_file :models, "bar" assert_match "2 runs, 2 assertions, 0 failures", run_test_command("test/models/foo_test.rb test/models/bar_test.rb") end + def test_run_multiple_files_with_absolute_paths + create_test_file :models, "foo" + create_test_file :controllers, "foobar_controller" + create_test_file :models, "bar" + + assert_match "2 runs, 2 assertions, 0 failures", run_test_command("#{app_path}/test/models/foo_test.rb #{app_path}/test/controllers/foobar_controller_test.rb") + end + def test_run_file_with_syntax_error app_file "test/models/error_test.rb", <<-RUBY require 'test_helper' def; end RUBY - error = capture(:stderr) { run_test_command("test/models/error_test.rb") } + error = capture(:stderr) { run_test_command("test/models/error_test.rb", stderr: true) } assert_match "syntax error", error end @@ -60,12 +86,12 @@ module ApplicationTests end def test_run_units - skip "we no longer have the concept of unit tests. Just different directories..." create_test_file :models, "foo" create_test_file :helpers, "bar_helper" create_test_file :unit, "baz_unit" create_test_file :controllers, "foobar_controller" - run_test_units_command.tap do |output| + + rails("test:units").tap do |output| assert_match "FooTest", output assert_match "BarHelperTest", output assert_match "BazUnitTest", output @@ -107,12 +133,12 @@ module ApplicationTests end def test_run_functionals - skip "we no longer have the concept of functional tests. Just different directories..." create_test_file :mailers, "foo_mailer" create_test_file :controllers, "bar_controller" create_test_file :functional, "baz_functional" create_test_file :models, "foo" - run_test_functionals_command.tap do |output| + + rails("test:functionals").tap do |output| assert_match "FooMailerTest", output assert_match "BarControllerTest", output assert_match "BazFunctionalTest", output @@ -250,6 +276,18 @@ module ApplicationTests end end + def test_run_multiple_folders_with_absolute_paths + create_test_file :models, "account" + create_test_file :controllers, "accounts_controller" + create_test_file :helpers, "foo_helper" + + run_test_command("#{app_path}/test/models #{app_path}/test/controllers").tap do |output| + assert_match "AccountTest", output + assert_match "AccountsControllerTest", output + assert_match "2 runs, 2 assertions, 0 failures, 0 errors, 0 skips", output + end + end + def test_run_with_ruby_command app_file "test/models/post_test.rb", <<-RUBY require 'test_helper' @@ -309,7 +347,7 @@ module ApplicationTests assert true end - test "test line filter does not run this" do + test "line filter does not run this" do assert true end end @@ -455,7 +493,7 @@ module ApplicationTests def test_run_app_without_rails_loaded # Simulate a real Rails app boot. app_file "config/boot.rb", <<-RUBY - ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) + ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) require 'bundler/setup' # Set up gems listed in the Gemfile. RUBY @@ -475,18 +513,18 @@ module ApplicationTests create_test_file :models, "post", pass: false output = run_test_command("test/models/post_test.rb") - assert_match %r{Finished in.*\n\n1 runs, 1 assertions}, output + assert_match %r{Finished in.*\n1 runs, 1 assertions}, output end def test_fail_fast create_test_file :models, "post", pass: false assert_match(/Interrupt/, - capture(:stderr) { run_test_command("test/models/post_test.rb --fail-fast") }) + capture(:stderr) { run_test_command("test/models/post_test.rb --fail-fast", stderr: true) }) end def test_raise_error_when_specified_file_does_not_exist - error = capture(:stderr) { run_test_command("test/not_exists.rb") } + error = capture(:stderr) { run_test_command("test/not_exists.rb", stderr: true) } assert_match(%r{cannot load such file.+test/not_exists\.rb}, error) end @@ -512,14 +550,16 @@ module ApplicationTests def test_rails_db_create_all_restores_db_connection create_test_file :models, "account" - output = Dir.chdir(app_path) { `bin/rails db:create:all db:migrate && echo ".tables" | rails dbconsole` } + rails "db:create:all", "db:migrate" + output = Dir.chdir(app_path) { `echo ".tables" | rails dbconsole` } assert_match "ar_internal_metadata", output, "tables should be dumped" end def test_rails_db_create_all_restores_db_connection_after_drop create_test_file :models, "account" - Dir.chdir(app_path) { `bin/rails db:create:all` } # create all to avoid warnings - output = Dir.chdir(app_path) { `bin/rails db:drop:all db:create:all db:migrate && echo ".tables" | rails dbconsole` } + rails "db:create:all" # create all to avoid warnings + rails "db:drop:all", "db:create:all", "db:migrate" + output = Dir.chdir(app_path) { `echo ".tables" | rails dbconsole` } assert_match "ar_internal_metadata", output, "tables should be dumped" end @@ -529,6 +569,40 @@ module ApplicationTests assert_match "AccountTest#test_truth", output, "passing TEST= should run selected test" end + def test_running_with_ruby_gets_test_env_by_default + # Subshells inherit `ENV`, so we need to ensure `RAILS_ENV` is set to + # nil before we run the tests in the test app. + re, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], nil + + file = create_test_for_env("test") + results = Dir.chdir(app_path) { + `ruby -Ilib:test #{file}`.each_line.map { |line| JSON.parse line } + } + assert_equal 1, results.length + failures = results.first["failures"] + flunk(failures.first) unless failures.empty? + + ensure + ENV["RAILS_ENV"] = re + end + + def test_running_with_ruby_can_set_env_via_cmdline + # Subshells inherit `ENV`, so we need to ensure `RAILS_ENV` is set to + # nil before we run the tests in the test app. + re, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], nil + + file = create_test_for_env("development") + results = Dir.chdir(app_path) { + `RAILS_ENV=development ruby -Ilib:test #{file}`.each_line.map { |line| JSON.parse line } + } + assert_equal 1, results.length + failures = results.first["failures"] + flunk(failures.first) unless failures.empty? + + ensure + ENV["RAILS_ENV"] = re + end + def test_rake_passes_multiple_TESTOPTS_to_minitest create_test_file :models, "account" output = Dir.chdir(app_path) { `bin/rake test TESTOPTS='-v --seed=1234'` } @@ -559,16 +633,90 @@ module ApplicationTests end RUBY assert_match(/warning: assigned but unused variable/, - capture(:stderr) { run_test_command("test/models/warnings_test.rb -w") }) + capture(:stderr) { run_test_command("test/models/warnings_test.rb -w", stderr: true) }) + end + + def test_reset_sessions_before_rollback_on_system_tests + app_file "test/system/reset_session_before_rollback_test.rb", <<-RUBY + require "application_system_test_case" + + class ResetSessionBeforeRollbackTest < ApplicationSystemTestCase + def teardown_fixtures + puts "rollback" + super + end + + Capybara.singleton_class.prepend(Module.new do + def reset_sessions! + puts "reset sessions" + super + end + end) + + test "dummy" do + end + end + RUBY + + run_test_command("test/system/reset_session_before_rollback_test.rb").tap do |output| + assert_match "reset sessions\nrollback", output + assert_match "1 runs, 0 assertions, 0 failures, 0 errors, 0 skips", output + end + end + + def test_system_tests_are_not_run_with_the_default_test_command + app_file "test/system/dummy_test.rb", <<-RUBY + require "application_system_test_case" + + class DummyTest < ApplicationSystemTestCase + test "something" do + assert true + end + end + RUBY + + run_test_command("").tap do |output| + assert_match "0 runs, 0 assertions, 0 failures, 0 errors, 0 skips", output + end + end + + def test_system_tests_are_not_run_through_rake_test + app_file "test/system/dummy_test.rb", <<-RUBY + require "application_system_test_case" + + class DummyTest < ApplicationSystemTestCase + test "something" do + assert true + end + end + RUBY + + output = Dir.chdir(app_path) { `bin/rake test` } + assert_match "0 runs, 0 assertions, 0 failures, 0 errors, 0 skips", output + end + + def test_system_tests_are_run_through_rake_test_when_given_in_TEST + app_file "test/system/dummy_test.rb", <<-RUBY + require "application_system_test_case" + + class DummyTest < ApplicationSystemTestCase + test "something" do + assert true + end + end + RUBY + + output = Dir.chdir(app_path) { `bin/rake test TEST=test/system/dummy_test.rb` } + assert_match "1 runs, 1 assertions, 0 failures, 0 errors, 0 skips", output end private - def run_test_command(arguments = "test/unit/test_test.rb") - Dir.chdir(app_path) { `bin/rails t #{arguments}` } + def run_test_command(arguments = "test/unit/test_test.rb", **opts) + rails "t", *Shellwords.split(arguments), allow_failure: true, **opts end def create_model_with_fixture - script "generate model user name:string" + rails "generate", "model", "user", "name:string" app_file "test/fixtures/users.yml", <<-YAML.strip_heredoc vampire: @@ -613,6 +761,45 @@ module ApplicationTests app_file "db/schema.rb", "" end + def create_test_for_env(env) + app_file "test/models/environment_test.rb", <<-RUBY + require 'test_helper' + class JSONReporter < Minitest::AbstractReporter + def record(result) + puts JSON.dump(klass: result.class.name, + name: result.name, + failures: result.failures, + assertions: result.assertions, + time: result.time) + end + end + + def Minitest.plugin_json_reporter_init(opts) + Minitest.reporter.reporters.clear + Minitest.reporter.reporters << JSONReporter.new + end + + Minitest.extensions << "rails" + Minitest.extensions << "json_reporter" + + # Minitest uses RubyGems to find plugins, and since RubyGems + # doesn't know about the Rails installation we're pointing at, + # Minitest won't require the Rails minitest plugin when we run + # these integration tests. So we have to manually require the + # Minitest plugin here. + require 'minitest/rails_plugin' + + class EnvironmentTest < ActiveSupport::TestCase + def test_environment + test_db = ActiveRecord::Base.configurations[#{env.dump}]["database"] + db_file = ActiveRecord::Base.connection_config[:database] + assert_match(test_db, db_file) + assert_equal #{env.dump}, ENV["RAILS_ENV"] + end + end + RUBY + end + def create_test_file(path = :unit, name = "test", pass: true) app_file "test/#{path}/#{name}_test.rb", <<-RUBY require 'test_helper' @@ -639,17 +826,17 @@ module ApplicationTests end def create_scaffold - script "generate scaffold user name:string" - Dir.chdir(app_path) { File.exist?("app/models/user.rb") } + rails "generate", "scaffold", "user", "name:string" + assert File.exist?("#{app_path}/app/models/user.rb") run_migration end def create_controller - script "generate controller admin/dashboard index" + rails "generate", "controller", "admin/dashboard", "index" end def run_migration - Dir.chdir(app_path) { `bin/rails db:migrate` } + rails "db:migrate" end end end diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb index 32d2a6857c..0a51e98656 100644 --- a/railties/test/application/test_test.rb +++ b/railties/test/application/test_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests @@ -55,7 +57,7 @@ module ApplicationTests end RUBY - assert_unsuccessful_run "unit/foo_test.rb", "Failed assertion" + assert_unsuccessful_run "unit/foo_test.rb", "Failure:\nFooTest#test_truth" end test "integration test" do @@ -100,7 +102,7 @@ module ApplicationTests end test "ruby schema migrations" do - output = script("generate model user name:string") + output = rails("generate", "model", "user", "name:string") version = output.match(/(\d+)_create_users\.rb/)[1] app_file "test/models/user_test.rb", <<-RUBY @@ -137,7 +139,7 @@ module ApplicationTests end test "sql structure migrations" do - output = script("generate model user name:string") + output = rails("generate", "model", "user", "name:string") version = output.match(/(\d+)_create_users\.rb/)[1] app_file "test/models/user_test.rb", <<-RUBY @@ -176,7 +178,7 @@ module ApplicationTests end test "sql structure migrations when adding column to existing table" do - output_1 = script("generate model user name:string") + output_1 = rails("generate", "model", "user", "name:string") version_1 = output_1.match(/(\d+)_create_users\.rb/)[1] app_file "test/models/user_test.rb", <<-RUBY @@ -201,7 +203,7 @@ module ApplicationTests assert_successful_test_run("models/user_test.rb") - output_2 = script("generate migration add_email_to_users") + output_2 = rails("generate", "migration", "add_email_to_users") version_2 = output_2.match(/(\d+)_add_email_to_users\.rb/)[1] app_file "test/models/user_test.rb", <<-RUBY @@ -229,7 +231,7 @@ module ApplicationTests # For now, the user has to synchronize the schema manually. # This test-case serves as a reminder for this use-case. test "manually synchronize test schema after rollback" do - output = script("generate model user name:string") + output = rails("generate", "model", "user", "name:string") version = output.match(/(\d+)_create_users\.rb/)[1] app_file "test/models/user_test.rb", <<-RUBY @@ -263,7 +265,7 @@ module ApplicationTests assert_successful_test_run "models/user_test.rb" - Dir.chdir(app_path) { `bin/rails db:test:prepare` } + rails "db:test:prepare" assert_unsuccessful_run "models/user_test.rb", <<-ASSERTION Expected: ["id", "name"] @@ -272,7 +274,7 @@ Expected: ["id", "name"] end test "hooks for plugins" do - output = script("generate model user name:string") + output = rails("generate", "model", "user", "name:string") version = output.match(/(\d+)_create_users\.rb/)[1] app_file "lib/tasks/hooks.rake", <<-RUBY @@ -332,7 +334,7 @@ Expected: ["id", "name"] end def run_test_file(name, options = {}) - Dir.chdir(app_path) { `bin/rails test "#{app_path}/test/#{name}" 2>&1` } + rails "test", "#{app_path}/test/#{name}", allow_failure: true end end end diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb index 37f129475c..f22b9fda3d 100644 --- a/railties/test/application/url_generation_test.rb +++ b/railties/test/application/url_generation_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests @@ -14,7 +16,6 @@ module ApplicationTests require "action_view/railtie" class MyApp < Rails::Application - secrets.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4" config.session_store :cookie_store, key: "_myapp_session" config.active_support.deprecation = :log config.eager_load = false diff --git a/railties/test/application/version_test.rb b/railties/test/application/version_test.rb new file mode 100644 index 0000000000..ae85cf8f05 --- /dev/null +++ b/railties/test/application/version_test.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "rails/gem_version" + +class VersionTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test "command works" do + output = rails("version") + assert_equal "Rails #{Rails.gem_version}\n", output + end + + test "short-cut alias works" do + output = rails("-v") + assert_equal "Rails #{Rails.gem_version}\n", output + end +end diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb index f71e56f323..70917ba20b 100644 --- a/railties/test/backtrace_cleaner_test.rb +++ b/railties/test/backtrace_cleaner_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "rails/backtrace_cleaner" diff --git a/railties/test/code_statistics_calculator_test.rb b/railties/test/code_statistics_calculator_test.rb index 1bd4225f34..51917de2e0 100644 --- a/railties/test/code_statistics_calculator_test.rb +++ b/railties/test/code_statistics_calculator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "rails/code_statistics_calculator" @@ -317,7 +319,7 @@ class Animal private def temp_file(name, content) - dir = File.expand_path "../fixtures/tmp", __FILE__ + dir = File.expand_path "fixtures/tmp", __dir__ path = "#{dir}/#{name}" FileUtils.mkdir_p dir diff --git a/railties/test/code_statistics_test.rb b/railties/test/code_statistics_test.rb index 965b6eeb79..7ad1ac3094 100644 --- a/railties/test/code_statistics_test.rb +++ b/railties/test/code_statistics_test.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require "abstract_unit" require "rails/code_statistics" class CodeStatisticsTest < ActiveSupport::TestCase def setup - @tmp_path = File.expand_path(File.join(File.dirname(__FILE__), "fixtures", "tmp")) + @tmp_path = File.expand_path("fixtures/tmp", __dir__) @dir_js = File.join(@tmp_path, "lib.js") FileUtils.mkdir_p(@dir_js) end diff --git a/railties/test/command/base_test.rb b/railties/test/command/base_test.rb new file mode 100644 index 0000000000..a49ae8aae7 --- /dev/null +++ b/railties/test/command/base_test.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "rails/command" +require "rails/commands/generate/generate_command" +require "rails/commands/secrets/secrets_command" + +class Rails::Command::BaseTest < ActiveSupport::TestCase + test "printing commands" do + assert_equal %w(generate), Rails::Command::GenerateCommand.printing_commands + assert_equal %w(secrets:setup secrets:edit secrets:show), Rails::Command::SecretsCommand.printing_commands + end +end diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb index 4fc082e4ca..45ab8d87ff 100644 --- a/railties/test/commands/console_test.rb +++ b/railties/test/commands/console_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "env_helpers" require "rails/command" @@ -47,7 +49,7 @@ class Rails::ConsoleTest < ActiveSupport::TestCase end def test_console_with_environment - start ["-e production"] + start ["-e", "production"] assert_match(/\sproduction\s/, output) end @@ -82,24 +84,35 @@ class Rails::ConsoleTest < ActiveSupport::TestCase assert_match(/\sspecial-production\s/, output) end + def test_e_option_is_properly_expanded + start ["-e", "prod"] + assert_match(/\sproduction\s/, output) + end + def test_environment_option start ["--environment=special-production"] assert_match(/\sspecial-production\s/, output) end def test_rails_env_is_production_when_first_argument_is_p - start ["p"] - assert_match(/\sproduction\s/, output) + assert_deprecated do + start ["p"] + assert_match(/\sproduction\s/, output) + end end def test_rails_env_is_test_when_first_argument_is_t - start ["t"] - assert_match(/\stest\s/, output) + assert_deprecated do + start ["t"] + assert_match(/\stest\s/, output) + end end def test_rails_env_is_development_when_argument_is_d - start ["d"] - assert_match(/\sdevelopment\s/, output) + assert_deprecated do + start ["d"] + assert_match(/\sdevelopment\s/, output) + end end def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present @@ -111,7 +124,9 @@ class Rails::ConsoleTest < ActiveSupport::TestCase end end - assert_match("dev", parse_arguments(["dev"])[:environment]) + assert_deprecated do + assert_match("dev", parse_arguments(["dev"])[:environment]) + end ensure Rails::Command::ConsoleCommand.class_eval do undef_method :available_environments @@ -157,21 +172,8 @@ class Rails::ConsoleTest < ActiveSupport::TestCase end def parse_arguments(args) - Rails::Command::ConsoleCommand.class_eval do - alias_method :old_perform, :perform - define_method(:perform) do - extract_environment_option_from_argument - - options - end - end - - Rails::Command.invoke(:console, args) - ensure - Rails::Command::ConsoleCommand.class_eval do - undef_method :perform - alias_method :perform, :old_perform - undef_method :old_perform - end + command = Rails::Command::ConsoleCommand.new([], args) + command.send(:extract_environment_option_from_argument) + command.options end end diff --git a/railties/test/commands/credentials_test.rb b/railties/test/commands/credentials_test.rb new file mode 100644 index 0000000000..7c464b3fde --- /dev/null +++ b/railties/test/commands/credentials_test.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "env_helpers" +require "rails/command" +require "rails/commands/credentials/credentials_command" + +class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation, EnvHelpers + + setup { build_app } + + teardown { teardown_app } + + test "edit without editor gives hint" do + run_edit_command(editor: "").tap do |output| + assert_match "No $EDITOR to open file in", output + assert_match "bin/rails credentials:edit", output + end + end + + test "edit credentials" do + # Run twice to ensure credentials can be reread after first edit pass. + 2.times do + assert_match(/access_key_id: 123/, run_edit_command) + end + end + + test "edit command does not add master key to gitignore when already exist" do + run_edit_command + + Dir.chdir(app_path) do + gitignore = File.read(".gitignore") + assert_equal 1, gitignore.scan(%r|config/master\.key|).length + end + end + + test "edit command does not overwrite by default if credentials already exists" do + run_edit_command(editor: "eval echo api_key: abc >") + assert_match(/api_key: abc/, run_show_command) + + run_edit_command + assert_match(/api_key: abc/, run_show_command) + end + + test "show credentials" do + assert_match(/access_key_id: 123/, run_show_command) + end + + test "show command raise error when require_master_key is specified and key does not exist" do + remove_file "config/master.key" + add_to_config "config.require_master_key = true" + + assert_match(/Missing encryption key to decrypt file with/, run_show_command(allow_failure: true)) + end + + test "show command does not raise error when require_master_key is false and master key does not exist" do + remove_file "config/master.key" + add_to_config "config.require_master_key = false" + + assert_match(/Missing master key to decrypt credentials/, run_show_command) + end + + private + def run_edit_command(editor: "cat") + switch_env("EDITOR", editor) do + rails "credentials:edit" + end + end + + def run_show_command(**options) + rails "credentials:show", **options + end +end diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb index 0f8c5dbb79..6ad96b28c7 100644 --- a/railties/test/commands/dbconsole_test.rb +++ b/railties/test/commands/dbconsole_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "minitest/mock" require "rails/command" @@ -98,14 +100,24 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase end def test_rails_env_is_development_when_argument_is_dev + assert_deprecated do + stub_available_environments([ "development", "test" ]) do + assert_match("development", parse_arguments([ "dev" ])[:environment]) + end + end + end + + def test_rails_env_is_development_when_environment_option_is_dev stub_available_environments([ "development", "test" ]) do - assert_match("development", parse_arguments([ "dev" ])[:environment]) + assert_match("development", parse_arguments([ "-e", "dev" ])[:environment]) end end def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present - stub_available_environments([ "dev" ]) do - assert_match("dev", parse_arguments([ "dev" ])[:environment]) + assert_deprecated do + stub_available_environments([ "dev" ]) do + assert_match("dev", parse_arguments([ "dev" ])[:environment]) + end end end @@ -194,12 +206,61 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase assert_equal ["sqlplus", "user/secret@db"], dbconsole.find_cmd_and_exec_args end + def test_sqlserver + start(adapter: "sqlserver", database: "db", username: "user", password: "secret", host: "localhost", port: 1433) + assert_not aborted + assert_equal ["sqsh", "-D", "db", "-U", "user", "-P", "secret", "-S", "localhost:1433"], dbconsole.find_cmd_and_exec_args + end + def test_unknown_command_line_client start(adapter: "unknown", database: "db") assert aborted assert_match(/Unknown command-line client for db/, output) end + def test_primary_is_automatically_picked_with_3_level_configuration + sample_config = { + "test" => { + "primary" => { + "adapter" => "postgresql" + } + } + } + + app_db_config(sample_config) do + assert_equal "postgresql", Rails::DBConsole.new.config["adapter"] + end + end + + def test_specifying_a_custom_connection_and_environment + stub_available_environments(["development"]) do + dbconsole = parse_arguments(["-c", "custom", "-e", "development"]) + + assert_equal "development", dbconsole[:environment] + assert_equal "custom", dbconsole.connection + end + end + + def test_specifying_a_missing_connection + app_db_config({}) do + e = assert_raises(ActiveRecord::AdapterNotSpecified) do + Rails::Command.invoke(:dbconsole, ["-c", "i_do_not_exist"]) + end + + assert_includes e.message, "'i_do_not_exist' connection is not configured." + end + end + + def test_specifying_a_missing_environment + app_db_config({}) do + e = assert_raises(ActiveRecord::AdapterNotSpecified) do + Rails::Command.invoke(:dbconsole) + end + + assert_includes e.message, "'test' database is not configured." + end + end + def test_print_help_short stdout = capture(:stdout) do Rails::Command.invoke(:dbconsole, ["-h"]) diff --git a/railties/test/commands/encrypted_test.rb b/railties/test/commands/encrypted_test.rb new file mode 100644 index 0000000000..6647dcc902 --- /dev/null +++ b/railties/test/commands/encrypted_test.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "env_helpers" +require "rails/command" +require "rails/commands/encrypted/encrypted_command" + +class Rails::Command::EncryptedCommandTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation, EnvHelpers + + setup :build_app + teardown :teardown_app + + test "edit without editor gives hint" do + run_edit_command("config/tokens.yml.enc", editor: "").tap do |output| + assert_match "No $EDITOR to open file in", output + assert_match "bin/rails encrypted:edit", output + end + end + + test "edit encrypted file" do + # Run twice to ensure file can be reread after first edit pass. + 2.times do + assert_match(/access_key_id: 123/, run_edit_command("config/tokens.yml.enc")) + end + end + + test "edit command does not add master key to gitignore when already exist" do + run_edit_command("config/tokens.yml.enc") + + Dir.chdir(app_path) do + assert_match "/config/master.key", File.read(".gitignore") + end + end + + test "edit encrypts file with custom key" do + run_edit_command("config/tokens.yml.enc", key: "config/tokens.key") + + Dir.chdir(app_path) do + assert File.exist?("config/tokens.yml.enc") + assert File.exist?("config/tokens.key") + + assert_match "/config/tokens.key", File.read(".gitignore") + end + + assert_match(/access_key_id: 123/, run_edit_command("config/tokens.yml.enc", key: "config/tokens.key")) + end + + test "show encrypted file with custom key" do + run_edit_command("config/tokens.yml.enc", key: "config/tokens.key") + + assert_match(/access_key_id: 123/, run_show_command("config/tokens.yml.enc", key: "config/tokens.key")) + end + + test "show command raise error when require_master_key is specified and key does not exist" do + add_to_config "config.require_master_key = true" + + assert_match(/Missing encryption key to decrypt file with/, + run_show_command("config/tokens.yml.enc", key: "unexist.key", allow_failure: true)) + end + + test "show command does not raise error when require_master_key is false and master key does not exist" do + remove_file "config/master.key" + add_to_config "config.require_master_key = false" + + assert_match(/Missing 'config\/master\.key' to decrypt data/, run_show_command("config/tokens.yml.enc")) + end + + test "won't corrupt encrypted file when passed wrong key" do + run_edit_command("config/tokens.yml.enc", key: "config/tokens.key") + + assert_match "passed the wrong key", + run_edit_command("config/tokens.yml.enc", allow_failure: true) + + assert_match(/access_key_id: 123/, run_show_command("config/tokens.yml.enc", key: "config/tokens.key")) + end + + private + def run_edit_command(file, key: nil, editor: "cat", **options) + switch_env("EDITOR", editor) do + rails "encrypted:edit", prepare_args(file, key), **options + end + end + + def run_show_command(file, key: nil, **options) + rails "encrypted:show", prepare_args(file, key), **options + end + + def prepare_args(file, key) + args = [ file ] + args.push("--key", key) if key + args + end +end diff --git a/railties/test/commands/secrets_test.rb b/railties/test/commands/secrets_test.rb new file mode 100644 index 0000000000..6b9f284a0c --- /dev/null +++ b/railties/test/commands/secrets_test.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "env_helpers" +require "rails/command" +require "rails/commands/secrets/secrets_command" + +class Rails::Command::SecretsCommandTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation, EnvHelpers + + setup :build_app + teardown :teardown_app + + test "edit without editor gives hint" do + assert_match "No $EDITOR to open decrypted secrets in", run_edit_command(editor: "") + end + + test "encrypted secrets are deprecated when using credentials" do + assert_match "Encrypted secrets is deprecated", run_setup_command + assert_equal 1, $?.exitstatus + assert_not File.exist?("config/secrets.yml.enc") + end + + test "encrypted secrets are deprecated when running edit without setup" do + assert_match "Encrypted secrets is deprecated", run_setup_command + assert_equal 1, $?.exitstatus + assert_not File.exist?("config/secrets.yml.enc") + end + + test "encrypted secrets are deprecated for 5.1 config/secrets.yml apps" do + Dir.chdir(app_path) do + FileUtils.rm("config/credentials.yml.enc") + FileUtils.touch("config/secrets.yml") + + assert_match "Encrypted secrets is deprecated", run_setup_command + assert_equal 1, $?.exitstatus + assert_not File.exist?("config/secrets.yml.enc") + end + end + + test "edit secrets" do + prevent_deprecation + + # Run twice to ensure encrypted secrets can be reread after first edit pass. + 2.times do + assert_match(/external_api_key: 1466aac22e6a869134be3d09b9e89232fc2c2289/, run_edit_command) + end + end + + test "show secrets" do + prevent_deprecation + + assert_match(/external_api_key: 1466aac22e6a869134be3d09b9e89232fc2c2289/, run_show_command) + end + + private + def prevent_deprecation + Dir.chdir(app_path) do + File.write("config/secrets.yml.key", "f731758c639da2604dfb6bf3d1025de8") + File.write("config/secrets.yml.enc", "sEB0mHxDbeP1/KdnMk00wyzPFACl9K6t0cZWn5/Mfx/YbTHvnI07vrneqHg9kaH3wOS7L6pIQteu1P077OtE4BSx/ZRc/sgQPHyWu/tXsrfHqnPNpayOF/XZqizE91JacSFItNMWpuPsp9ynbzz+7cGhoB1S4aPNIU6u0doMrzdngDbijsaAFJmsHIQh6t/QHoJx--8aMoE0PvUWmw1Iqz--ldFqnM/K0g9k17M8PKoN/Q==") + end + end + + def run_edit_command(editor: "cat") + switch_env("EDITOR", editor) do + rails "secrets:edit", allow_failure: true + end + end + + def run_show_command + rails "secrets:show", allow_failure: true + end + + def run_setup_command + rails "secrets:setup", allow_failure: true + end +end diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb index e3dfc3e82b..33715ea75f 100644 --- a/railties/test/commands/server_test.rb +++ b/railties/test/commands/server_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "env_helpers" require "rails/command" @@ -20,6 +22,18 @@ class Rails::ServerTest < ActiveSupport::TestCase assert_nil options[:server] end + def test_daemon_with_option + args = ["-d"] + options = parse_arguments(args) + assert_equal true, options[:daemonize] + end + + def test_daemon_without_option + args = [] + options = parse_arguments(args) + assert_equal false, options[:daemonize] + end + def test_server_option_without_environment args = ["thin"] with_rack_env nil do @@ -79,6 +93,18 @@ class Rails::ServerTest < ActiveSupport::TestCase assert_equal false, options[:caching] end + def test_early_hints_with_option + args = ["--early-hints"] + options = parse_arguments(args) + assert_equal true, options[:early_hints] + end + + def test_early_hints_is_nil_by_default + args = [] + options = parse_arguments(args) + assert_nil options[:early_hints] + end + def test_log_stdout with_rack_env nil do with_rails_env nil do @@ -121,6 +147,58 @@ class Rails::ServerTest < ActiveSupport::TestCase end end + def test_host + with_rails_env "development" do + options = parse_arguments([]) + assert_equal "localhost", options[:Host] + end + + with_rails_env "production" do + options = parse_arguments([]) + assert_equal "0.0.0.0", options[:Host] + end + + with_rails_env "development" do + args = ["-b", "127.0.0.1"] + options = parse_arguments(args) + assert_equal "127.0.0.1", options[:Host] + end + end + + def test_argument_precedence_over_environment_variable + switch_env "PORT", "1234" do + args = ["-p", "5678"] + options = parse_arguments(args) + assert_equal 5678, options[:Port] + end + + switch_env "PORT", "1234" do + args = ["-p", "3000"] + options = parse_arguments(args) + assert_equal 3000, options[:Port] + end + + switch_env "HOST", "1.2.3.4" do + args = ["-b", "127.0.0.1"] + options = parse_arguments(args) + assert_equal "127.0.0.1", options[:Host] + end + end + + def test_records_user_supplied_options + server_options = parse_arguments(["-p", 3001]) + assert_equal [:Port], server_options[:user_supplied_options] + + server_options = parse_arguments(["--port", 3001]) + assert_equal [:Port], server_options[:user_supplied_options] + + server_options = parse_arguments(["-p3001", "-C", "--binding", "127.0.0.1"]) + assert_equal [:Port, :Host, :caching], server_options[:user_supplied_options] + + server_options = parse_arguments(["--port=3001"]) + assert_equal [:Port], server_options[:user_supplied_options] + end + def test_default_options server = Rails::Server.new old_default_options = server.default_options @@ -136,7 +214,7 @@ class Rails::ServerTest < ActiveSupport::TestCase ARGV.replace args options = parse_arguments(args) - expected = "bin/rails server -p 4567 -b 127.0.0.1 -c dummy_config.ru -d -e test -P tmp/server.pid -C" + expected = "bin/rails server -p 4567 -b 127.0.0.1 -c dummy_config.ru -d -e test -P tmp/server.pid -C --restart" assert_equal expected, options[:restart_cmd] ensure diff --git a/railties/test/configuration/middleware_stack_proxy_test.rb b/railties/test/configuration/middleware_stack_proxy_test.rb index 559ce72693..bc72b7f0c9 100644 --- a/railties/test/configuration/middleware_stack_proxy_test.rb +++ b/railties/test/configuration/middleware_stack_proxy_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support" require "active_support/testing/autorun" require "rails/configuration" diff --git a/railties/test/console_helpers.rb b/railties/test/console_helpers.rb new file mode 100644 index 0000000000..8350fce5ee --- /dev/null +++ b/railties/test/console_helpers.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +begin + require "pty" +rescue LoadError +end + +module ConsoleHelpers + def assert_output(expected, io, timeout = 10) + timeout = Time.now + timeout + + output = "".dup + until output.include?(expected) || Time.now > timeout + if IO.select([io], [], [], 0.1) + output << io.read(1) + end + end + + assert_includes output, expected, "#{expected.inspect} expected, but got:\n\n#{output}" + end + + def available_pty? + defined?(PTY) && PTY.respond_to?(:open) + end +end diff --git a/railties/test/engine/commands_test.rb b/railties/test/engine/commands_test.rb index b1c937143f..aeb64d445b 100644 --- a/railties/test/engine/commands_test.rb +++ b/railties/test/engine/commands_test.rb @@ -1,10 +1,11 @@ +# frozen_string_literal: true + require "abstract_unit" -begin - require "pty" -rescue LoadError -end +require "console_helpers" class Rails::Engine::CommandsTest < ActiveSupport::TestCase + include ConsoleHelpers + def setup @destination_root = Dir.mktmpdir("bukkits") Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --mountable` } @@ -64,19 +65,6 @@ class Rails::Engine::CommandsTest < ActiveSupport::TestCase "#{@destination_root}/bukkits" end - def assert_output(expected, io, timeout = 10) - timeout = Time.now + timeout - - output = "" - until output.include?(expected) || Time.now > timeout - if IO.select([io], [], [], 0.1) - output << io.read(1) - end - end - - assert_includes output, expected, "#{expected.inspect} expected, but got:\n\n#{output}" - end - def spawn_command(command, fd) Process.spawn( "#{plugin_path}/bin/rails #{command}", @@ -84,10 +72,6 @@ class Rails::Engine::CommandsTest < ActiveSupport::TestCase ) end - def available_pty? - defined?(PTY) && PTY.respond_to?(:open) - end - def kill(pid) Process.kill("TERM", pid) Process.wait(pid) diff --git a/railties/test/engine/test_test.rb b/railties/test/engine/test_test.rb new file mode 100644 index 0000000000..18af85a0aa --- /dev/null +++ b/railties/test/engine/test_test.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "abstract_unit" + +class Rails::Engine::TestTest < ActiveSupport::TestCase + setup do + @destination_root = Dir.mktmpdir("bukkits") + Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --mountable` } + end + + teardown do + FileUtils.rm_rf(@destination_root) + end + + test "automatically synchronize test schema" do + Dir.chdir(plugin_path) do + # In order to confirm that migration files are loaded, generate multiple migration files. + `bin/rails generate model user name:string; + bin/rails generate model todo name:string; + RAILS_ENV=development bin/rails db:migrate` + + output = `bin/rails test test/models/bukkits/user_test.rb` + assert_includes(output, "0 runs, 0 assertions, 0 failures, 0 errors, 0 skips") + end + end + + private + def plugin_path + "#{@destination_root}/bukkits" + end +end diff --git a/railties/test/engine_test.rb b/railties/test/engine_test.rb index 248afa2d2c..4bd8a07085 100644 --- a/railties/test/engine_test.rb +++ b/railties/test/engine_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class EngineTest < ActiveSupport::TestCase diff --git a/railties/test/env_helpers.rb b/railties/test/env_helpers.rb index 1f64d5fda3..336832b867 100644 --- a/railties/test/env_helpers.rb +++ b/railties/test/env_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails" module EnvHelpers diff --git a/railties/test/fixtures/about_yml_plugins/bad_about_yml/about.yml b/railties/test/fixtures/about_yml_plugins/bad_about_yml/about.yml deleted file mode 100644 index fe80872a16..0000000000 --- a/railties/test/fixtures/about_yml_plugins/bad_about_yml/about.yml +++ /dev/null @@ -1 +0,0 @@ -# an empty YAML file - any content in here seems to get parsed as a string
\ No newline at end of file diff --git a/railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb b/railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb deleted file mode 100644 index 636bc1a8ab..0000000000 --- a/railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb +++ /dev/null @@ -1 +0,0 @@ -# intentionally empty diff --git a/railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb b/railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb deleted file mode 100644 index 636bc1a8ab..0000000000 --- a/railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb +++ /dev/null @@ -1 +0,0 @@ -# intentionally empty diff --git a/railties/test/fixtures/lib/create_test_dummy_template.rb b/railties/test/fixtures/lib/create_test_dummy_template.rb index e4378bbd1a..b9eb6a912d 100644 --- a/railties/test/fixtures/lib/create_test_dummy_template.rb +++ b/railties/test/fixtures/lib/create_test_dummy_template.rb @@ -1 +1,3 @@ +# frozen_string_literal: true + create_dummy_app("spec/dummy") diff --git a/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb b/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb index 1139350b94..f196971f20 100644 --- a/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb +++ b/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rails/generators/active_record" module ActiveRecord diff --git a/railties/test/fixtures/lib/generators/fixjour_generator.rb b/railties/test/fixtures/lib/generators/fixjour_generator.rb index ef3e9edbed..22197835a8 100644 --- a/railties/test/fixtures/lib/generators/fixjour_generator.rb +++ b/railties/test/fixtures/lib/generators/fixjour_generator.rb @@ -1,2 +1,4 @@ +# frozen_string_literal: true + class FixjourGenerator < Rails::Generators::NamedBase end diff --git a/railties/test/fixtures/lib/generators/model_generator.rb b/railties/test/fixtures/lib/generators/model_generator.rb index 0905fa9631..3009472c3d 100644 --- a/railties/test/fixtures/lib/generators/model_generator.rb +++ b/railties/test/fixtures/lib/generators/model_generator.rb @@ -1 +1,3 @@ +# frozen_string_literal: true + raise "I should never be loaded" diff --git a/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb b/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb index 21b0ff6c28..5a847a8bd2 100644 --- a/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb +++ b/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + require "rails/generators" class UsageTemplateGenerator < Rails::Generators::Base - source_root File.expand_path("templates", File.dirname(__FILE__)) + source_root File.expand_path("templates", __dir__) end diff --git a/railties/test/fixtures/lib/rails/generators/foobar/foobar_generator.rb b/railties/test/fixtures/lib/rails/generators/foobar/foobar_generator.rb index d1de8c56fa..159843866c 100644 --- a/railties/test/fixtures/lib/rails/generators/foobar/foobar_generator.rb +++ b/railties/test/fixtures/lib/rails/generators/foobar/foobar_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Foobar class FoobarGenerator < Rails::Generators::Base end diff --git a/railties/test/fixtures/lib/template.rb b/railties/test/fixtures/lib/template.rb index c14a1a8784..44083c25e8 100644 --- a/railties/test/fixtures/lib/template.rb +++ b/railties/test/fixtures/lib/template.rb @@ -1 +1,3 @@ +# frozen_string_literal: true + say "It works from file!" diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index 360e8e97d7..f421207025 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/app/app_generator" require "env_helpers" @@ -69,10 +71,17 @@ class ActionsTest < Rails::Generators::TestCase def test_gem_with_version_should_include_version_in_gemfile run_generator - - action :gem, "rspec", ">=2.0.0.a5" - - assert_file "Gemfile", /gem 'rspec', '>=2.0.0.a5'/ + action :gem, "rspec", ">= 2.0.0.a5" + action :gem, "RedCloth", ">= 4.1.0", "< 4.2.0" + action :gem, "nokogiri", version: ">= 1.4.2" + action :gem, "faker", version: [">= 0.1.0", "< 0.3.0"] + + assert_file "Gemfile" do |content| + assert_match(/gem 'rspec', '>= 2\.0\.0\.a5'/, content) + assert_match(/gem 'RedCloth', '>= 4\.1\.0', '< 4\.2\.0'/, content) + assert_match(/gem 'nokogiri', '>= 1\.4\.2'/, content) + assert_match(/gem 'faker', '>= 0\.1\.0', '< 0\.3\.0'/, content) + end end def test_gem_should_insert_on_separate_lines @@ -139,14 +148,14 @@ class ActionsTest < Rails::Generators::TestCase run_generator autoload_paths = 'config.autoload_paths += %w["#{Rails.root}/app/extras"]' action :environment, autoload_paths - assert_file "config/application.rb", / class Application < Rails::Application\n #{Regexp.escape(autoload_paths)}/ + assert_file "config/application.rb", / class Application < Rails::Application\n #{Regexp.escape(autoload_paths)}\n/ end def test_environment_should_include_data_in_environment_initializer_block_with_env_option run_generator autoload_paths = 'config.autoload_paths += %w["#{Rails.root}/app/extras"]' action :environment, autoload_paths, env: "development" - assert_file "config/environments/development.rb", /Rails\.application\.configure do\n #{Regexp.escape(autoload_paths)}/ + assert_file "config/environments/development.rb", /Rails\.application\.configure do\n #{Regexp.escape(autoload_paths)}\n/ end def test_environment_with_block_should_include_block_contents_in_environment_initializer_block @@ -163,6 +172,26 @@ class ActionsTest < Rails::Generators::TestCase end end + def test_environment_with_block_should_include_block_contents_with_multiline_data_in_environment_initializer_block + run_generator + data = <<-RUBY + config.encoding = "utf-8" + config.time_zone = "UTC" + RUBY + action(:environment) { data } + assert_file "config/application.rb", / class Application < Rails::Application\n#{Regexp.escape(data.strip_heredoc.indent(4))}/ + end + + def test_environment_should_include_block_contents_with_multiline_data_in_environment_initializer_block_with_env_option + run_generator + data = <<-RUBY + config.encoding = "utf-8" + config.time_zone = "UTC" + RUBY + action(:environment, nil, env: "development") { data } + assert_file "config/environments/development.rb", /Rails\.application\.configure do\n#{Regexp.escape(data.strip_heredoc.indent(2))}/ + end + def test_git_with_symbol_should_run_command_using_git_scm assert_called_with(generator, :run, ["git init"]) do action :git, :init @@ -177,22 +206,62 @@ class ActionsTest < Rails::Generators::TestCase def test_vendor_should_write_data_to_file_in_vendor action :vendor, "vendor_file.rb", "# vendor data" - assert_file "vendor/vendor_file.rb", "# vendor data" + assert_file "vendor/vendor_file.rb", "# vendor data\n" + end + + def test_vendor_should_write_data_to_file_with_block_in_vendor + code = <<-RUBY + puts "one" + puts "two" + puts "three" + RUBY + action(:vendor, "vendor_file.rb") { code } + assert_file "vendor/vendor_file.rb", code.strip_heredoc end def test_lib_should_write_data_to_file_in_lib action :lib, "my_library.rb", "class MyLibrary" - assert_file "lib/my_library.rb", "class MyLibrary" + assert_file "lib/my_library.rb", "class MyLibrary\n" + end + + def test_lib_should_write_data_to_file_with_block_in_lib + code = <<-RUBY + class MyLib + MY_CONSTANT = 123 + end + RUBY + action(:lib, "my_library.rb") { code } + assert_file "lib/my_library.rb", code.strip_heredoc end def test_rakefile_should_write_date_to_file_in_lib_tasks action :rakefile, "myapp.rake", "task run: [:environment]" - assert_file "lib/tasks/myapp.rake", "task run: [:environment]" + assert_file "lib/tasks/myapp.rake", "task run: [:environment]\n" + end + + def test_rakefile_should_write_date_to_file_with_block_in_lib_tasks + code = <<-RUBY + task rock: :environment do + puts "Rockin'" + end + RUBY + action(:rakefile, "myapp.rake") { code } + assert_file "lib/tasks/myapp.rake", code.strip_heredoc end def test_initializer_should_write_date_to_file_in_config_initializers action :initializer, "constants.rb", "MY_CONSTANT = 42" - assert_file "config/initializers/constants.rb", "MY_CONSTANT = 42" + assert_file "config/initializers/constants.rb", "MY_CONSTANT = 42\n" + end + + def test_initializer_should_write_date_to_file_with_block_in_config_initializers + code = <<-RUBY + MyLib.configure do |config| + config.value = 123 + end + RUBY + action(:initializer, "constants.rb") { code } + assert_file "config/initializers/constants.rb", code.strip_heredoc end def test_generate_should_run_script_generate_with_argument_and_options @@ -239,6 +308,14 @@ class ActionsTest < Rails::Generators::TestCase end end + test "rake command with capture option should run rake command with capture" do + assert_called_with(generator, :run, ["rake log:clear RAILS_ENV=development", verbose: false, capture: true]) do + with_rails_env nil do + action :rake, "log:clear", capture: true + end + end + end + test "rails command should run rails_command with default env" do assert_called_with(generator, :run, ["rails log:clear RAILS_ENV=development", verbose: false]) do with_rails_env nil do @@ -277,10 +354,21 @@ class ActionsTest < Rails::Generators::TestCase end end + test "rails command with capture option should run rails_command with capture" do + assert_called_with(generator, :run, ["rails log:clear RAILS_ENV=development", verbose: false, capture: true]) do + with_rails_env nil do + action :rails_command, "log:clear", capture: true + end + end + end + def test_capify_should_run_the_capify_command - assert_called_with(generator, :run, ["capify .", verbose: false]) do - action :capify! + content = capture(:stderr) do + assert_called_with(generator, :run, ["capify .", verbose: false]) do + action :capify! + end end + assert_match(/DEPRECATION WARNING: `capify!` is deprecated/, content) end def test_route_should_add_data_to_the_routes_block_in_config_routes diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index c54d9cc599..4815cf6362 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/app/app_generator" @@ -33,26 +35,26 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase def test_api_modified_files run_generator + assert_file ".gitignore" do |content| + assert_no_match(/\/public\/assets/, content) + end + assert_file "Gemfile" do |content| assert_no_match(/gem 'coffee-rails'/, content) assert_no_match(/gem 'sass-rails'/, content) assert_no_match(/gem 'web-console'/, content) + assert_no_match(/gem 'capybara'/, content) + assert_no_match(/gem 'selenium-webdriver'/, content) assert_match(/# gem 'jbuilder'/, content) + assert_match(/# gem 'rack-cors'/, content) end - assert_file "config/application.rb" do |content| - assert_match(/config.api_only = true/, content) - end - - assert_file "config/initializers/cors.rb" - - assert_file "config/initializers/wrap_parameters.rb" - + assert_file "config/application.rb", /config\.api_only = true/ assert_file "app/controllers/application_controller.rb", /ActionController::API/ end def test_generator_if_skip_action_cable_is_given - run_generator [destination_root, "--skip-action-cable"] + run_generator [destination_root, "--api", "--skip-action-cable"] assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/ assert_no_file "config/cable.yml" assert_no_file "app/channels" @@ -61,32 +63,74 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase end end - def test_generator_skips_per_form_csrf_token_and_origin_check_configs_for_api_apps + def test_app_update_does_not_generate_unnecessary_config_files run_generator - assert_file "config/initializers/new_framework_defaults.rb" do |initializer_content| - assert_no_match(/per_form_csrf_tokens/, initializer_content) - assert_no_match(/forgery_protection_origin_check/, initializer_content) - end + generator = Rails::Generators::AppGenerator.new ["rails"], + { api: true, update: true }, { destination_root: destination_root, shell: @shell } + quietly { generator.send(:update_config_files) } + + assert_no_file "config/initializers/cookies_serializer.rb" + assert_no_file "config/initializers/assets.rb" + assert_no_file "config/initializers/content_security_policy.rb" + end + + def test_app_update_does_not_generate_unnecessary_bin_files + run_generator + + generator = Rails::Generators::AppGenerator.new ["rails"], + { api: true, update: true }, { destination_root: destination_root, shell: @shell } + quietly { generator.send(:update_bin_files) } + + assert_no_file "bin/yarn" end private def default_files - files = %W( - .gitignore + %w(.gitignore + .ruby-version + README.md Gemfile Rakefile config.ru + app/channels app/controllers app/mailers app/models + app/views/layouts app/views/layouts/mailer.html.erb app/views/layouts/mailer.text.erb + bin/bundle + bin/rails + bin/rake + bin/setup + bin/update + config/application.rb + config/boot.rb + config/cable.yml + config/environment.rb config/environments + config/environments/development.rb + config/environments/production.rb + config/environments/test.rb config/initializers + config/initializers/application_controller_renderer.rb + config/initializers/backtrace_silencers.rb + config/initializers/cors.rb + config/initializers/filter_parameter_logging.rb + config/initializers/inflections.rb + config/initializers/mime_types.rb + config/initializers/wrap_parameters.rb config/locales + config/locales/en.yml + config/puma.rb + config/routes.rb + config/credentials.yml.enc + config/spring.rb + config/storage.yml db + db/seeds.rb lib lib/tasks log @@ -97,8 +141,6 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase tmp vendor ) - files.concat %w(bin/bundle bin/rails bin/rake) - files end def skipped_files @@ -108,6 +150,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase bin/yarn config/initializers/assets.rb config/initializers/cookies_serializer.rb + config/initializers/content_security_policy.rb lib/assets test/helpers tmp/cache/assets @@ -116,8 +159,8 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase public/500.html public/apple-touch-icon-precomposed.png public/apple-touch-icon.png - public/favicon.icon - vendor/package.json + public/favicon.ico + package.json ) end end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index e80c253ec9..fcb515c606 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -1,39 +1,83 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/app/app_generator" require "generators/shared_generator_tests" DEFAULT_APP_FILES = %w( .gitignore + .ruby-version README.md Gemfile Rakefile config.ru + app/assets/config/manifest.js + app/assets/images app/assets/javascripts + app/assets/javascripts/application.js + app/assets/javascripts/cable.js + app/assets/javascripts/channels app/assets/stylesheets - app/assets/images + app/assets/stylesheets/application.css + app/channels/application_cable/channel.rb + app/channels/application_cable/connection.rb app/controllers + app/controllers/application_controller.rb app/controllers/concerns app/helpers + app/helpers/application_helper.rb app/mailers + app/mailers/application_mailer.rb app/models + app/models/application_record.rb app/models/concerns app/jobs + app/jobs/application_job.rb app/views/layouts + app/views/layouts/application.html.erb + app/views/layouts/mailer.html.erb + app/views/layouts/mailer.text.erb bin/bundle bin/rails bin/rake bin/setup + bin/update + bin/yarn + config/application.rb + config/boot.rb + config/cable.yml + config/environment.rb config/environments + config/environments/development.rb + config/environments/production.rb + config/environments/test.rb config/initializers + config/initializers/application_controller_renderer.rb + config/initializers/assets.rb + config/initializers/backtrace_silencers.rb + config/initializers/cookies_serializer.rb + config/initializers/content_security_policy.rb + config/initializers/filter_parameter_logging.rb + config/initializers/inflections.rb + config/initializers/mime_types.rb + config/initializers/wrap_parameters.rb config/locales - config/cable.yml + config/locales/en.yml config/puma.rb + config/routes.rb + config/credentials.yml.enc config/spring.rb + config/storage.yml db + db/seeds.rb lib lib/tasks lib/assets log + package.json + public + storage + test/application_system_test_case.rb test/test_helper.rb test/fixtures test/fixtures/files @@ -42,10 +86,12 @@ DEFAULT_APP_FILES = %w( test/helpers test/mailers test/integration + test/system vendor tmp tmp/cache tmp/cache/assets + tmp/storage ) class AppGeneratorTest < Rails::Generators::TestCase @@ -59,6 +105,15 @@ class AppGeneratorTest < Rails::Generators::TestCase ::DEFAULT_APP_FILES end + def test_skip_bundle + assert_not_called(generator([destination_root], skip_bundle: true), :bundle_command) do + quietly { generator.invoke_all } + # skip_bundle is only about running bundle install, ensure the Gemfile is still + # generated. + assert_file "Gemfile" + end + end + def test_assets run_generator @@ -132,7 +187,7 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_rails_update_generates_correct_session_key + def test_app_update_generates_correct_session_key app_root = File.join(destination_root, "myapp") run_generator [app_root] @@ -155,67 +210,65 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_file "config/initializers/cors.rb" end - def test_rails_update_keep_the_cookie_serializer_if_it_is_already_configured - app_root = File.join(destination_root, "myapp") + def test_new_application_doesnt_need_defaults + assert_no_file "config/initializers/new_framework_defaults_5_2.rb" + end + + def test_new_application_load_defaults + app_root = File.join(destination_root, "myfirstapp") run_generator [app_root] + output = nil - stub_rails_application(app_root) do - generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell - generator.send(:app_const) - quietly { generator.send(:update_config_files) } - assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/) + Dir.chdir(app_root) do + output = `./bin/rails r "puts Rails.application.config.assets.unknown_asset_fallback"` end + + assert_equal "false\n", output end - def test_rails_update_set_the_cookie_serializer_to_marshal_if_it_is_not_already_configured + def test_app_update_keep_the_cookie_serializer_if_it_is_already_configured app_root = File.join(destination_root, "myapp") run_generator [app_root] - FileUtils.rm("#{app_root}/config/initializers/cookies_serializer.rb") - stub_rails_application(app_root) do generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell generator.send(:app_const) quietly { generator.send(:update_config_files) } - assert_file("#{app_root}/config/initializers/cookies_serializer.rb", - /Valid options are :json, :marshal, and :hybrid\.\nRails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/) + assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/) end end - def test_rails_update_dont_set_file_watcher + def test_app_update_set_the_cookie_serializer_to_marshal_if_it_is_not_already_configured app_root = File.join(destination_root, "myapp") run_generator [app_root] + FileUtils.rm("#{app_root}/config/initializers/cookies_serializer.rb") + stub_rails_application(app_root) do generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell generator.send(:app_const) quietly { generator.send(:update_config_files) } - assert_file "#{app_root}/config/environments/development.rb" do |content| - assert_match(/# config.file_watcher/, content) - end + assert_file("#{app_root}/config/initializers/cookies_serializer.rb", + /Valid options are :json, :marshal, and :hybrid\.\nRails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/) end end - def test_rails_update_does_not_create_new_framework_defaults_by_default + def test_app_update_create_new_framework_defaults app_root = File.join(destination_root, "myapp") run_generator [app_root] - FileUtils.rm("#{app_root}/config/initializers/new_framework_defaults.rb") + assert_no_file "#{app_root}/config/initializers/new_framework_defaults_5_2.rb" stub_rails_application(app_root) do - generator = Rails::Generators::AppGenerator.new ["rails"], { update: true }, destination_root: app_root, shell: @shell + generator = Rails::Generators::AppGenerator.new ["rails"], { update: true }, { destination_root: app_root, shell: @shell } generator.send(:app_const) quietly { generator.send(:update_config_files) } - assert_file "#{app_root}/config/initializers/new_framework_defaults.rb" do |content| - assert_match(/ActiveSupport\.halt_callback_chains_on_return_false = true/, content) - assert_match(/Rails\.application\.config.active_record\.belongs_to_required_by_default = false/, content) - assert_no_match(/Rails\.application\.config\.ssl_options/, content) - end + assert_file "#{app_root}/config/initializers/new_framework_defaults_5_2.rb" end end - def test_rails_update_does_not_create_rack_cors + def test_app_update_does_not_create_rack_cors app_root = File.join(destination_root, "myapp") run_generator [app_root] @@ -227,7 +280,7 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_rails_update_does_not_remove_rack_cors_if_already_present + def test_app_update_does_not_remove_rack_cors_if_already_present app_root = File.join(destination_root, "myapp") run_generator [app_root] @@ -241,6 +294,96 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_app_update_does_not_generate_action_cable_contents_when_skip_action_cable_is_given + app_root = File.join(destination_root, "myapp") + run_generator [app_root, "--skip-action-cable"] + + FileUtils.cd(app_root) do + quietly { system("bin/rails app:update") } + end + + assert_no_file "#{app_root}/config/cable.yml" + assert_file "#{app_root}/config/environments/production.rb" do |content| + assert_no_match(/config\.action_cable/, content) + end + end + + def test_active_storage_mini_magick_gem + run_generator + assert_file "Gemfile", /^# gem 'mini_magick'/ + end + + def test_active_storage_install + command_check = -> command, _ do + @binstub_called ||= 0 + case command + when "active_storage:install" + @binstub_called += 1 + assert_equal 1, @binstub_called, "active_storage:install expected to be called once, but was called #{@binstub_called} times" + end + end + + generator.stub :rails_command, command_check do + generator.stub :bundle_command, nil do + quietly { generator.invoke_all } + end + end + end + + def test_app_update_does_not_generate_active_storage_contents_when_skip_active_storage_is_given + app_root = File.join(destination_root, "myapp") + run_generator [app_root, "--skip-active-storage"] + + FileUtils.cd(app_root) do + quietly { system("bin/rails app:update") } + end + + assert_file "#{app_root}/config/environments/development.rb" do |content| + assert_no_match(/config\.active_storage/, content) + end + + assert_file "#{app_root}/config/environments/production.rb" do |content| + assert_no_match(/config\.active_storage/, content) + end + + assert_file "#{app_root}/config/environments/test.rb" do |content| + assert_no_match(/config\.active_storage/, content) + end + + assert_no_file "#{app_root}/config/storage.yml" + + assert_file "#{app_root}/Gemfile" do |content| + assert_no_match(/gem 'mini_magick'/, content) + end + end + + def test_app_update_does_not_generate_active_storage_contents_when_skip_active_record_is_given + app_root = File.join(destination_root, "myapp") + run_generator [app_root, "--skip-active-record"] + + FileUtils.cd(app_root) do + quietly { system("bin/rails app:update") } + end + + assert_file "#{app_root}/config/environments/development.rb" do |content| + assert_no_match(/config\.active_storage/, content) + end + + assert_file "#{app_root}/config/environments/production.rb" do |content| + assert_no_match(/config\.active_storage/, content) + end + + assert_file "#{app_root}/config/environments/test.rb" do |content| + assert_no_match(/config\.active_storage/, content) + end + + assert_no_file "#{app_root}/config/storage.yml" + + assert_file "#{app_root}/Gemfile" do |content| + assert_no_match(/gem 'mini_magick'/, content) + end + end + def test_application_names_are_not_singularized run_generator [File.join(destination_root, "hats")] assert_file "hats/config/environment.rb", /Rails\.application\.initialize!/ @@ -272,7 +415,7 @@ class AppGeneratorTest < Rails::Generators::TestCase if defined?(JRUBY_VERSION) assert_gem "activerecord-jdbcmysql-adapter" else - assert_gem "mysql2", "'>= 0.3.18', '< 0.5'" + assert_gem "mysql2", "'~> 0.4.4'" end end @@ -324,23 +467,9 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_generator_without_skips - run_generator - assert_file "config/application.rb", /\s+require\s+["']rails\/all["']/ - assert_file "config/environments/development.rb" do |content| - assert_match(/config\.action_mailer\.raise_delivery_errors = false/, content) - end - assert_file "config/environments/test.rb" do |content| - assert_match(/config\.action_mailer\.delivery_method = :test/, content) - end - assert_file "config/environments/production.rb" do |content| - assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content) - end - end - def test_generator_defaults_to_puma_version run_generator [destination_root] - assert_gem "puma", "'~> 3.0'" + assert_gem "puma", "'~> 3.11'" end def test_generator_if_skip_puma_is_given @@ -351,43 +480,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_generator_if_skip_active_record_is_given - run_generator [destination_root, "--skip-active-record"] - assert_no_directory "db/" - assert_no_file "config/database.yml" - assert_no_file "app/models/application_record.rb" - assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ - assert_file "test/test_helper.rb" do |helper_content| - assert_no_match(/fixtures :all/, helper_content) - end - assert_file "bin/setup" do |setup_content| - assert_no_match(/db:setup/, setup_content) - end - assert_file "bin/update" do |update_content| - assert_no_match(/db:migrate/, update_content) - end - - assert_file "config/initializers/new_framework_defaults.rb" do |initializer_content| - assert_no_match(/belongs_to_required_by_default/, initializer_content) - end - end - - def test_generator_if_skip_action_mailer_is_given - run_generator [destination_root, "--skip-action-mailer"] - assert_file "config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/ - assert_file "config/environments/development.rb" do |content| - assert_no_match(/config\.action_mailer/, content) - end - assert_file "config/environments/test.rb" do |content| - assert_no_match(/config\.action_mailer/, content) - end - assert_file "config/environments/production.rb" do |content| - assert_no_match(/config\.action_mailer/, content) - end - assert_no_directory "app/mailers" - assert_no_directory "test/mailers" - end - def test_generator_has_assets_gems run_generator @@ -395,59 +487,57 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_gem "uglifier" end - def test_generator_if_skip_sprockets_is_given - run_generator [destination_root, "--skip-sprockets"] - assert_no_file "config/initializers/assets.rb" - assert_file "config/application.rb" do |content| - assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content) - end - assert_file "Gemfile" do |content| - assert_no_match(/sass-rails/, content) - assert_no_match(/uglifier/, content) - assert_no_match(/coffee-rails/, content) - end - assert_file "config/environments/development.rb" do |content| - assert_no_match(/config\.assets\.debug = true/, content) - end - assert_file "config/environments/production.rb" do |content| - assert_no_match(/config\.assets\.digest = true/, content) - assert_no_match(/config\.assets\.js_compressor = :uglifier/, content) - assert_no_match(/config\.assets\.css_compressor = :sass/, content) - end - assert_file "config/initializers/new_framework_defaults.rb" do |content| - assert_no_match(/unknown_asset_fallback/, content) - end + def test_action_cable_redis_gems + run_generator + assert_file "Gemfile", /^# gem 'redis'/ end - def test_generator_if_skip_yarn_is_given - run_generator [destination_root, "--skip-yarn"] + def test_generator_if_skip_test_is_given + run_generator [destination_root, "--skip-test"] + + assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/ + + assert_file "Gemfile" do |content| + assert_no_match(/capybara/, content) + assert_no_match(/selenium-webdriver/, content) + assert_no_match(/chromedriver-helper/, content) + end - assert_no_file "vendor/package.json" - assert_no_file "bin/yarn" + assert_no_directory("test") end - def test_generator_if_skip_action_cable_is_given - run_generator [destination_root, "--skip-action-cable"] - assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/ - assert_no_file "config/cable.yml" - assert_no_file "app/assets/javascripts/cable.js" - assert_no_file "app/channels" + def test_generator_if_skip_system_test_is_given + run_generator [destination_root, "--skip-system-test"] assert_file "Gemfile" do |content| - assert_no_match(/redis/, content) + assert_no_match(/capybara/, content) + assert_no_match(/selenium-webdriver/, content) + assert_no_match(/chromedriver-helper/, content) end + + assert_directory("test") + + assert_no_directory("test/system") end - def test_action_cable_redis_gems - run_generator - assert_file "Gemfile", /^# gem 'redis'/ + def test_does_not_generate_system_test_files_if_skip_system_test_is_given + run_generator [destination_root, "--skip-system-test"] + + Dir.chdir(destination_root) do + quietly { `./bin/rails g scaffold User` } + + assert_no_file("test/application_system_test_case.rb") + assert_no_file("test/system/users_test.rb") + end end def test_inclusion_of_javascript_runtime run_generator if defined?(JRUBY_VERSION) assert_gem "therubyrhino" + elsif RUBY_PLATFORM =~ /mingw|mswin/ + assert_gem "duktape" else - assert_file "Gemfile", /# gem 'therubyracer', platforms: :ruby/ + assert_file "Gemfile", /# gem 'mini_racer', platforms: :ruby/ end end @@ -458,14 +548,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_inclusion_of_javascript_libraries_if_required - run_generator [destination_root, "-j", "jquery"] - assert_file "app/assets/javascripts/application.js" do |contents| - assert_match %r{^//= require jquery}, contents - end - assert_gem "jquery-rails" - end - def test_javascript_is_skipped_if_required run_generator [destination_root, "--skip-javascript"] @@ -495,26 +577,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_generator_for_yarn - run_generator([destination_root]) - assert_file "vendor/package.json", /dependencies/ - assert_file "config/initializers/assets.rb", /node_modules/ - end - - def test_generator_for_yarn_skipped - run_generator([destination_root, "--skip-yarn"]) - assert_no_file "vendor/package.json" - - assert_file "config/initializers/assets.rb" do |content| - assert_no_match(/node_modules/, content) - end - - assert_file ".gitignore" do |content| - assert_no_match(/vendor\/node_modules/, content) - assert_no_match(/vendor\/yarn-error\.log/, content) - end - end - def test_inclusion_of_jbuilder run_generator assert_gem "jbuilder" @@ -549,9 +611,9 @@ class AppGeneratorTest < Rails::Generators::TestCase run_generator assert_file "config/environments/development.rb" do |content| if RbConfig::CONFIG["host_os"] =~ /darwin|linux/ - assert_match(/^\s*config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) + assert_match(/^\s*config\.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) else - assert_match(/^\s*# config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) + assert_match(/^\s*# config\.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) end end end @@ -582,24 +644,17 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "lib/test_file.rb", "heres test data" end - def test_tests_are_removed_from_frameworks_if_skip_test_is_given - run_generator [destination_root, "--skip-test"] - assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/ - end - - def test_no_active_record_or_tests_if_skips_given - run_generator [destination_root, "--skip-test", "--skip-active-record"] - assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/ - assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ - assert_file "config/application.rb", /\s+require\s+["']active_job\/railtie["']/ - end - def test_pretend_option output = run_generator [File.join(destination_root, "myapp"), "--pretend"] assert_no_match(/run bundle install/, output) assert_no_match(/run git init/, output) end + def test_quiet_option + output = run_generator [File.join(destination_root, "myapp"), "--quiet"] + assert_empty output + end + def test_application_name_with_spaces path = File.join(destination_root, "foo bar") @@ -619,7 +674,7 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile" do |content| assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content) - assert_no_match(/\Agem 'web-console', '>= 3.3.0'\z/, content) + assert_no_match(/\Agem 'web-console', '>= 3\.3\.0'\z/, content) end end @@ -628,7 +683,7 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile" do |content| assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content) - assert_no_match(/\Agem 'web-console', '>= 3.3.0'\z/, content) + assert_no_match(/\Agem 'web-console', '>= 3\.3\.0'\z/, content) end end @@ -699,6 +754,45 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_webpack_option + command_check = -> command, *_ do + @called ||= 0 + if command == "webpacker:install" + @called += 1 + assert_equal 1, @called, "webpacker:install expected to be called once, but was called #{@called} times." + end + end + + generator([destination_root], webpack: "webpack").stub(:rails_command, command_check) do + generator.stub :bundle_command, nil do + quietly { generator.invoke_all } + end + end + + assert_gem "webpacker" + end + + def test_webpack_option_with_js_framework + command_check = -> command, *_ do + case command + when "webpacker:install" + @webpacker ||= 0 + @webpacker += 1 + assert_equal 1, @webpacker, "webpacker:install expected to be called once, but was called #{@webpacker} times." + when "webpacker:install:react" + @react ||= 0 + @react += 1 + assert_equal 1, @react, "webpacker:install:react expected to be called once, but was called #{@react} times." + end + end + + generator([destination_root], webpack: "react").stub(:rails_command, command_check) do + generator.stub :bundle_command, nil do + quietly { generator.invoke_all } + end + end + end + def test_generator_if_skip_turbolinks_is_given run_generator [destination_root, "--skip-turbolinks"] @@ -713,27 +807,45 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_gitignore_when_sqlite3 + def test_bootsnap run_generator - assert_file ".gitignore" do |content| - assert_match(/sqlite3/, content) + assert_gem "bootsnap" + assert_file "config/boot.rb" do |content| + assert_match(/require 'bootsnap\/setup'/, content) end end - def test_gitignore_when_no_active_record - run_generator [destination_root, "--skip-active-record"] + def test_skip_bootsnap + run_generator [destination_root, "--skip-bootsnap"] - assert_file ".gitignore" do |content| - assert_no_match(/sqlite/i, content) + assert_file "Gemfile" do |content| + assert_no_match(/bootsnap/, content) + end + assert_file "config/boot.rb" do |content| + assert_no_match(/require 'bootsnap\/setup'/, content) end end - def test_gitignore_when_non_sqlite3_db - run_generator([destination_root, "-d", "mysql"]) + def test_bootsnap_with_dev_option + run_generator [destination_root, "--dev"] - assert_file ".gitignore" do |content| - assert_no_match(/sqlite/i, content) + assert_file "Gemfile" do |content| + assert_no_match(/bootsnap/, content) + end + assert_file "config/boot.rb" do |content| + assert_no_match(/require 'bootsnap\/setup'/, content) + end + end + + def test_inclusion_of_ruby_version + run_generator + + assert_file "Gemfile" do |content| + assert_match(/ruby '#{RUBY_VERSION}'/, content) + end + assert_file ".ruby-version" do |content| + assert_match(/#{RUBY_VERSION}/, content) end end @@ -767,7 +879,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_psych_gem run_generator - gem_regex = /gem 'psych',\s+'~> 2.0',\s+platforms: :rbx/ + gem_regex = /gem 'psych',\s+'~> 2\.0',\s+platforms: :rbx/ assert_file "Gemfile" do |content| if defined?(Rubinius) @@ -780,7 +892,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_after_bundle_callback path = "http://example.org/rails_template" - template = %{ after_bundle { run 'echo ran after_bundle' } } + template = %{ after_bundle { run 'echo ran after_bundle' } }.dup template.instance_eval "def read; self; end" # Make the string respond to read check_open = -> *args do @@ -788,9 +900,9 @@ class AppGeneratorTest < Rails::Generators::TestCase template end - sequence = ["git init", "install", "exec spring binstub --all", "echo ran after_bundle"] + sequence = ["git init", "install", "exec spring binstub --all", "active_storage:install", "echo ran after_bundle"] @sequence_step ||= 0 - ensure_bundler_first = -> command do + ensure_bundler_first = -> command, options = nil do assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}" @sequence_step += 1 end @@ -798,16 +910,32 @@ class AppGeneratorTest < Rails::Generators::TestCase generator([destination_root], template: path).stub(:open, check_open, template) do generator.stub(:bundle_command, ensure_bundler_first) do generator.stub(:run, ensure_bundler_first) do - quietly { generator.invoke_all } + generator.stub(:rails_command, ensure_bundler_first) do + quietly { generator.invoke_all } + end end end end - assert_equal 4, @sequence_step + assert_equal 5, @sequence_step end - private + def test_gitignore + run_generator + + assert_file ".gitignore" do |content| + assert_match(/config\/master\.key/, content) + end + end + + def test_system_tests_directory_generated + run_generator + assert_file("test/system/.keep") + assert_directory("test/system") + end + + private def stub_rails_application(root) Rails.application.config.root = root Rails.application.class.stub(:name, "Myapp") do @@ -832,7 +960,7 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_gem "spring-watcher-listen" assert_file "config/environments/development.rb" do |content| - assert_match(/^\s*config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) + assert_match(/^\s*config\.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) end end @@ -842,7 +970,7 @@ class AppGeneratorTest < Rails::Generators::TestCase end assert_file "config/environments/development.rb" do |content| - assert_match(/^\s*# config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) + assert_match(/^\s*# config\.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) end end diff --git a/railties/test/generators/application_record_generator_test.rb b/railties/test/generators/application_record_generator_test.rb new file mode 100644 index 0000000000..2c0aa7211b --- /dev/null +++ b/railties/test/generators/application_record_generator_test.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "generators/generators_test_helper" +require "rails/generators/rails/application_record/application_record_generator" + +class ApplicationRecordGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + + def test_application_record_skeleton_is_created + run_generator + assert_file "app/models/application_record.rb" do |record| + assert_match(/class ApplicationRecord < ActiveRecord::Base/, record) + assert_match(/self\.abstract_class = true/, record) + end + end +end diff --git a/railties/test/generators/argv_scrubber_test.rb b/railties/test/generators/argv_scrubber_test.rb index 7f4295a20f..9ef61dc978 100644 --- a/railties/test/generators/argv_scrubber_test.rb +++ b/railties/test/generators/argv_scrubber_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/test_case" require "active_support/testing/autorun" require "rails/generators/rails/app/app_generator" @@ -80,9 +82,8 @@ module Rails file.puts "--hello --world" file.flush - message = nil scrubber = Class.new(ARGVScrubber) { - define_method(:puts) { |msg| message = msg } + define_method(:puts) { |msg| } }.new ["new", "--rc=#{file.path}"] args = scrubber.prepare! assert_equal ["--hello", "--world"], args diff --git a/railties/test/generators/assets_generator_test.rb b/railties/test/generators/assets_generator_test.rb index 1c02c67e42..3cec41dbf8 100644 --- a/railties/test/generators/assets_generator_test.rb +++ b/railties/test/generators/assets_generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/assets/assets_generator" diff --git a/railties/test/generators/channel_generator_test.rb b/railties/test/generators/channel_generator_test.rb index a1d54200ba..e238197eba 100644 --- a/railties/test/generators/channel_generator_test.rb +++ b/railties/test/generators/channel_generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/channel/channel_generator" @@ -25,7 +27,7 @@ class ChannelGeneratorTest < Rails::Generators::TestCase end assert_file "app/assets/javascripts/channels/chat.js" do |channel| - assert_match(/App.chat = App.cable.subscriptions.create\("ChatChannel/, channel) + assert_match(/App\.chat = App\.cable\.subscriptions\.create\("ChatChannel/, channel) end end @@ -39,7 +41,7 @@ class ChannelGeneratorTest < Rails::Generators::TestCase end assert_file "app/assets/javascripts/channels/chat.js" do |channel| - assert_match(/App.chat = App.cable.subscriptions.create\("ChatChannel/, channel) + assert_match(/App\.chat = App\.cable\.subscriptions\.create\("ChatChannel/, channel) assert_match(/,\n\n speak/, channel) assert_match(/,\n\n mute: function\(\) \{\n return this\.perform\('mute'\);\n \}\n\}\);/, channel) end diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb index af86a0136f..a3218951a6 100644 --- a/railties/test/generators/controller_generator_test.rb +++ b/railties/test/generators/controller_generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/controller/controller_generator" @@ -100,4 +102,11 @@ class ControllerGeneratorTest < Rails::Generators::TestCase assert_match(/^ namespace :admin do\n get 'dashboard\/index'\n end$/, route) end end + + def test_namespaced_routes_with_multiple_actions_are_created_in_routes + run_generator ["admin/dashboard", "index", "show"] + assert_file "config/routes.rb" do |route| + assert_match(/^ namespace :admin do\n get 'dashboard\/index'\n get 'dashboard\/show'\n end$/, route) + end + end end diff --git a/railties/test/generators/create_migration_test.rb b/railties/test/generators/create_migration_test.rb index ddd40e4d02..3cb7fd6baa 100644 --- a/railties/test/generators/create_migration_test.rb +++ b/railties/test/generators/create_migration_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/migration/migration_generator" @@ -46,12 +48,12 @@ class CreateMigrationTest < Rails::Generators::TestCase def test_invoke create_migration - assert_match(/create db\/migrate\/1_create_articles.rb\n/, invoke!) + assert_match(/create db\/migrate\/1_create_articles\.rb\n/, invoke!) assert_file @migration.destination end def test_invoke_pretended - create_migration(default_destination_path, {}, pretend: true) + create_migration(default_destination_path, {}, { pretend: true }) assert_no_file @migration.destination end @@ -67,7 +69,7 @@ class CreateMigrationTest < Rails::Generators::TestCase migration_exists! create_migration - assert_match(/identical db\/migrate\/1_create_articles.rb\n/, invoke!) + assert_match(/identical db\/migrate\/1_create_articles\.rb\n/, invoke!) assert @migration.identical? end @@ -84,29 +86,29 @@ class CreateMigrationTest < Rails::Generators::TestCase create_migration(dest, force: true) { "different content" } stdout = invoke! - assert_match(/remove db\/migrate\/1_migration.rb\n/, stdout) - assert_match(/create db\/migrate\/2_migration.rb\n/, stdout) + assert_match(/remove db\/migrate\/1_migration\.rb\n/, stdout) + assert_match(/create db\/migrate\/2_migration\.rb\n/, stdout) assert_file @migration.destination assert_no_file @existing_migration.destination end def test_invoke_forced_pretended_when_exists_not_identical migration_exists! - create_migration(default_destination_path, { force: true }, pretend: true) do + create_migration(default_destination_path, { force: true }, { pretend: true }) do "different content" end stdout = invoke! - assert_match(/remove db\/migrate\/1_create_articles.rb\n/, stdout) - assert_match(/create db\/migrate\/2_create_articles.rb\n/, stdout) + assert_match(/remove db\/migrate\/1_create_articles\.rb\n/, stdout) + assert_match(/create db\/migrate\/2_create_articles\.rb\n/, stdout) assert_no_file @migration.destination end def test_invoke_skipped_when_exists_not_identical migration_exists! - create_migration(default_destination_path, {}, skip: true) { "different content" } + create_migration(default_destination_path, {}, { skip: true }) { "different content" } - assert_match(/skip db\/migrate\/2_create_articles.rb\n/, invoke!) + assert_match(/skip db\/migrate\/2_create_articles\.rb\n/, invoke!) assert_no_file @migration.destination end @@ -114,21 +116,21 @@ class CreateMigrationTest < Rails::Generators::TestCase migration_exists! create_migration - assert_match(/remove db\/migrate\/1_create_articles.rb\n/, revoke!) + assert_match(/remove db\/migrate\/1_create_articles\.rb\n/, revoke!) assert_no_file @existing_migration.destination end def test_revoke_pretended migration_exists! - create_migration(default_destination_path, {}, pretend: true) + create_migration(default_destination_path, {}, { pretend: true }) - assert_match(/remove db\/migrate\/1_create_articles.rb\n/, revoke!) + assert_match(/remove db\/migrate\/1_create_articles\.rb\n/, revoke!) assert_file @existing_migration.destination end def test_revoke_when_no_exists create_migration - assert_match(/remove db\/migrate\/1_create_articles.rb\n/, revoke!) + assert_match(/remove db\/migrate\/1_create_articles\.rb\n/, revoke!) end end diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb index 97847c8624..c6f7bdeda1 100644 --- a/railties/test/generators/generated_attribute_test.rb +++ b/railties/test/generators/generated_attribute_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/generated_attribute" diff --git a/railties/test/generators/generator_generator_test.rb b/railties/test/generators/generator_generator_test.rb index 5ff8bb0357..eaa964cabc 100644 --- a/railties/test/generators/generator_generator_test.rb +++ b/railties/test/generators/generator_generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/generator/generator_generator" diff --git a/railties/test/generators/generator_test.rb b/railties/test/generators/generator_test.rb index 904bade658..5f7daf5ac3 100644 --- a/railties/test/generators/generator_test.rb +++ b/railties/test/generators/generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/test_case" require "active_support/testing/autorun" require "rails/generators/app_base" @@ -88,12 +90,12 @@ module Rails specifier_for = -> v { generator.send(:rails_version_specifier, Gem::Version.new(v)) } assert_equal "~> 4.1.13", specifier_for["4.1.13"] - assert_equal [">= 4.1.6.rc1", "< 4.2"], specifier_for["4.1.6.rc1"] + assert_equal "~> 4.1.6.rc1", specifier_for["4.1.6.rc1"] assert_equal ["~> 4.1.7", ">= 4.1.7.1"], specifier_for["4.1.7.1"] assert_equal ["~> 4.1.7", ">= 4.1.7.1.2"], specifier_for["4.1.7.1.2"] - assert_equal [">= 4.1.7.1.rc2", "< 4.2"], specifier_for["4.1.7.1.rc2"] - assert_equal [">= 4.2.0.beta1", "< 4.3"], specifier_for["4.2.0.beta1"] - assert_equal [">= 5.0.0.beta1", "< 5.1"], specifier_for["5.0.0.beta1"] + assert_equal ["~> 4.1.7", ">= 4.1.7.1.rc2"], specifier_for["4.1.7.1.rc2"] + assert_equal "~> 4.2.0.beta1", specifier_for["4.2.0.beta1"] + assert_equal "~> 5.0.0.beta1", specifier_for["5.0.0.beta1"] end end end diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index 2cdddc8713..ad2a55f496 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_support/core_ext/module/remove_method" require "active_support/testing/stream" @@ -9,7 +11,7 @@ module Rails class << self remove_possible_method :root def root - @root ||= Pathname.new(File.expand_path("../../fixtures", __FILE__)) + @root ||= Pathname.new(File.expand_path("../fixtures", __dir__)) end end end @@ -41,9 +43,9 @@ module GeneratorsTestHelper end def copy_routes - routes = File.expand_path("../../../lib/rails/generators/rails/app/templates/config/routes.rb", __FILE__) + routes = File.expand_path("../../lib/rails/generators/rails/app/templates/config/routes.rb.tt", __dir__) destination = File.join(destination_root, "config") FileUtils.mkdir_p(destination) - FileUtils.cp routes, destination + FileUtils.cp routes, File.join(destination, "routes.rb") end end diff --git a/railties/test/generators/helper_generator_test.rb b/railties/test/generators/helper_generator_test.rb index d9e6e0a85a..4cdb6adf82 100644 --- a/railties/test/generators/helper_generator_test.rb +++ b/railties/test/generators/helper_generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/helper/helper_generator" diff --git a/railties/test/generators/integration_test_generator_test.rb b/railties/test/generators/integration_test_generator_test.rb index 8bcc02440a..82791f1a27 100644 --- a/railties/test/generators/integration_test_generator_test.rb +++ b/railties/test/generators/integration_test_generator_test.rb @@ -1,12 +1,18 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/integration_test/integration_test_generator" class IntegrationTestGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper - arguments %w(integration) def test_integration_test_skeleton_is_created - run_generator + run_generator %w(integration) assert_file "test/integration/integration_test.rb", /class IntegrationTest < ActionDispatch::IntegrationTest/ end + + def test_namespaced_integration_test_skeleton_is_created + run_generator %w(iguchi/integration) + assert_file "test/integration/iguchi/integration_test.rb", /class Iguchi::IntegrationTest < ActionDispatch::IntegrationTest/ + end end diff --git a/railties/test/generators/job_generator_test.rb b/railties/test/generators/job_generator_test.rb index 68d158eb39..13276fac65 100644 --- a/railties/test/generators/job_generator_test.rb +++ b/railties/test/generators/job_generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/job/job_generator" diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb index 7d69d7470d..ddac6e1a1e 100644 --- a/railties/test/generators/mailer_generator_test.rb +++ b/railties/test/generators/mailer_generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/mailer/mailer_generator" @@ -9,13 +11,13 @@ class MailerGeneratorTest < Rails::Generators::TestCase run_generator assert_file "app/mailers/notifier_mailer.rb" do |mailer| assert_match(/class NotifierMailer < ApplicationMailer/, mailer) - assert_no_match(/default from: "from@example.com"/, mailer) + assert_no_match(/default from: "from@example\.com"/, mailer) assert_no_match(/layout :mailer_notifier/, mailer) end assert_file "app/mailers/application_mailer.rb" do |mailer| assert_match(/class ApplicationMailer < ActionMailer::Base/, mailer) - assert_match(/default from: 'from@example.com'/, mailer) + assert_match(/default from: 'from@example\.com'/, mailer) assert_match(/layout 'mailer'/, mailer) end end @@ -48,11 +50,11 @@ class MailerGeneratorTest < Rails::Generators::TestCase assert_match(/class NotifierMailerPreview < ActionMailer::Preview/, preview) assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/notifier_mailer\/foo/, preview) assert_instance_method :foo, preview do |foo| - assert_match(/NotifierMailer.foo/, foo) + assert_match(/NotifierMailer\.foo/, foo) end assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/notifier_mailer\/bar/, preview) assert_instance_method :bar, preview do |bar| - assert_match(/NotifierMailer.bar/, bar) + assert_match(/NotifierMailer\.bar/, bar) end end end @@ -137,12 +139,12 @@ class MailerGeneratorTest < Rails::Generators::TestCase assert_file "app/mailers/notifier_mailer.rb" do |mailer| assert_instance_method :foo, mailer do |foo| - assert_match(/mail to: "to@example.org"/, foo) + assert_match(/mail to: "to@example\.org"/, foo) assert_match(/@greeting = "Hi"/, foo) end assert_instance_method :bar, mailer do |bar| - assert_match(/mail to: "to@example.org"/, bar) + assert_match(/mail to: "to@example\.org"/, bar) assert_match(/@greeting = "Hi"/, bar) end end diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index f46278cefe..88a939a55a 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/migration/migration_generator" @@ -48,6 +50,17 @@ class MigrationGeneratorTest < Rails::Generators::TestCase end end + def test_add_migration_with_table_having_from_in_title + migration = "add_email_address_to_blacklisted_from_campaign" + run_generator [migration, "email_address:string"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/add_column :blacklisted_from_campaigns, :email_address, :string/, change) + end + end + end + def test_remove_migration_with_indexed_attribute migration = "remove_title_body_from_posts" run_generator [migration, "title:string:index", "body:text"] @@ -73,6 +86,17 @@ class MigrationGeneratorTest < Rails::Generators::TestCase end end + def test_remove_migration_with_table_having_to_in_title + migration = "remove_email_address_from_sent_to_user" + run_generator [migration, "email_address:string"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/remove_column :sent_to_users, :email_address, :string/, change) + end + end + end + def test_remove_migration_with_references_options migration = "remove_references_from_books" run_generator [migration, "author:belongs_to", "distributor:references{polymorphic}"] @@ -204,8 +228,8 @@ class MigrationGeneratorTest < Rails::Generators::TestCase assert_migration "db/migrate/#{migration}.rb" do |content| assert_method :change, content do |change| assert_match(/create_join_table :artists, :musics/, change) - assert_match(/# t.index \[:artist_id, :music_id\]/, change) - assert_match(/ t.index \[:music_id, :artist_id\], unique: true/, change) + assert_match(/# t\.index \[:artist_id, :music_id\]/, change) + assert_match(/ t\.index \[:music_id, :artist_id\], unique: true/, change) end end end @@ -265,8 +289,8 @@ class MigrationGeneratorTest < Rails::Generators::TestCase assert_migration "db/migrate/#{migration}.rb" do |content| assert_method :change, content do |change| assert_match(/create_join_table :artist, :music/, change) - assert_match(/# t.index \[:artist_id, :music_id\]/, change) - assert_match(/ t.index \[:music_id, :artist_id\], unique: true/, change) + assert_match(/# t\.index \[:artist_id, :music_id\]/, change) + assert_match(/ t\.index \[:music_id, :artist_id\], unique: true/, change) end end end diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index 99490af3a9..516aa0704f 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/model/model_generator" require "active_support/core_ext/string/strip" @@ -6,14 +8,6 @@ class ModelGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper arguments %w(Account name:string age:integer) - def test_application_record_skeleton_is_created - run_generator - assert_file "app/models/application_record.rb" do |record| - assert_match(/class ApplicationRecord < ActiveRecord::Base/, record) - assert_match(/self.abstract_class = true/, record) - end - end - def test_help_shows_invoked_generators_options content = run_generator ["--help"] assert_match(/ActiveRecord options:/, content) @@ -43,17 +37,6 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_no_migration "db/migrate/create_accounts.rb" end - def test_model_with_existent_application_record - mkdir_p "#{destination_root}/app/models" - touch "#{destination_root}/app/models/application_record.rb" - - Dir.chdir(destination_root) do - run_generator ["account"] - end - - assert_file "app/models/account.rb", /class Account < ApplicationRecord/ - end - def test_plural_names_are_singularized content = run_generator ["accounts".freeze] assert_file "app/models/account.rb", /class Account < ApplicationRecord/ @@ -253,7 +236,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase def test_migration_with_timestamps run_generator - assert_migration "db/migrate/create_accounts.rb", /t.timestamps/ + assert_migration "db/migrate/create_accounts.rb", /t\.timestamps/ end def test_migration_timestamps_are_skipped @@ -261,7 +244,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_migration "db/migrate/create_accounts.rb" do |m| assert_method :change, m do |up| - assert_no_match(/t.timestamps/, up) + assert_no_match(/t\.timestamps/, up) end end end @@ -269,19 +252,19 @@ class ModelGeneratorTest < Rails::Generators::TestCase def test_migration_is_skipped_with_skip_option run_generator output = run_generator ["Account", "--skip"] - assert_match %r{skip\s+db/migrate/\d+_create_accounts.rb}, output + assert_match %r{skip\s+db/migrate/\d+_create_accounts\.rb}, output end def test_migration_is_ignored_as_identical_with_skip_option run_generator ["Account"] output = run_generator ["Account", "--skip"] - assert_match %r{identical\s+db/migrate/\d+_create_accounts.rb}, output + assert_match %r{identical\s+db/migrate/\d+_create_accounts\.rb}, output end def test_migration_is_skipped_on_skip_behavior run_generator output = run_generator ["Account"], behavior: :skip - assert_match %r{skip\s+db/migrate/\d+_create_accounts.rb}, output + assert_match %r{skip\s+db/migrate/\d+_create_accounts\.rb}, output end def test_migration_error_is_not_shown_on_revoke diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb index 3015b5363b..4e61b660d7 100644 --- a/railties/test/generators/named_base_test.rb +++ b/railties/test/generators/named_base_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/scaffold_controller/scaffold_controller_generator" @@ -31,6 +33,17 @@ class NamedBaseTest < Rails::Generators::TestCase assert_name g, "foos", :plural_name assert_name g, "admin.foo", :i18n_scope assert_name g, "admin_foos", :table_name + assert_name g, "admin/foos", :controller_name + assert_name g, %w(admin), :controller_class_path + assert_name g, "Admin::Foos", :controller_class_name + assert_name g, "admin/foos", :controller_file_path + assert_name g, "foos", :controller_file_name + assert_name g, "admin.foos", :controller_i18n_scope + assert_name g, "admin_foo", :singular_route_name + assert_name g, "admin_foos", :plural_route_name + assert_name g, "@admin_foo", :redirect_resource_name + assert_name g, "admin_foo", :model_resource_name + assert_name g, "admin_foos", :index_helper end def test_named_generator_attributes_as_ruby @@ -45,6 +58,17 @@ class NamedBaseTest < Rails::Generators::TestCase assert_name g, "foos", :plural_name assert_name g, "admin.foo", :i18n_scope assert_name g, "admin_foos", :table_name + assert_name g, "Admin::Foos", :controller_name + assert_name g, %w(admin), :controller_class_path + assert_name g, "Admin::Foos", :controller_class_name + assert_name g, "admin/foos", :controller_file_path + assert_name g, "foos", :controller_file_name + assert_name g, "admin.foos", :controller_i18n_scope + assert_name g, "admin_foo", :singular_route_name + assert_name g, "admin_foos", :plural_route_name + assert_name g, "@admin_foo", :redirect_resource_name + assert_name g, "admin_foo", :model_resource_name + assert_name g, "admin_foos", :index_helper end def test_named_generator_attributes_without_pluralized @@ -57,7 +81,7 @@ class NamedBaseTest < Rails::Generators::TestCase ActiveRecord::Base.pluralize_table_names = original_pluralize_table_names end - def test_scaffold_plural_names + def test_namespaced_scaffold_plural_names g = generator ["admin/foo"] assert_name g, "admin/foos", :controller_name assert_name g, %w(admin), :controller_class_path @@ -67,7 +91,7 @@ class NamedBaseTest < Rails::Generators::TestCase assert_name g, "admin.foos", :controller_i18n_scope end - def test_scaffold_plural_names_as_ruby + def test_namespaced_scaffold_plural_names_as_ruby g = generator ["Admin::Foo"] assert_name g, "Admin::Foos", :controller_name assert_name g, %w(admin), :controller_class_path @@ -129,6 +153,19 @@ class NamedBaseTest < Rails::Generators::TestCase assert_name g, "admin/foos", :controller_file_path assert_name g, "foos", :controller_file_name assert_name g, "admin.foos", :controller_i18n_scope + assert_name g, "admin_user", :singular_route_name + assert_name g, "admin_users", :plural_route_name + assert_name g, "[:admin, @user]", :redirect_resource_name + assert_name g, "[:admin, user]", :model_resource_name + assert_name g, "admin_users", :index_helper + end + + def test_scaffold_plural_names + g = generator ["User"] + assert_name g, "@user", :redirect_resource_name + assert_name g, "user", :model_resource_name + assert_name g, "user", :singular_route_name + assert_name g, "users", :plural_route_name end private diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index 1caabbe6b1..4b75a31f17 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -1,8 +1,11 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/controller/controller_generator" require "rails/generators/rails/model/model_generator" require "rails/generators/mailer/mailer_generator" require "rails/generators/rails/scaffold/scaffold_generator" +require "rails/generators/rails/application_record/application_record_generator" class NamespacedGeneratorTestCase < Rails::Generators::TestCase include GeneratorsTestHelper @@ -149,7 +152,7 @@ class NamespacedMailerGeneratorTest < NamespacedGeneratorTestCase assert_file "app/mailers/test_app/notifier_mailer.rb" do |mailer| assert_match(/module TestApp/, mailer) assert_match(/class NotifierMailer < ApplicationMailer/, mailer) - assert_no_match(/default from: "from@example.com"/, mailer) + assert_no_match(/default from: "from@example\.com"/, mailer) end end @@ -421,3 +424,13 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase /module TestApp\n class Admin::RolesControllerTest < ActionDispatch::IntegrationTest/ end end + +class NamespacedApplicationRecordGeneratorTest < NamespacedGeneratorTestCase + include GeneratorsTestHelper + tests Rails::Generators::ApplicationRecordGenerator + + def test_adds_namespace_to_application_record + run_generator + assert_file "app/models/test_app/application_record.rb", /module TestApp/, / class ApplicationRecord < ActiveRecord::Base/ + end +end diff --git a/railties/test/generators/orm_test.rb b/railties/test/generators/orm_test.rb index 88ae930554..6eaf2fbfd3 100644 --- a/railties/test/generators/orm_test.rb +++ b/railties/test/generators/orm_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/scaffold_controller/scaffold_controller_generator" diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index ddfbc1a698..fc7584c175 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -1,6 +1,9 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/plugin/plugin_generator" require "generators/shared_generator_tests" +require "rails/engine/updater" DEFAULT_PLUGIN_FILES = %w( .gitignore @@ -23,6 +26,10 @@ class PluginGeneratorTest < Rails::Generators::TestCase destination File.join(Rails.root, "tmp/bukkits") arguments [destination_root] + def application_path + "#{destination_root}/test/dummy" + end + # brings setup, teardown, and some tests include SharedGeneratorTests @@ -47,7 +54,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase run_generator [File.join(destination_root, "hyphenated-name")] assert_no_file "hyphenated-name/lib/hyphenated-name.rb" assert_no_file "hyphenated-name/lib/hyphenated_name.rb" - assert_file "hyphenated-name/lib/hyphenated/name.rb", /module Hyphenated\n module Name\n # Your code goes here...\n end\nend/ + assert_file "hyphenated-name/lib/hyphenated/name.rb", /module Hyphenated\n module Name\n # Your code goes here\.\.\.\n end\nend/ end def test_correct_file_in_lib_folder_of_camelcase_plugin_name @@ -62,16 +69,30 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_no_file "config/routes.rb" assert_no_file "app/assets/config/bukkits_manifest.js" assert_file "test/test_helper.rb" do |content| - assert_match(/require.+test\/dummy\/config\/environment/, content) + assert_match(/require_relative.+test\/dummy\/config\/environment/, content) assert_match(/ActiveRecord::Migrator\.migrations_paths.+test\/dummy\/db\/migrate/, content) assert_match(/Minitest\.backtrace_filter = Minitest::BacktraceFilter\.new/, content) assert_match(/Rails::TestUnitReporter\.executable = 'bin\/test'/, content) end + assert_file "lib/bukkits/railtie.rb", /module Bukkits\n class Railtie < ::Rails::Railtie\n end\nend/ + assert_file "lib/bukkits.rb", /require "bukkits\/railtie"/ assert_file "test/bukkits_test.rb", /assert_kind_of Module, Bukkits/ assert_file "bin/test" assert_no_file "bin/rails" end + def test_generating_in_full_mode_with_almost_of_all_skip_options + run_generator [destination_root, "--full", "-M", "-O", "-C", "-S", "-T"] + assert_file "bin/rails" do |content| + assert_no_match(/\s+require\s+["']rails\/all["']/, content) + end + assert_file "bin/rails", /#\s+require\s+["']active_record\/railtie["']/ + assert_file "bin/rails", /#\s+require\s+["']action_mailer\/railtie["']/ + assert_file "bin/rails", /#\s+require\s+["']action_cable\/engine["']/ + assert_file "bin/rails", /#\s+require\s+["']sprockets\/railtie["']/ + assert_file "bin/rails", /#\s+require\s+["']rails\/test_unit\/railtie["']/ + end + def test_generating_test_files_in_full_mode run_generator [destination_root, "--full"] assert_directory "test/integration/" @@ -79,6 +100,11 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "test/integration/navigation_test.rb", /ActionDispatch::IntegrationTest/ end + def test_inclusion_of_git_source + run_generator [destination_root] + assert_file "Gemfile", /git_source/ + end + def test_inclusion_of_a_debugger run_generator [destination_root, "--full"] if defined?(JRUBY_VERSION) || RUBY_ENGINE == "rbx" @@ -94,23 +120,19 @@ class PluginGeneratorTest < Rails::Generators::TestCase run_generator [destination_root, "-T", "--full"] assert_no_directory "test/integration/" - assert_no_file "test" + assert_no_directory "test" assert_file "Rakefile" do |contents| assert_no_match(/APP_RAKEFILE/, contents) end - end - - def test_generating_adds_dummy_app_in_full_mode_without_sprockets - run_generator [destination_root, "-S", "--full"] - - assert_file "test/dummy/config/environments/production.rb" do |contents| - assert_no_match(/config\.assets/, contents) + assert_file "bin/rails" do |contents| + assert_no_match(/APP_PATH/, contents) end end def test_generating_adds_dummy_app_rake_tasks_without_unit_test_files run_generator [destination_root, "-T", "--mountable", "--dummy-path", "my_dummy_app"] assert_file "Rakefile", /APP_RAKEFILE/ + assert_file "bin/rails", /APP_PATH/ end def test_generating_adds_dummy_app_without_javascript_and_assets_deps @@ -131,8 +153,8 @@ class PluginGeneratorTest < Rails::Generators::TestCase def test_ensure_that_test_dummy_can_be_generated_from_a_template FileUtils.cd(Rails.root) run_generator([destination_root, "-m", "lib/create_test_dummy_template.rb", "--skip-test"]) - assert_file "spec/dummy" - assert_no_file "test" + assert_directory "spec/dummy" + assert_no_directory "test" end def test_database_entry_is_generated_for_sqlite3_by_default_in_full_mode @@ -151,51 +173,17 @@ class PluginGeneratorTest < Rails::Generators::TestCase run_generator [destination_root, "--skip-active-record"] assert_file "bukkits.gemspec" do |contents| - assert_no_match(/s.add_development_dependency "sqlite3"/, contents) - end - end - - def test_app_generator_without_skips - run_generator - assert_file "test/dummy/config/application.rb", /\s+require\s+["']rails\/all["']/ - assert_file "test/dummy/config/environments/development.rb" do |content| - assert_match(/config\.action_mailer\.raise_delivery_errors = false/, content) - end - assert_file "test/dummy/config/environments/test.rb" do |content| - assert_match(/config\.action_mailer\.delivery_method = :test/, content) - end - assert_file "test/dummy/config/environments/production.rb" do |content| - assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content) + assert_no_match(/s\.add_development_dependency "sqlite3"/, contents) end end - def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given - run_generator [destination_root, "--skip-active-record"] - assert_file "test/dummy/config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ - end - def test_ensure_that_skip_active_record_option_is_passed_to_app_generator run_generator [destination_root, "--skip_active_record"] - assert_no_file "test/dummy/config/database.yml" assert_file "test/test_helper.rb" do |contents| assert_no_match(/ActiveRecord/, contents) end end - def test_action_mailer_is_removed_from_frameworks_if_skip_action_mailer_is_given - run_generator [destination_root, "--skip-action-mailer"] - assert_file "test/dummy/config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/ - assert_file "test/dummy/config/environments/development.rb" do |content| - assert_no_match(/config\.action_mailer/, content) - end - assert_file "test/dummy/config/environments/test.rb" do |content| - assert_no_match(/config\.action_mailer/, content) - end - assert_file "test/dummy/config/environments/production.rb" do |content| - assert_no_match(/config\.action_mailer/, content) - end - end - def test_ensure_that_database_option_is_passed_to_app_generator run_generator [destination_root, "--database", "postgresql"] assert_file "test/dummy/config/database.yml", /postgres/ @@ -273,7 +261,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "app/views" assert_file "app/helpers" assert_file "app/mailers" - assert_file "bin/rails" + assert_file "bin/rails", /\s+require\s+["']rails\/all["']/ assert_file "config/routes.rb", /Rails.application.routes.draw do/ assert_file "lib/bukkits/engine.rb", /module Bukkits\n class Engine < ::Rails::Engine\n end\nend/ assert_file "lib/bukkits.rb", /require "bukkits\/engine"/ @@ -293,7 +281,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "hyphenated-name/config/routes.rb", /Rails.application.routes.draw do/ assert_file "hyphenated-name/lib/hyphenated/name/engine.rb", /module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n end\n end\nend/ assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/ - assert_file "hyphenated-name/bin/rails", /\.\.\/\.\.\/lib\/hyphenated\/name\/engine/ + assert_file "hyphenated-name/bin/rails", /\.\.\/lib\/hyphenated\/name\/engine/ end def test_creating_engine_with_hyphenated_and_underscored_name_in_full_mode @@ -307,14 +295,14 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "my_hyphenated-name/app/helpers" assert_file "my_hyphenated-name/app/mailers" assert_file "my_hyphenated-name/bin/rails" - assert_file "my_hyphenated-name/config/routes.rb", /Rails.application.routes.draw do/ + assert_file "my_hyphenated-name/config/routes.rb", /Rails\.application\.routes\.draw do/ assert_file "my_hyphenated-name/lib/my_hyphenated/name/engine.rb", /module MyHyphenated\n module Name\n class Engine < ::Rails::Engine\n end\n end\nend/ assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/ - assert_file "my_hyphenated-name/bin/rails", /\.\.\/\.\.\/lib\/my_hyphenated\/name\/engine/ + assert_file "my_hyphenated-name/bin/rails", /\.\.\/lib\/my_hyphenated\/name\/engine/ end def test_being_quiet_while_creating_dummy_application - assert_no_match(/create\s+config\/application.rb/, run_generator) + assert_no_match(/create\s+config\/application\.rb/, run_generator) end def test_create_mountable_application_with_mountable_option @@ -322,13 +310,13 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "app/assets/javascripts/bukkits" assert_file "app/assets/stylesheets/bukkits" assert_file "app/assets/images/bukkits" - assert_file "config/routes.rb", /Bukkits::Engine.routes.draw do/ + assert_file "config/routes.rb", /Bukkits::Engine\.routes\.draw do/ assert_file "lib/bukkits/engine.rb", /isolate_namespace Bukkits/ assert_file "test/dummy/config/routes.rb", /mount Bukkits::Engine => "\/bukkits"/ assert_file "app/controllers/bukkits/application_controller.rb", /module Bukkits\n class ApplicationController < ActionController::Base/ assert_file "app/models/bukkits/application_record.rb", /module Bukkits\n class ApplicationRecord < ActiveRecord::Base/ assert_file "app/jobs/bukkits/application_job.rb", /module Bukkits\n class ApplicationJob < ActiveJob::Base/ - assert_file "app/mailers/bukkits/application_mailer.rb", /module Bukkits\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n/ + assert_file "app/mailers/bukkits/application_mailer.rb", /module Bukkits\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example\.com'\n layout 'mailer'\n/ assert_file "app/helpers/bukkits/application_helper.rb", /module Bukkits\n module ApplicationHelper/ assert_file "app/views/layouts/bukkits/application.html.erb" do |contents| assert_match "<title>Bukkits</title>", contents @@ -349,15 +337,15 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "hyphenated-name/app/assets/javascripts/hyphenated/name" assert_file "hyphenated-name/app/assets/stylesheets/hyphenated/name" assert_file "hyphenated-name/app/assets/images/hyphenated/name" - assert_file "hyphenated-name/config/routes.rb", /Hyphenated::Name::Engine.routes.draw do/ - assert_file "hyphenated-name/lib/hyphenated/name/version.rb", /module Hyphenated\n module Name\n VERSION = '0.1.0'\n end\nend/ + assert_file "hyphenated-name/config/routes.rb", /Hyphenated::Name::Engine\.routes\.draw do/ + assert_file "hyphenated-name/lib/hyphenated/name/version.rb", /module Hyphenated\n module Name\n VERSION = '0\.1\.0'\n end\nend/ assert_file "hyphenated-name/lib/hyphenated/name/engine.rb", /module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Hyphenated::Name\n end\n end\nend/ assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/ assert_file "hyphenated-name/test/dummy/config/routes.rb", /mount Hyphenated::Name::Engine => "\/hyphenated-name"/ assert_file "hyphenated-name/app/controllers/hyphenated/name/application_controller.rb", /module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n protect_from_forgery with: :exception\n end\n end\nend\n/ assert_file "hyphenated-name/app/models/hyphenated/name/application_record.rb", /module Hyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\nend/ assert_file "hyphenated-name/app/jobs/hyphenated/name/application_job.rb", /module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/ - assert_file "hyphenated-name/app/mailers/hyphenated/name/application_mailer.rb", /module Hyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\nend/ + assert_file "hyphenated-name/app/mailers/hyphenated/name/application_mailer.rb", /module Hyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example\.com'\n layout 'mailer'\n end\n end\nend/ assert_file "hyphenated-name/app/helpers/hyphenated/name/application_helper.rb", /module Hyphenated\n module Name\n module ApplicationHelper\n end\n end\nend/ assert_file "hyphenated-name/app/views/layouts/hyphenated/name/application.html.erb" do |contents| assert_match "<title>Hyphenated name</title>", contents @@ -371,15 +359,15 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "my_hyphenated-name/app/assets/javascripts/my_hyphenated/name" assert_file "my_hyphenated-name/app/assets/stylesheets/my_hyphenated/name" assert_file "my_hyphenated-name/app/assets/images/my_hyphenated/name" - assert_file "my_hyphenated-name/config/routes.rb", /MyHyphenated::Name::Engine.routes.draw do/ - assert_file "my_hyphenated-name/lib/my_hyphenated/name/version.rb", /module MyHyphenated\n module Name\n VERSION = '0.1.0'\n end\nend/ + assert_file "my_hyphenated-name/config/routes.rb", /MyHyphenated::Name::Engine\.routes\.draw do/ + assert_file "my_hyphenated-name/lib/my_hyphenated/name/version.rb", /module MyHyphenated\n module Name\n VERSION = '0\.1\.0'\n end\nend/ assert_file "my_hyphenated-name/lib/my_hyphenated/name/engine.rb", /module MyHyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace MyHyphenated::Name\n end\n end\nend/ assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/ assert_file "my_hyphenated-name/test/dummy/config/routes.rb", /mount MyHyphenated::Name::Engine => "\/my_hyphenated-name"/ assert_file "my_hyphenated-name/app/controllers/my_hyphenated/name/application_controller.rb", /module MyHyphenated\n module Name\n class ApplicationController < ActionController::Base\n protect_from_forgery with: :exception\n end\n end\nend\n/ assert_file "my_hyphenated-name/app/models/my_hyphenated/name/application_record.rb", /module MyHyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\nend/ assert_file "my_hyphenated-name/app/jobs/my_hyphenated/name/application_job.rb", /module MyHyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/ - assert_file "my_hyphenated-name/app/mailers/my_hyphenated/name/application_mailer.rb", /module MyHyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\nend/ + assert_file "my_hyphenated-name/app/mailers/my_hyphenated/name/application_mailer.rb", /module MyHyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example\.com'\n layout 'mailer'\n end\n end\nend/ assert_file "my_hyphenated-name/app/helpers/my_hyphenated/name/application_helper.rb", /module MyHyphenated\n module Name\n module ApplicationHelper\n end\n end\nend/ assert_file "my_hyphenated-name/app/views/layouts/my_hyphenated/name/application.html.erb" do |contents| assert_match "<title>My hyphenated name</title>", contents @@ -393,15 +381,15 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "deep-hyphenated-name/app/assets/javascripts/deep/hyphenated/name" assert_file "deep-hyphenated-name/app/assets/stylesheets/deep/hyphenated/name" assert_file "deep-hyphenated-name/app/assets/images/deep/hyphenated/name" - assert_file "deep-hyphenated-name/config/routes.rb", /Deep::Hyphenated::Name::Engine.routes.draw do/ - assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/version.rb", /module Deep\n module Hyphenated\n module Name\n VERSION = '0.1.0'\n end\n end\nend/ + assert_file "deep-hyphenated-name/config/routes.rb", /Deep::Hyphenated::Name::Engine\.routes\.draw do/ + assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/version.rb", /module Deep\n module Hyphenated\n module Name\n VERSION = '0\.1\.0'\n end\n end\nend/ assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/engine.rb", /module Deep\n module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Deep::Hyphenated::Name\n end\n end\n end\nend/ assert_file "deep-hyphenated-name/lib/deep/hyphenated/name.rb", /require "deep\/hyphenated\/name\/engine"/ assert_file "deep-hyphenated-name/test/dummy/config/routes.rb", /mount Deep::Hyphenated::Name::Engine => "\/deep-hyphenated-name"/ assert_file "deep-hyphenated-name/app/controllers/deep/hyphenated/name/application_controller.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n protect_from_forgery with: :exception\n end\n end\n end\nend\n/ assert_file "deep-hyphenated-name/app/models/deep/hyphenated/name/application_record.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\n end\nend/ assert_file "deep-hyphenated-name/app/jobs/deep/hyphenated/name/application_job.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/ - assert_file "deep-hyphenated-name/app/mailers/deep/hyphenated/name/application_mailer.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\n end\nend/ + assert_file "deep-hyphenated-name/app/mailers/deep/hyphenated/name/application_mailer.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example\.com'\n layout 'mailer'\n end\n end\n end\nend/ assert_file "deep-hyphenated-name/app/helpers/deep/hyphenated/name/application_helper.rb", /module Deep\n module Hyphenated\n module Name\n module ApplicationHelper\n end\n end\n end\nend/ assert_file "deep-hyphenated-name/app/views/layouts/deep/hyphenated/name/application.html.erb" do |contents| assert_match "<title>Deep hyphenated name</title>", contents @@ -412,16 +400,16 @@ class PluginGeneratorTest < Rails::Generators::TestCase def test_creating_gemspec run_generator - assert_file "bukkits.gemspec", /s.name\s+= "bukkits"/ - assert_file "bukkits.gemspec", /s.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*", "MIT-LICENSE", "Rakefile", "README\.md"\]/ - assert_file "bukkits.gemspec", /s.version\s+ = Bukkits::VERSION/ + assert_file "bukkits.gemspec", /s\.name\s+= "bukkits"/ + assert_file "bukkits.gemspec", /s\.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*", "MIT-LICENSE", "Rakefile", "README\.md"\]/ + assert_file "bukkits.gemspec", /s\.version\s+ = Bukkits::VERSION/ end def test_usage_of_engine_commands run_generator [destination_root, "--full"] - assert_file "bin/rails", /ENGINE_PATH = File.expand_path\('..\/..\/lib\/bukkits\/engine', __FILE__\)/ - assert_file "bin/rails", /ENGINE_ROOT = File.expand_path\('..\/..', __FILE__\)/ - assert_file "bin/rails", %r|APP_PATH = File.expand_path\('../../test/dummy/config/application', __FILE__\)| + assert_file "bin/rails", /ENGINE_PATH = File\.expand_path\('\.\.\/lib\/bukkits\/engine', __dir__\)/ + assert_file "bin/rails", /ENGINE_ROOT = File\.expand_path\('\.\.', __dir__\)/ + assert_file "bin/rails", %r|APP_PATH = File\.expand_path\('\.\./test/dummy/config/application', __dir__\)| assert_file "bin/rails", /require 'rails\/all'/ assert_file "bin/rails", /require 'rails\/engine\/commands'/ end @@ -437,7 +425,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "spec/dummy/config/application.rb" assert_no_file "test/dummy" assert_file "test/test_helper.rb" do |content| - assert_match(/require.+spec\/dummy\/config\/environment/, content) + assert_match(/require_relative.+spec\/dummy\/config\/environment/, content) assert_match(/ActiveRecord::Migrator\.migrations_paths.+spec\/dummy\/db\/migrate/, content) end end @@ -448,17 +436,16 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "spec/fake/config/application.rb" assert_no_file "test/dummy" assert_file "test/test_helper.rb" do |content| - assert_match(/require.+spec\/fake\/config\/environment/, content) + assert_match(/require_relative.+spec\/fake\/config\/environment/, content) assert_match(/ActiveRecord::Migrator\.migrations_paths.+spec\/fake\/db\/migrate/, content) end end def test_creating_dummy_without_tests_but_with_dummy_path run_generator [destination_root, "--dummy_path", "spec/dummy", "--skip-test"] - assert_file "spec/dummy" - assert_file "spec/dummy/config/application.rb" - assert_no_file "test" - assert_no_file "test/test_helper.rb" + assert_directory "spec/dummy" + assert_file "spec/dummy/config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/ + assert_no_directory "test" assert_file ".gitignore" do |contents| assert_match(/spec\/dummy/, contents) end @@ -468,7 +455,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase run_generator assert_file "test/dummy/config/environments/development.rb" do |contents| - assert_match(/^\s*# config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, contents) + assert_match(/^\s*# config\.file_watcher = ActiveSupport::EventedFileUpdateChecker/, contents) end end @@ -487,17 +474,19 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_no_file "test/dummy/Gemfile" assert_no_file "test/dummy/public/robots.txt" assert_no_file "test/dummy/README.md" + assert_no_file "test/dummy/config/master.key" + assert_no_file "test/dummy/config/credentials.yml.enc" assert_no_directory "test/dummy/lib/tasks" - assert_no_directory "test/dummy/doc" assert_no_directory "test/dummy/test" assert_no_directory "test/dummy/vendor" + assert_no_directory "test/dummy/.git" end def test_skipping_test_files run_generator [destination_root, "--skip-test"] - assert_no_file "test" + assert_no_directory "test" assert_file ".gitignore" do |contents| - assert_no_match(/test\dummy/, contents) + assert_no_match(/test\/dummy/, contents) end end @@ -526,10 +515,26 @@ class PluginGeneratorTest < Rails::Generators::TestCase gemfile_path = "#{Rails.root}/Gemfile" Object.const_set("APP_PATH", Rails.root) FileUtils.touch gemfile_path + File.write(gemfile_path, "#foo") run_generator - assert_file gemfile_path, /gem 'bukkits', path: 'tmp\/bukkits'/ + assert_file gemfile_path, /^gem 'bukkits', path: 'tmp\/bukkits'/ + ensure + Object.send(:remove_const, "APP_PATH") + FileUtils.rm gemfile_path + end + + def test_creating_plugin_only_specify_plugin_name_in_app_directory_adds_gemfile_entry + # simulate application existence + gemfile_path = "#{Rails.root}/Gemfile" + Object.const_set("APP_PATH", Rails.root) + FileUtils.touch gemfile_path + + FileUtils.cd(destination_root) + run_generator ["bukkits"] + + assert_file gemfile_path, /gem 'bukkits', path: 'bukkits'/ ensure Object.send(:remove_const, "APP_PATH") FileUtils.rm gemfile_path @@ -660,20 +665,6 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "app/models/bukkits/article.rb", /class Article < ApplicationRecord/ end - def test_generate_application_record_when_does_not_exist_in_mountable_engine - run_generator [destination_root, "--mountable"] - FileUtils.rm "#{destination_root}/app/models/bukkits/application_record.rb" - capture(:stdout) do - `#{destination_root}/bin/rails g model article` - end - - assert_file "#{destination_root}/app/models/bukkits/application_record.rb" do |record| - assert_match(/module Bukkits/, record) - assert_match(/class ApplicationRecord < ActiveRecord::Base/, record) - assert_match(/self.abstract_class = true/, record) - end - end - def test_generate_application_mailer_when_does_not_exist_in_mountable_engine run_generator [destination_root, "--mountable"] FileUtils.rm "#{destination_root}/app/mailers/bukkits/application_mailer.rb" @@ -715,6 +706,53 @@ class PluginGeneratorTest < Rails::Generators::TestCase end end + def test_app_update_generates_bin_file + run_generator [destination_root, "--mountable"] + + Object.const_set("ENGINE_ROOT", destination_root) + FileUtils.rm("#{destination_root}/bin/rails") + + quietly { Rails::Engine::Updater.run(:create_bin_files) } + + assert_file "#{destination_root}/bin/rails" do |content| + assert_match(%r|APP_PATH = File\.expand_path\('\.\./test/dummy/config/application', __dir__\)|, content) + end + ensure + Object.send(:remove_const, "ENGINE_ROOT") + end + + def test_after_bundle_callback + path = "http://example.org/rails_template" + template = %{ after_bundle { run "echo ran after_bundle" } }.dup + template.instance_eval "def read; self; end" # Make the string respond to read + + check_open = -> *args do + assert_equal [ path, "Accept" => "application/x-thor-template" ], args + template + end + + sequence = ["echo ran after_bundle"] + @sequence_step ||= 0 + ensure_bundler_first = -> command do + assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}" + @sequence_step += 1 + end + + content = nil + generator([destination_root], template: path).stub(:open, check_open, template) do + generator.stub(:bundle_command, ensure_bundler_first) do + generator.stub(:run, ensure_bundler_first) do + silence_stream($stdout) do + content = capture(:stderr) { generator.invoke_all } + end + end + end + end + + assert_equal 1, @sequence_step + assert_match(/DEPRECATION WARNING: `after_bundle` is deprecated/, content) + end + private def action(*args, &block) diff --git a/railties/test/generators/plugin_test_helper.rb b/railties/test/generators/plugin_test_helper.rb index 8ac90e3484..528f8d88f9 100644 --- a/railties/test/generators/plugin_test_helper.rb +++ b/railties/test/generators/plugin_test_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "tmpdir" diff --git a/railties/test/generators/plugin_test_runner_test.rb b/railties/test/generators/plugin_test_runner_test.rb index 0bdd2a77d2..89c3f1e496 100644 --- a/railties/test/generators/plugin_test_runner_test.rb +++ b/railties/test/generators/plugin_test_runner_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/plugin_test_helper" class PluginTestRunnerTest < ActiveSupport::TestCase @@ -71,7 +73,7 @@ class PluginTestRunnerTest < ActiveSupport::TestCase create_test_file "post", pass: false output = run_test_command("test/post_test.rb") - assert_match %r{Finished in.*\n\n1 runs, 1 assertions}, output + assert_match %r{Finished in.*\n1 runs, 1 assertions}, output end def test_fail_fast @@ -103,6 +105,12 @@ class PluginTestRunnerTest < ActiveSupport::TestCase capture(:stderr) { run_test_command("test/models/warnings_test.rb -w") }) end + def test_run_rake_test + create_test_file "foo" + result = Dir.chdir(plugin_path) { `rake test TEST=test/foo_test.rb` } + assert_match "1 runs, 1 assertions, 0 failures", result + end + private def plugin_path "#{@destination_root}/bukkits" diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb index e976e58180..63a2cd3869 100644 --- a/railties/test/generators/resource_generator_test.rb +++ b/railties/test/generators/resource_generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/resource/resource_generator" diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index 9971626f9f..fd5aa817b4 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/scaffold_controller/scaffold_controller_generator" @@ -172,6 +174,29 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_instance_method :index, content do |m| assert_match("@users = User.all", m) end + + assert_instance_method :create, content do |m| + assert_match("redirect_to [:admin, @user]", m) + end + + assert_instance_method :update, content do |m| + assert_match("redirect_to [:admin, @user]", m) + end + end + + assert_file "app/views/admin/users/index.html.erb" do |content| + assert_match("'Show', [:admin, user]", content) + assert_match("'Edit', edit_admin_user_path(user)", content) + assert_match("'Destroy', [:admin, user]", content) + assert_match("'New User', new_admin_user_path", content) + end + + assert_file "app/views/admin/users/new.html.erb" do |content| + assert_match("'Back', admin_users_path", content) + end + + assert_file "app/views/admin/users/_form.html.erb" do |content| + assert_match("model: [:admin, user]", content) end end @@ -182,6 +207,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase Dir.chdir(engine_path) do quietly { `bin/rails g controller dashboard foo` } + quietly { `bin/rails db:migrate RAILS_ENV=test` } assert_match(/2 runs, 2 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) end end @@ -193,6 +219,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase Dir.chdir(engine_path) do quietly { `bin/rails g controller dashboard foo` } + quietly { `bin/rails db:migrate RAILS_ENV=test` } assert_match(/2 runs, 2 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) end end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 6b7e2c91d7..29426cd99f 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/scaffold/scaffold_generator" @@ -62,6 +64,14 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_match(/patch product_line_url\(@product_line\), params: \{ product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test) end + # System tests + assert_file "test/system/product_lines_test.rb" do |test| + assert_match(/class ProductLinesTest < ApplicationSystemTestCase/, test) + assert_match(/visit product_lines_url/, test) + assert_match(/fill_in "Title", with: @product_line\.title/, test) + assert_match(/assert_text "Product line was successfully updated"/, test) + end + # Views assert_no_file "app/views/layouts/product_lines.html.erb" @@ -141,6 +151,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_no_match(/assert_redirected_to/, test) end + # System tests + assert_no_file "test/system/product_lines_test.rb" + # Views assert_no_file "app/views/layouts/product_lines.html.erb" @@ -168,6 +181,16 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end end + def test_system_tests_without_attributes + run_generator ["product_line"] + + assert_file "test/system/product_lines_test.rb" do |content| + assert_match(/class ProductLinesTest < ApplicationSystemTestCase/, content) + assert_match(/test "visiting the index"/, content) + assert_no_match(/fill_in/, content) + end + end + def test_scaffold_on_revoke run_generator run_generator ["product_line"], behavior: :revoke @@ -187,6 +210,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_no_file "app/controllers/product_lines_controller.rb" assert_no_file "test/controllers/product_lines_controller_test.rb" + # System tests + assert_no_file "test/system/product_lines_test.rb" + # Views assert_no_file "app/views/product_lines" assert_no_file "app/views/layouts/product_lines.html.erb" @@ -252,8 +278,18 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_file "test/controllers/admin/roles_controller_test.rb", /class Admin::RolesControllerTest < ActionDispatch::IntegrationTest/ + assert_file "test/system/admin/roles_test.rb", + /class Admin::RolesTest < ApplicationSystemTestCase/ + # Views - %w(index edit new show _form).each do |view| + assert_file "app/views/admin/roles/index.html.erb" do |content| + assert_match("'Show', admin_role", content) + assert_match("'Edit', edit_admin_role_path(admin_role)", content) + assert_match("'Destroy', admin_role", content) + assert_match("'New Admin Role', new_admin_role_path", content) + end + + %w(edit new show _form).each do |view| assert_file "app/views/admin/roles/#{view}.html.erb" end assert_no_file "app/views/layouts/admin/roles.html.erb" @@ -287,6 +323,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_no_file "app/controllers/admin/roles_controller.rb" assert_no_file "test/controllers/admin/roles_controller_test.rb" + # System tests + assert_no_file "test/system/admin/roles_test.rb" + # Views assert_no_file "app/views/admin/roles" assert_no_file "app/views/layouts/admin/roles.html.erb" @@ -432,8 +471,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end assert_file "app/views/accounts/_form.html.erb" do |content| - assert_match(/^\W{4}<%= f\.text_field :name %>/, content) - assert_match(/^\W{4}<%= f\.text_field :currency_id %>/, content) + assert_match(/^\W{4}<%= form\.text_field :name %>/, content) + assert_match(/^\W{4}<%= form\.text_field :currency_id %>/, content) end end @@ -456,8 +495,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end assert_file "app/views/users/_form.html.erb" do |content| - assert_match(/<%= f\.password_field :password %>/, content) - assert_match(/<%= f\.password_field :password_confirmation %>/, content) + assert_match(/<%= form\.password_field :password %>/, content) + assert_match(/<%= form\.password_field :password_confirmation %>/, content) end assert_file "app/views/users/index.html.erb" do |content| @@ -473,6 +512,11 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_match(/password_confirmation: 'secret'/, content) end + assert_file "test/system/users_test.rb" do |content| + assert_match(/fill_in "Password", with: 'secret'/, content) + assert_match(/fill_in "Password Confirmation", with: 'secret'/, content) + end + assert_file "test/fixtures/users.yml" do |content| assert_match(/password_digest: <%= BCrypt::Password.create\('secret'\) %>/, content) end @@ -492,6 +536,26 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end end + def test_scaffold_tests_pass_by_default_inside_namespaced_mountable_engine + Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits-admin --mountable` } + + engine_path = File.join(destination_root, "bukkits-admin") + + Dir.chdir(engine_path) do + quietly do + `bin/rails g scaffold User name:string age:integer; + bin/rails db:migrate` + end + + assert_file "bukkits-admin/app/controllers/bukkits/admin/users_controller.rb" do |content| + assert_match(/module Bukkits::Admin/, content) + assert_match(/class UsersController < ApplicationController/, content) + end + + assert_match(/8 runs, 10 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) + end + end + def test_scaffold_tests_pass_by_default_inside_full_engine Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --full` } @@ -533,4 +597,63 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_match(/6 runs, 8 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) end end + + def test_scaffold_on_invoke_inside_mountable_engine + Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --mountable` } + engine_path = File.join(destination_root, "bukkits") + + Dir.chdir(engine_path) do + quietly { `bin/rails generate scaffold User name:string age:integer` } + + assert File.exist?("app/models/bukkits/user.rb") + assert File.exist?("test/models/bukkits/user_test.rb") + assert File.exist?("test/fixtures/bukkits/users.yml") + + assert File.exist?("app/controllers/bukkits/users_controller.rb") + assert File.exist?("test/controllers/bukkits/users_controller_test.rb") + + assert File.exist?("test/system/bukkits/users_test.rb") + + assert File.exist?("app/views/bukkits/users/index.html.erb") + assert File.exist?("app/views/bukkits/users/edit.html.erb") + assert File.exist?("app/views/bukkits/users/show.html.erb") + assert File.exist?("app/views/bukkits/users/new.html.erb") + assert File.exist?("app/views/bukkits/users/_form.html.erb") + + assert File.exist?("app/helpers/bukkits/users_helper.rb") + + assert File.exist?("app/assets/javascripts/bukkits/users.js") + assert File.exist?("app/assets/stylesheets/bukkits/users.css") + end + end + + def test_scaffold_on_revoke_inside_mountable_engine + Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --mountable` } + engine_path = File.join(destination_root, "bukkits") + + Dir.chdir(engine_path) do + quietly { `bin/rails generate scaffold User name:string age:integer` } + quietly { `bin/rails destroy scaffold User` } + + assert_not File.exist?("app/models/bukkits/user.rb") + assert_not File.exist?("test/models/bukkits/user_test.rb") + assert_not File.exist?("test/fixtures/bukkits/users.yml") + + assert_not File.exist?("app/controllers/bukkits/users_controller.rb") + assert_not File.exist?("test/controllers/bukkits/users_controller_test.rb") + + assert_not File.exist?("test/system/bukkits/users_test.rb") + + assert_not File.exist?("app/views/bukkits/users/index.html.erb") + assert_not File.exist?("app/views/bukkits/users/edit.html.erb") + assert_not File.exist?("app/views/bukkits/users/show.html.erb") + assert_not File.exist?("app/views/bukkits/users/new.html.erb") + assert_not File.exist?("app/views/bukkits/users/_form.html.erb") + + assert_not File.exist?("app/helpers/bukkits/users_helper.rb") + + assert_not File.exist?("app/assets/javascripts/bukkits/users.js") + assert_not File.exist?("app/assets/stylesheets/bukkits/users.css") + end + end end diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index cc9d3629e9..29528825b8 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # # Tests, setup, and teardown common to the application and plugin generator suites. # @@ -20,6 +22,10 @@ module SharedGeneratorTests Rails.application = TestApp::Application.instance end + def application_path + destination_root + end + def test_skeleton_is_created run_generator @@ -45,14 +51,14 @@ module SharedGeneratorTests reserved_words = %w[application destroy plugin runner test] reserved_words.each do |reserved| content = capture(:stderr) { run_generator [File.join(destination_root, reserved)] } - assert_match(/Invalid \w+ name #{reserved}. Please give a name which does not match one of the reserved rails words: application, destroy, plugin, runner, test\n/, content) + assert_match(/Invalid \w+ name #{reserved}\. Please give a name which does not match one of the reserved rails words: application, destroy, plugin, runner, test\n/, content) end end def test_name_raises_an_error_if_name_already_used_constant %w{ String Hash Class Module Set Symbol }.each do |ruby_class| content = capture(:stderr) { run_generator [File.join(destination_root, ruby_class)] } - assert_match(/Invalid \w+ name #{ruby_class}, constant #{ruby_class} is already in use. Please choose another \w+ name.\n/, content) + assert_match(/Invalid \w+ name #{ruby_class}, constant #{ruby_class} is already in use\. Please choose another \w+ name\.\n/, content) end end @@ -77,7 +83,7 @@ module SharedGeneratorTests def test_template_is_executed_when_supplied_an_https_path path = "https://gist.github.com/josevalim/103208/raw/" - template = %{ say "It works!" } + template = %{ say "It works!" }.dup template.instance_eval "def read; self; end" # Make the string respond to read check_open = -> *args do @@ -86,7 +92,9 @@ module SharedGeneratorTests end generator([destination_root], template: path).stub(:open, check_open, template) do - quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) } + generator.stub :bundle_command, nil do + quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) } + end end end @@ -97,15 +105,6 @@ module SharedGeneratorTests end end - def test_skip_bundle - assert_not_called(generator([destination_root], skip_bundle: true), :bundle_command) do - quietly { generator.invoke_all } - # skip_bundle is only about running bundle install, ensure the Gemfile is still - # generated. - assert_file "Gemfile" - end - end - def test_skip_git run_generator [destination_root, "--skip-git", "--full"] assert_no_file(".gitignore") @@ -121,4 +120,247 @@ module SharedGeneratorTests assert_no_file("app/models/concerns/.keep") end + + def test_default_frameworks_are_required_when_others_are_removed + run_generator [ + destination_root, + "--skip-active-record", + "--skip-active-storage", + "--skip-action-mailer", + "--skip-action-cable", + "--skip-sprockets" + ] + + assert_file "#{application_path}/config/application.rb", /^require\s+["']rails["']/ + assert_file "#{application_path}/config/application.rb", /^require\s+["']active_model\/railtie["']/ + assert_file "#{application_path}/config/application.rb", /^require\s+["']active_job\/railtie["']/ + assert_file "#{application_path}/config/application.rb", /^# require\s+["']active_record\/railtie["']/ + assert_file "#{application_path}/config/application.rb", /^# require\s+["']active_storage\/engine["']/ + assert_file "#{application_path}/config/application.rb", /^require\s+["']action_controller\/railtie["']/ + assert_file "#{application_path}/config/application.rb", /^# require\s+["']action_mailer\/railtie["']/ + assert_file "#{application_path}/config/application.rb", /^require\s+["']action_view\/railtie["']/ + assert_file "#{application_path}/config/application.rb", /^# require\s+["']action_cable\/engine["']/ + assert_file "#{application_path}/config/application.rb", /^# require\s+["']sprockets\/railtie["']/ + assert_file "#{application_path}/config/application.rb", /^require\s+["']rails\/test_unit\/railtie["']/ + end + + def test_generator_without_skips + run_generator + assert_file "#{application_path}/config/application.rb", /\s+require\s+["']rails\/all["']/ + assert_file "#{application_path}/config/environments/development.rb" do |content| + assert_match(/config\.action_mailer\.raise_delivery_errors = false/, content) + end + assert_file "#{application_path}/config/environments/test.rb" do |content| + assert_match(/config\.action_mailer\.delivery_method = :test/, content) + end + assert_file "#{application_path}/config/environments/production.rb" do |content| + assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content) + assert_match(/^ # config\.require_master_key = true/, content) + end + end + + def test_gitignore_when_sqlite3 + run_generator + + assert_file ".gitignore" do |content| + assert_match(/sqlite3/, content) + end + end + + def test_gitignore_when_non_sqlite3_db + run_generator([destination_root, "-d", "mysql"]) + + assert_file ".gitignore" do |content| + assert_no_match(/sqlite/i, content) + end + end + + def test_generator_if_skip_active_record_is_given + run_generator [destination_root, "--skip-active-record"] + assert_no_directory "#{application_path}/db/" + assert_no_file "#{application_path}/config/database.yml" + assert_no_file "#{application_path}/app/models/application_record.rb" + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ + assert_file "test/test_helper.rb" do |helper_content| + assert_no_match(/fixtures :all/, helper_content) + end + assert_file "#{application_path}/bin/setup" do |setup_content| + assert_no_match(/db:setup/, setup_content) + end + assert_file "#{application_path}/bin/update" do |update_content| + assert_no_match(/db:migrate/, update_content) + end + assert_file ".gitignore" do |content| + assert_no_match(/sqlite/i, content) + end + end + + def test_generator_for_active_storage + run_generator + + assert_file "#{application_path}/app/assets/javascripts/application.js" do |content| + assert_match(/^\/\/= require activestorage/, content) + end + + assert_file "#{application_path}/config/environments/development.rb" do |content| + assert_match(/config\.active_storage/, content) + end + + assert_file "#{application_path}/config/environments/production.rb" do |content| + assert_match(/config\.active_storage/, content) + end + + assert_file "#{application_path}/config/environments/test.rb" do |content| + assert_match(/config\.active_storage/, content) + end + + assert_file "#{application_path}/config/storage.yml" + assert_directory "#{application_path}/storage" + assert_directory "#{application_path}/tmp/storage" + + assert_file ".gitignore" do |content| + assert_match(/\/storage\//, content) + end + end + + def test_generator_if_skip_active_storage_is_given + run_generator [destination_root, "--skip-active-storage"] + + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']active_storage\/engine["']/ + + assert_file "#{application_path}/app/assets/javascripts/application.js" do |content| + assert_no_match(/^\/\/= require activestorage/, content) + end + + assert_file "#{application_path}/config/environments/development.rb" do |content| + assert_no_match(/config\.active_storage/, content) + end + + assert_file "#{application_path}/config/environments/production.rb" do |content| + assert_no_match(/config\.active_storage/, content) + end + + assert_file "#{application_path}/config/environments/test.rb" do |content| + assert_no_match(/config\.active_storage/, content) + end + + assert_no_file "#{application_path}/config/storage.yml" + assert_no_directory "#{application_path}/db/migrate" + assert_no_directory "#{application_path}/storage" + assert_no_directory "#{application_path}/tmp/storage" + + assert_file ".gitignore" do |content| + assert_no_match(/\/storage\//, content) + end + end + + def test_generator_does_not_generate_active_storage_contents_if_skip_active_record_is_given + run_generator [destination_root, "--skip-active-record"] + + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']active_storage\/engine["']/ + + assert_file "#{application_path}/app/assets/javascripts/application.js" do |content| + assert_no_match(/^\/\/= require activestorage/, content) + end + + assert_file "#{application_path}/config/environments/development.rb" do |content| + assert_no_match(/config\.active_storage/, content) + end + + assert_file "#{application_path}/config/environments/production.rb" do |content| + assert_no_match(/config\.active_storage/, content) + end + + assert_file "#{application_path}/config/environments/test.rb" do |content| + assert_no_match(/config\.active_storage/, content) + end + + assert_no_file "#{application_path}/config/storage.yml" + assert_no_directory "#{application_path}/db/migrate" + assert_no_directory "#{application_path}/storage" + assert_no_directory "#{application_path}/tmp/storage" + + assert_file ".gitignore" do |content| + assert_no_match(/\/storage\//, content) + end + end + + def test_generator_if_skip_action_mailer_is_given + run_generator [destination_root, "--skip-action-mailer"] + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/ + assert_file "#{application_path}/config/environments/development.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_file "#{application_path}/config/environments/test.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_file "#{application_path}/config/environments/production.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_no_directory "#{application_path}/app/mailers" + assert_no_directory "#{application_path}/test/mailers" + end + + def test_generator_if_skip_action_cable_is_given + run_generator [destination_root, "--skip-action-cable"] + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_cable\/engine["']/ + assert_no_file "#{application_path}/config/cable.yml" + assert_no_file "#{application_path}/app/assets/javascripts/cable.js" + assert_no_directory "#{application_path}/app/assets/javascripts/channels" + assert_no_directory "#{application_path}/app/channels" + assert_file "Gemfile" do |content| + assert_no_match(/redis/, content) + end + end + + def test_generator_if_skip_sprockets_is_given + run_generator [destination_root, "--skip-sprockets"] + + assert_no_file "#{application_path}/config/initializers/assets.rb" + + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']sprockets\/railtie["']/ + + assert_file "Gemfile" do |content| + assert_no_match(/sass-rails/, content) + assert_no_match(/uglifier/, content) + assert_no_match(/coffee-rails/, content) + end + + assert_file "#{application_path}/config/environments/development.rb" do |content| + assert_no_match(/config\.assets\.debug/, content) + end + + assert_file "#{application_path}/config/environments/production.rb" do |content| + assert_no_match(/config\.assets\.digest/, content) + assert_no_match(/config\.assets\.js_compressor/, content) + assert_no_match(/config\.assets\.css_compressor/, content) + assert_no_match(/config\.assets\.compile/, content) + end + end + + def test_generator_for_yarn + run_generator + assert_file "#{application_path}/package.json", /dependencies/ + assert_file "#{application_path}/config/initializers/assets.rb", /node_modules/ + + assert_file ".gitignore" do |content| + assert_match(/node_modules/, content) + assert_match(/yarn-error\.log/, content) + end + end + + def test_generator_for_yarn_skipped + run_generator([destination_root, "--skip-yarn"]) + assert_no_file "#{application_path}/package.json" + assert_no_file "#{application_path}/bin/yarn" + + assert_file "#{application_path}/config/initializers/assets.rb" do |content| + assert_no_match(/node_modules/, content) + end + + assert_file ".gitignore" do |content| + assert_no_match(/node_modules/, content) + assert_no_match(/yarn-error\.log/, content) + end + end end diff --git a/railties/test/generators/system_test_generator_test.rb b/railties/test/generators/system_test_generator_test.rb new file mode 100644 index 0000000000..efa70a050b --- /dev/null +++ b/railties/test/generators/system_test_generator_test.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "generators/generators_test_helper" +require "rails/generators/rails/system_test/system_test_generator" + +class SystemTestGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + arguments %w(user) + + def test_system_test_skeleton_is_created + run_generator + assert_file "test/system/users_test.rb", /class UsersTest < ApplicationSystemTestCase/ + end + + def test_namespaced_system_test_skeleton_is_created + run_generator %w(admin/user) + assert_file "test/system/admin/users_test.rb", /class Admin::UsersTest < ApplicationSystemTestCase/ + end +end diff --git a/railties/test/generators/task_generator_test.rb b/railties/test/generators/task_generator_test.rb index 2285534bb9..5f162919d8 100644 --- a/railties/test/generators/task_generator_test.rb +++ b/railties/test/generators/task_generator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/task/task_generator" diff --git a/railties/test/generators/test_runner_in_engine_test.rb b/railties/test/generators/test_runner_in_engine_test.rb index 4b5fb3ba3f..0e15b5e388 100644 --- a/railties/test/generators/test_runner_in_engine_test.rb +++ b/railties/test/generators/test_runner_in_engine_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/plugin_test_helper" class TestRunnerInEngineTest < ActiveSupport::TestCase @@ -17,7 +19,7 @@ class TestRunnerInEngineTest < ActiveSupport::TestCase create_test_file "post", pass: false output = run_test_command("test/post_test.rb") - expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth \[[^\]]+test/post_test.rb:6\]:\nwups!\n\nbin/rails test test/post_test.rb:4} + expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth \[[^\]]+test/post_test\.rb:6\]:\nwups!\n\nbin/rails test test/post_test\.rb:4} assert_match expect, output end diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 68ba435393..1735804664 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "generators/generators_test_helper" require "rails/generators/rails/model/model_generator" require "rails/generators/test_unit/model/model_generator" @@ -34,6 +36,19 @@ class GeneratorsTest < Rails::Generators::TestCase assert_match "Maybe you meant 'migration'", output end + def test_generator_suggestions_except_en_locale + orig_available_locales = I18n.available_locales + orig_default_locale = I18n.default_locale + I18n.available_locales = :ja + I18n.default_locale = :ja + name = :tas + output = capture(:stdout) { Rails::Generators.invoke name } + assert_match "Maybe you meant 'task', 'job' or", output + ensure + I18n.available_locales = orig_available_locales + I18n.default_locale = orig_default_locale + end + def test_generator_multiple_suggestions name = :tas output = capture(:stdout) { Rails::Generators.invoke name } @@ -124,7 +139,7 @@ class GeneratorsTest < Rails::Generators::TestCase def test_rails_generators_help_does_not_include_app_nor_plugin_new output = capture(:stdout) { Rails::Generators.help } - assert_no_match(/app/, output) + assert_no_match(/app\W/, output) assert_no_match(/[^:]plugin/, output) end @@ -200,7 +215,7 @@ class GeneratorsTest < Rails::Generators::TestCase self.class.class_eval(<<-end_eval, __FILE__, __LINE__ + 1) class WithOptionsGenerator < Rails::Generators::Base - class_option :generate, :default => true + class_option :generate, default: true, type: :boolean end end_eval @@ -233,7 +248,7 @@ class GeneratorsTest < Rails::Generators::TestCase end def test_usage_with_embedded_ruby - require File.expand_path("fixtures/lib/generators/usage_template/usage_template_generator", File.dirname(__FILE__)) + require_relative "fixtures/lib/generators/usage_template/usage_template_generator" output = capture(:stdout) { Rails::Generators.invoke :usage_template, ["--help"] } assert_match(/:: 2 ::/, output) end diff --git a/railties/test/initializable_test.rb b/railties/test/initializable_test.rb index 4b67c91cc5..59fee245f9 100644 --- a/railties/test/initializable_test.rb +++ b/railties/test/initializable_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "rails/initializable" diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 1902eac862..6568a356d6 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Note: # It is important to keep this file as light as possible # the goal for tests that require this is to test booting up @@ -14,7 +16,7 @@ require "active_support/testing/autorun" require "active_support/testing/stream" require "active_support/test_case" -RAILS_FRAMEWORK_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../..") +RAILS_FRAMEWORK_ROOT = File.expand_path("../../..", __dir__) # These files do not require any others and are needed # to run the tests @@ -22,6 +24,7 @@ require "active_support/core_ext/object/blank" require "active_support/testing/isolation" require "active_support/core_ext/kernel/reporting" require "tmpdir" +require "rails/secrets" module TestHelpers module Paths @@ -53,10 +56,7 @@ module TestHelpers @app ||= begin ENV["RAILS_ENV"] = env - # FIXME: shush Sass warning spam, not relevant to testing Railties - Kernel.silence_warnings do - require "#{app_path}/config/environment" - end + require "#{app_path}/config/environment" Rails.application end @@ -65,7 +65,7 @@ module TestHelpers end def extract_body(response) - "".tap do |body| + "".dup.tap do |body| response[2].each { |chunk| body << chunk } end end @@ -82,22 +82,6 @@ module TestHelpers assert_match "charset=utf-8", resp[1]["Content-Type"] assert extract_body(resp).match(/Yay! You.*re on Rails!/) end - - def assert_success(resp) - assert_equal 202, resp[0] - end - - def assert_missing(resp) - assert_equal 404, resp[0] - end - - def assert_header(key, value, resp) - assert_equal value, resp[1][key.to_s] - end - - def assert_body(expected, resp) - assert_equal expected, extract_body(resp) - end end module Generation @@ -105,7 +89,6 @@ module TestHelpers def build_app(options = {}) @prev_rails_env = ENV["RAILS_ENV"] ENV["RAILS_ENV"] = "development" - ENV["SECRET_KEY_BASE"] ||= SecureRandom.hex(16) FileUtils.rm_rf(app_path) FileUtils.cp_r(app_template_path, app_path) @@ -118,7 +101,7 @@ module TestHelpers end routes = File.read("#{app_path}/config/routes.rb") - if routes =~ /(\n\s*end\s*)\Z/ + if routes =~ /(\n\s*end\s*)\z/ File.open("#{app_path}/config/routes.rb", "w") do |f| f.puts $` + "\nActiveSupport::Deprecation.silence { match ':controller(/:action(/:id))(.:format)', via: :all }\n" + $1 end @@ -154,6 +137,7 @@ module TestHelpers def teardown_app ENV["RAILS_ENV"] = @prev_rails_env if @prev_rails_env + FileUtils.rm_rf(tmp_path) end # Make a very basic app, without creating the whole directory structure. @@ -163,9 +147,10 @@ module TestHelpers require "action_controller/railtie" require "action_view/railtie" - @app = Class.new(Rails::Application) + @app = Class.new(Rails::Application) do + def self.name; "RailtiesTestApp"; end + end @app.config.eager_load = false - @app.secrets.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4" @app.config.session_store :cookie_store, key: "_myapp_session" @app.config.active_support.deprecation = :log @app.config.active_support.test_order = :random @@ -221,8 +206,8 @@ module TestHelpers FileUtils.mkdir_p(dir) app = File.readlines("#{app_path}/config/application.rb") - app.insert(2, "$:.unshift(\"#{dir}/lib\")") - app.insert(3, "require #{name.inspect}") + app.insert(4, "$:.unshift(\"#{dir}/lib\")") + app.insert(5, "require #{name.inspect}") File.open("#{app_path}/config/application.rb", "r+") do |f| f.puts app @@ -233,10 +218,86 @@ module TestHelpers end end - def script(script) - Dir.chdir(app_path) do - `#{Gem.ruby} #{app_path}/bin/rails #{script}` + # Invoke a bin/rails command inside the app + # + # allow_failure:: true to return normally if the command exits with + # a non-zero status. By default, this method will raise. + # stderr:: true to pass STDERR output straight to the "real" STDERR. + # By default, the STDERR and STDOUT of the process will be + # combined in the returned string. + def rails(*args, allow_failure: false, stderr: false) + args = args.flatten + fork = true + + command = "bin/rails #{Shellwords.join args}#{' 2>&1' unless stderr}" + + # Don't fork if the environment has disabled it + fork = false if ENV["NO_FORK"] + + # Don't fork if the runtime isn't able to + fork = false if !Process.respond_to?(:fork) + + # Don't fork if we're re-invoking minitest + fork = false if args.first == "t" || args.grep(/\Atest(:|\z)/).any? + + if fork + out_read, out_write = IO.pipe + if stderr + err_read, err_write = IO.pipe + else + err_write = out_write + end + + pid = fork do + out_read.close + err_read.close if err_read + + $stdin.reopen(File::NULL, "r") + $stdout.reopen(out_write) + $stderr.reopen(err_write) + + at_exit do + case $! + when SystemExit + exit! $!.status + when nil + exit! 0 + else + err_write.puts "#{$!.class}: #{$!}" + exit! 1 + end + end + + Rails.instance_variable_set :@_env, nil + + $-v = $-w = false + Dir.chdir app_path unless Dir.pwd == app_path + + ARGV.replace(args) + load "./bin/rails" + + exit! 0 + end + + out_write.close + + if err_read + err_write.close + + $stderr.write err_read.read + end + + output = out_read.read + + Process.waitpid pid + + else + output = `cd #{app_path}; #{command}` end + + raise "rails command failed (#{$?.exitstatus}): #{command}\n#{output}" unless allow_failure || $?.success? + + output end def add_to_top_of_config(str) @@ -250,7 +311,7 @@ module TestHelpers def add_to_config(str) environment = File.read("#{app_path}/config/application.rb") - if environment =~ /(\n\s*end\s*end\s*)\Z/ + if environment =~ /(\n\s*end\s*end\s*)\z/ File.open("#{app_path}/config/application.rb", "w") do |f| f.puts $` + "\n#{str}\n" + $1 end @@ -259,7 +320,7 @@ module TestHelpers def add_to_env_config(env, str) environment = File.read("#{app_path}/config/environments/#{env}.rb") - if environment =~ /(\n\s*end\s*)\Z/ + if environment =~ /(\n\s*end\s*)\z/ File.open("#{app_path}/config/environments/#{env}.rb", "w") do |f| f.puts $` + "\n#{str}\n" + $1 end @@ -281,10 +342,12 @@ module TestHelpers end def app_file(path, contents, mode = "w") - FileUtils.mkdir_p File.dirname("#{app_path}/#{path}") - File.open("#{app_path}/#{path}", mode) do |f| + file_name = "#{app_path}/#{path}" + FileUtils.mkdir_p File.dirname(file_name) + File.open(file_name, mode) do |f| f.puts contents end + file_name end def remove_file(path) @@ -296,7 +359,7 @@ module TestHelpers end def use_frameworks(arr) - to_remove = [:actionmailer, :activerecord] - arr + to_remove = [:actionmailer, :activerecord, :activestorage, :activejob] - arr if to_remove.include?(:activerecord) remove_from_config "config.active_record.*" @@ -304,6 +367,21 @@ module TestHelpers $:.reject! { |path| path =~ %r'/(#{to_remove.join('|')})/' } end + + def use_postgresql + File.open("#{app_path}/config/database.yml", "w") do |f| + f.puts <<-YAML + default: &default + adapter: postgresql + pool: 5 + database: railties_test + development: + <<: *default + test: + <<: *default + YAML + end + end end end @@ -313,7 +391,9 @@ class ActiveSupport::TestCase include TestHelpers::Generation include ActiveSupport::Testing::Stream - self.test_order = :sorted + def frozen_error_class + Object.const_defined?(:FrozenError) ? FrozenError : RuntimeError + end end # Create a scope and build a fixture rails app @@ -328,4 +408,25 @@ Module.new do File.open("#{app_template_path}/config/boot.rb", "w") do |f| f.puts "require 'rails/all'" end + + # Fake 'Bundler.require' -- we run using the repo's Gemfile, not an + # app-specific one: we don't want to require every gem that lists. + contents = File.read("#{app_template_path}/config/application.rb") + contents.sub!(/^Bundler\.require.*/, "%w(turbolinks).each { |r| require r }") + File.write("#{app_template_path}/config/application.rb", contents) + + require "rails" + + require "active_model" + require "active_job" + require "active_record" + require "action_controller" + require "action_mailer" + require "action_view" + require "active_storage" + require "action_cable" + require "sprockets" + + require "action_view/helpers" + require "action_dispatch/routing/route_set" end unless defined?(RAILS_ISOLATED_ENGINE) diff --git a/railties/test/json_params_parsing_test.rb b/railties/test/json_params_parsing_test.rb index 7fff3bb465..65ad9673ff 100644 --- a/railties/test/json_params_parsing_test.rb +++ b/railties/test/json_params_parsing_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "action_dispatch" require "active_record" diff --git a/railties/test/path_generation_test.rb b/railties/test/path_generation_test.rb index c0b03d0c15..849b183b37 100644 --- a/railties/test/path_generation_test.rb +++ b/railties/test/path_generation_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_support/core_ext/object/with_options" require "active_support/core_ext/object/json" @@ -56,12 +58,14 @@ class PathGenerationTest < ActiveSupport::TestCase Rails.logger = Logger.new nil app = Class.new(Rails::Application) { + def self.name; "ScriptNameTestApp"; end + attr_accessor :controller + def initialize super app = self @routes = TestSet.new ->(c) { app.controller = c } - secrets.secret_key_base = "foo" secrets.secret_token = "foo" end def app; routes; end diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb index 7b2551062a..854b4448a4 100644 --- a/railties/test/paths_test.rb +++ b/railties/test/paths_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "rails/paths" require "minitest/mock" @@ -274,3 +276,23 @@ class PathsTest < ActiveSupport::TestCase end end end + +class PathsIntegrationTest < ActiveSupport::TestCase + test "A failed symlink is still a valid file" do + Dir.mktmpdir do |dir| + Dir.chdir(dir) do + FileUtils.mkdir_p("foo") + File.symlink("foo/doesnotexist.rb", "foo/bar.rb") + assert_equal true, File.symlink?("foo/bar.rb") + + root = Rails::Paths::Root.new("foo") + root.add "bar.rb" + + exception = assert_raises(RuntimeError) do + root["bar.rb"].existent + end + assert_match File.expand_path("foo/bar.rb"), exception.message + end + end + end +end diff --git a/railties/test/rack_logger_test.rb b/railties/test/rack_logger_test.rb index 33b4bc6a3a..e47f30d5b6 100644 --- a/railties/test/rack_logger_test.rb +++ b/railties/test/rack_logger_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "active_support/testing/autorun" require "active_support/test_case" diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb index d795629ccd..878a238f8d 100644 --- a/railties/test/rails_info_controller_test.rb +++ b/railties/test/rails_info_controller_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" module ActionController diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb index 9f4c5bb025..43b60b9144 100644 --- a/railties/test/rails_info_test.rb +++ b/railties/test/rails_info_test.rb @@ -1,19 +1,8 @@ -require "abstract_unit" - -unless defined?(Rails) && defined?(Rails::Info) - module Rails - class Info; end - end -end +# frozen_string_literal: true -require "active_support/core_ext/kernel/reporting" +require "abstract_unit" class InfoTest < ActiveSupport::TestCase - def setup - Rails.send :remove_const, :Info - silence_warnings { load "rails/info.rb" } - end - def test_property_with_block_swallows_exceptions_and_ignores_property assert_nothing_raised do Rails::Info.module_eval do @@ -39,7 +28,7 @@ class InfoTest < ActiveSupport::TestCase def test_rails_version assert_property "Rails version", - File.read(File.realpath("../../../RAILS_VERSION", __FILE__)).chomp + File.read(File.realpath("../../RAILS_VERSION", __dir__)).chomp end def test_html_includes_middleware diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 52d691b73b..339a56c34f 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" require "stringio" require "rack/test" @@ -30,6 +32,11 @@ module RailtiesTest require "#{app_path}/config/environment" end + def migrations + migration_root = File.expand_path(ActiveRecord::Migrator.migrations_paths.first, app_path) + ActiveRecord::Migrator.migrations(migration_root) + end + test "serving sprocket's assets" do @plugin.write "app/assets/javascripts/engine.js.erb", "<%= :alert %>();" add_to_env_config "development", "config.assets.digest = false" @@ -80,31 +87,32 @@ module RailtiesTest end RUBY - add_to_config "ActiveRecord::Base.timestamped_migrations = false" - boot_rails Dir.chdir(app_path) do + # Install Active Storage migration file first so as not to affect test. + `bundle exec rake active_storage:install` output = `bundle exec rake bukkits:install:migrations` - assert File.exist?("#{app_path}/db/migrate/2_create_users.bukkits.rb") - assert File.exist?("#{app_path}/db/migrate/3_add_last_name_to_users.bukkits.rb") - assert_match(/Copied migration 2_create_users.bukkits.rb from bukkits/, output) - assert_match(/Copied migration 3_add_last_name_to_users.bukkits.rb from bukkits/, output) - assert_match(/NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/, output) - assert_equal 3, Dir["#{app_path}/db/migrate/*.rb"].length - - output = `bundle exec rake railties:install:migrations`.split("\n") + ["CreateUsers", "AddLastNameToUsers", "CreateSessions"].each do |migration_name| + assert migrations.detect { |migration| migration.name == migration_name } + end + assert_match(/Copied migration \d+_create_users\.bukkits\.rb from bukkits/, output) + assert_match(/Copied migration \d+_add_last_name_to_users\.bukkits\.rb from bukkits/, output) + assert_match(/NOTE: Migration \d+_create_sessions\.rb from bukkits has been skipped/, output) - assert_no_match(/2_create_users/, output.join("\n")) + migrations_count = Dir["#{app_path}/db/migrate/*.rb"].length - bukkits_migration_order = output.index(output.detect { |o| /NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/ =~ o }) - assert_not_nil bukkits_migration_order, "Expected migration to be skipped" + assert_equal migrations.length, migrations_count - migrations_count = Dir["#{app_path}/db/migrate/*.rb"].length - `bundle exec rake railties:install:migrations` + output = `bundle exec rake railties:install:migrations`.split("\n") assert_equal migrations_count, Dir["#{app_path}/db/migrate/*.rb"].length + + assert_no_match(/\d+_create_users/, output.join("\n")) + + bukkits_migration_order = output.index(output.detect { |o| /NOTE: Migration \d+_create_sessions\.rb from bukkits has been skipped/ =~ o }) + assert_not_nil bukkits_migration_order, "Expected migration to be skipped" end end @@ -135,8 +143,8 @@ module RailtiesTest Dir.chdir(app_path) do output = `bundle exec rake railties:install:migrations`.split("\n") - assert_match(/Copied migration \d+_create_users.bukkits.rb from bukkits/, output.first) - assert_match(/Copied migration \d+_create_blogs.blog_engine.rb from blog_engine/, output.last) + assert_match(/Copied migration \d+_create_users\.bukkits\.rb from bukkits/, output.first) + assert_match(/Copied migration \d+_create_blogs\.blog_engine\.rb from blog_engine/, output.second) end end @@ -169,10 +177,12 @@ module RailtiesTest boot_rails Dir.chdir(app_path) do + # Install Active Storage migration file first so as not to affect test. + `bundle exec rake active_storage:install` output = `bundle exec rake railties:install:migrations`.split("\n") - assert_match(/Copied migration \d+_create_users.core_engine.rb from core_engine/, output.first) - assert_match(/Copied migration \d+_create_keys.api_engine.rb from api_engine/, output.last) + assert_match(/Copied migration \d+_create_users\.core_engine\.rb from core_engine/, output.first) + assert_match(/Copied migration \d+_create_keys\.api_engine\.rb from api_engine/, output.second) end end @@ -201,9 +211,12 @@ module RailtiesTest Dir.chdir(@plugin.path) do output = `bundle exec rake app:bukkits:install:migrations` - assert File.exist?("#{app_path}/db/migrate/0_add_first_name_to_users.bukkits.rb") - assert_match(/Copied migration 0_add_first_name_to_users.bukkits.rb from bukkits/, output) - assert_equal 1, Dir["#{app_path}/db/migrate/*.rb"].length + + migration_with_engine_path = migrations.detect { |migration| migration.name == "AddFirstNameToUsers" } + assert migration_with_engine_path + assert_match(/\/db\/migrate\/\d+_add_first_name_to_users\.bukkits\.rb/, migration_with_engine_path.filename) + assert_match(/Copied migration \d+_add_first_name_to_users\.bukkits\.rb from bukkits/, output) + assert_equal migrations.length, Dir["#{app_path}/db/migrate/*.rb"].length end end @@ -503,7 +516,7 @@ YAML def call(env) response = @app.call(env) - response[2].each(&:upcase!) + response[2] = response[2].collect(&:upcase) response end end @@ -882,7 +895,17 @@ YAML end RUBY - add_to_config "isolate_namespace AppTemplate" + engine "loaded_first" do |plugin| + plugin.write "lib/loaded_first.rb", <<-RUBY + module AppTemplate + module LoadedFirst + class Engine < ::Rails::Engine + isolate_namespace(AppTemplate) + end + end + end + RUBY + end app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do end @@ -890,7 +913,7 @@ YAML boot_rails - assert_equal AppTemplate::Engine, AppTemplate.railtie_namespace + assert_equal AppTemplate::LoadedFirst::Engine, AppTemplate.railtie_namespace end test "properly reload routes" do @@ -957,14 +980,14 @@ YAML boot_rails app_generators = Rails.application.config.generators.options[:rails] - assert_equal :mongoid , app_generators[:orm] - assert_equal :liquid , app_generators[:template_engine] + assert_equal :mongoid, app_generators[:orm] + assert_equal :liquid, app_generators[:template_engine] assert_equal :test_unit, app_generators[:test_framework] generators = Bukkits::Engine.config.generators.options[:rails] assert_equal :data_mapper, generators[:orm] - assert_equal :haml , generators[:template_engine] - assert_equal :rspec , generators[:test_framework] + assert_equal :haml, generators[:template_engine] + assert_equal :rspec, generators[:test_framework] end test "engine should get default generators with ability to overwrite them" do @@ -980,10 +1003,10 @@ YAML generators = Bukkits::Engine.config.generators.options[:rails] assert_equal :active_record, generators[:orm] - assert_equal :rspec , generators[:test_framework] + assert_equal :rspec, generators[:test_framework] app_generators = Rails.application.config.generators.options[:rails] - assert_equal :test_unit , app_generators[:test_framework] + assert_equal :test_unit, app_generators[:test_framework] end test "do not create table_name_prefix method if it already exists" do @@ -1285,10 +1308,10 @@ YAML boot_rails - get("/bukkits/bukkit", {}, "SCRIPT_NAME" => "/foo") + get("/bukkits/bukkit", {}, { "SCRIPT_NAME" => "/foo" }) assert_equal "/foo/bar", last_response.body - get("/bar", {}, "SCRIPT_NAME" => "/foo") + get("/bar", {}, { "SCRIPT_NAME" => "/foo" }) assert_equal "/foo/bukkits/bukkit", last_response.body end @@ -1334,13 +1357,143 @@ YAML boot_rails - get("/bukkits/bukkit", {}, "SCRIPT_NAME" => "/foo") + get("/bukkits/bukkit", {}, { "SCRIPT_NAME" => "/foo" }) assert_equal "/foo/bar", last_response.body - get("/bar", {}, "SCRIPT_NAME" => "/foo") + get("/bar", {}, { "SCRIPT_NAME" => "/foo" }) assert_equal "/foo/bukkits/bukkit", last_response.body end + test "isolated engine can be mounted under multiple static locations" do + app_file "app/controllers/foos_controller.rb", <<-RUBY + class FoosController < ApplicationController + def through_fruits + render plain: fruit_bukkits.posts_path + end + + def through_vegetables + render plain: vegetable_bukkits.posts_path + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + scope "/fruits" do + mount Bukkits::Engine => "/bukkits", as: :fruit_bukkits + end + + scope "/vegetables" do + mount Bukkits::Engine => "/bukkits", as: :vegetable_bukkits + end + + get "/through_fruits" => "foos#through_fruits" + get "/through_vegetables" => "foos#through_vegetables" + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Engine.routes.draw do + resources :posts, only: :index + end + RUBY + + boot_rails + + get("/through_fruits") + assert_equal "/fruits/bukkits/posts", last_response.body + + get("/through_vegetables") + assert_equal "/vegetables/bukkits/posts", last_response.body + end + + test "isolated engine can be mounted under multiple dynamic locations" do + app_file "app/controllers/foos_controller.rb", <<-RUBY + class FoosController < ApplicationController + def through_fruits + render plain: fruit_bukkits.posts_path(fruit_id: 1) + end + + def through_vegetables + render plain: vegetable_bukkits.posts_path(vegetable_id: 1) + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + resources :fruits do + mount Bukkits::Engine => "/bukkits" + end + + resources :vegetables do + mount Bukkits::Engine => "/bukkits" + end + + get "/through_fruits" => "foos#through_fruits" + get "/through_vegetables" => "foos#through_vegetables" + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Engine.routes.draw do + resources :posts, only: :index + end + RUBY + + boot_rails + + get("/through_fruits") + assert_equal "/fruits/1/bukkits/posts", last_response.body + + get("/through_vegetables") + assert_equal "/vegetables/1/bukkits/posts", last_response.body + end + + test "route helpers resolve script name correctly when called with different script name from current one" do + @plugin.write "app/controllers/posts_controller.rb", <<-RUBY + class PostsController < ActionController::Base + def index + render plain: fruit_bukkits.posts_path(fruit_id: 2) + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + resources :fruits do + mount Bukkits::Engine => "/bukkits" + end + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Engine.routes.draw do + resources :posts, only: :index + end + RUBY + + boot_rails + + get("/fruits/1/bukkits/posts") + assert_equal "/fruits/2/bukkits/posts", last_response.body + end + + test "active_storage:install task works within engine" do + @plugin.write "Rakefile", <<-RUBY + APP_RAKEFILE = '#{app_path}/Rakefile' + load 'rails/tasks/engine.rake' + RUBY + + Dir.chdir(@plugin.path) do + output = `bundle exec rake app:active_storage:install` + assert $?.success?, output + + active_storage_migration = migrations.detect { |migration| migration.name == "CreateActiveStorageTables" } + assert active_storage_migration + end + end + private def app Rails.application diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb index 5c691b9ec4..8383cb3050 100644 --- a/railties/test/railties/generators_test.rb +++ b/railties/test/railties/generators_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RAILS_ISOLATED_ENGINE = true require "isolation/abstract_unit" diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb index 5838d0d7e7..48f0fbc80f 100644 --- a/railties/test/railties/mounted_engine_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module ApplicationTests @@ -111,6 +113,7 @@ module ApplicationTests @plugin.write "config/routes.rb", <<-RUBY Blog::Engine.routes.draw do resources :posts + get '/different_context', to: 'posts#different_context' get '/generate_application_route', to: 'posts#generate_application_route' get '/application_route_in_view', to: 'posts#application_route_in_view' get '/engine_polymorphic_path', to: 'posts#engine_polymorphic_path' @@ -125,6 +128,10 @@ module ApplicationTests render plain: blog.post_path(1) end + def different_context + render plain: blog.post_path(1, user: "ada") + end + def generate_application_route path = main_app.url_for(controller: "/main", action: "index", @@ -141,7 +148,7 @@ module ApplicationTests end def engine_asset_path - render inline: "<%= asset_path 'images/foo.png' %>" + render inline: "<%= asset_path 'images/foo.png', skip_pipeline: true %>" end end end @@ -196,8 +203,12 @@ module ApplicationTests get "/john/blog/posts" assert_equal "/john/blog/posts/1", last_response.body + # test generating engine route from engine with a different context + get "/john/blog/different_context" + assert_equal "/ada/blog/posts/1", last_response.body + # test generating engine's route from engine with default_url_options - get "/john/blog/posts", {}, "SCRIPT_NAME" => "/foo" + get "/john/blog/posts", {}, { "SCRIPT_NAME" => "/foo" } assert_equal "/foo/john/blog/posts/1", last_response.body # test generating engine's route from application @@ -211,10 +222,10 @@ module ApplicationTests assert_equal "/john/blog/posts", last_response.body # test generating engine's route from application with default_url_options - get "/engine_route", {}, "SCRIPT_NAME" => "/foo" + get "/engine_route", {}, { "SCRIPT_NAME" => "/foo" } assert_equal "/foo/anonymous/blog/posts", last_response.body - get "/url_for_engine_route", {}, "SCRIPT_NAME" => "/foo" + get "/url_for_engine_route", {}, { "SCRIPT_NAME" => "/foo" } assert_equal "/foo/john/blog/posts", last_response.body # test generating application's route from engine @@ -232,14 +243,14 @@ module ApplicationTests assert_equal "/anonymous/blog/posts/1", last_response.body # test generating engine's route from other engine with default_url_options - get "/metrics/generate_blog_route", {}, "SCRIPT_NAME" => "/foo" + get "/metrics/generate_blog_route", {}, { "SCRIPT_NAME" => "/foo" } assert_equal "/foo/anonymous/blog/posts/1", last_response.body - get "/metrics/generate_blog_route_in_view", {}, "SCRIPT_NAME" => "/foo" + get "/metrics/generate_blog_route_in_view", {}, { "SCRIPT_NAME" => "/foo" } assert_equal "/foo/anonymous/blog/posts/1", last_response.body # test generating application's route from engine with default_url_options - get "/someone/blog/generate_application_route", {}, "SCRIPT_NAME" => "/foo" + get "/someone/blog/generate_application_route", {}, { "SCRIPT_NAME" => "/foo" } assert_equal "/foo/", last_response.body # test polymorphic routes diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb index 30cd525266..359ab0fdae 100644 --- a/railties/test/railties/railtie_test.rb +++ b/railties/test/railties/railtie_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "isolation/abstract_unit" module RailtiesTest diff --git a/railties/test/secrets_test.rb b/railties/test/secrets_test.rb new file mode 100644 index 0000000000..06877bc76a --- /dev/null +++ b/railties/test/secrets_test.rb @@ -0,0 +1,178 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "rails/secrets" + +class Rails::SecretsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + setup :build_app + teardown :teardown_app + + test "setting read to false skips parsing" do + run_secrets_generator do + Rails::Secrets.write(<<-end_of_secrets) + production: + yeah_yeah: lets-walk-in-the-cool-evening-light + end_of_secrets + + add_to_env_config("production", "config.read_encrypted_secrets = false") + app("production") + + assert_not Rails.application.secrets.yeah_yeah + end + end + + test "raises when reading secrets without a key" do + run_secrets_generator do + FileUtils.rm("config/secrets.yml.key") + + assert_raises Rails::Secrets::MissingKeyError do + Rails::Secrets.key + end + end + end + + test "reading with ENV variable" do + run_secrets_generator do + begin + old_key = ENV["RAILS_MASTER_KEY"] + ENV["RAILS_MASTER_KEY"] = IO.binread("config/secrets.yml.key").strip + FileUtils.rm("config/secrets.yml.key") + + assert_match "# production:\n# external_api_key:", Rails::Secrets.read + ensure + ENV["RAILS_MASTER_KEY"] = old_key + end + end + end + + test "reading from key file" do + run_secrets_generator do + File.binwrite("config/secrets.yml.key", "00112233445566778899aabbccddeeff") + + assert_equal "00112233445566778899aabbccddeeff", Rails::Secrets.key + end + end + + test "editing" do + run_secrets_generator do + decrypted_path = nil + + Rails::Secrets.read_for_editing do |tmp_path| + decrypted_path = tmp_path + + assert_match(/# production:\n# external_api_key/, File.read(tmp_path)) + + File.write(tmp_path, "Empty streets, empty nights. The Downtown Lights.") + end + + assert_not File.exist?(decrypted_path) + assert_equal "Empty streets, empty nights. The Downtown Lights.", Rails::Secrets.read + end + end + + test "merging secrets with encrypted precedence" do + run_secrets_generator do + File.write("config/secrets.yml", <<-end_of_secrets) + production: + yeah_yeah: lets-go-walking-down-this-empty-street + end_of_secrets + + Rails::Secrets.write(<<-end_of_secrets) + production: + yeah_yeah: lets-walk-in-the-cool-evening-light + end_of_secrets + + add_to_env_config("production", "config.read_encrypted_secrets = true") + app("production") + + assert_equal "lets-walk-in-the-cool-evening-light", Rails.application.secrets.yeah_yeah + end + end + + test "refer secrets inside env config" do + run_secrets_generator do + Rails::Secrets.write(<<-end_of_yaml) + production: + some_secret: yeah yeah + end_of_yaml + + add_to_env_config "production", <<-end_of_config + config.dereferenced_secret = Rails.application.secrets.some_secret + end_of_config + + app("production") + + assert_equal "yeah yeah", Rails.application.config.dereferenced_secret + end + end + + test "do not update secrets.yml.enc when secretes do not change" do + run_secrets_generator do + Rails::Secrets.read_for_editing do |tmp_path| + File.write(tmp_path, "Empty streets, empty nights. The Downtown Lights.") + end + + FileUtils.cp("config/secrets.yml.enc", "config/secrets.yml.enc.bk") + + Rails::Secrets.read_for_editing do |tmp_path| + File.write(tmp_path, "Empty streets, empty nights. The Downtown Lights.") + end + + assert_equal File.read("config/secrets.yml.enc.bk"), File.read("config/secrets.yml.enc") + end + end + + test "can read secrets written in binary" do + run_secrets_generator do + secrets = <<-end_of_secrets + production: + api_key: 00112233445566778899aabbccddeeff… + end_of_secrets + + Rails::Secrets.write(secrets.dup.force_encoding(Encoding::ASCII_8BIT)) + + Rails::Secrets.read_for_editing do |tmp_path| + assert_match(/production:\n\s*api_key: 00112233445566778899aabbccddeeff…\n/, File.read(tmp_path)) + end + + app("production") + + assert_equal "00112233445566778899aabbccddeeff…", Rails.application.secrets.api_key + end + end + + test "can read secrets written in non-binary" do + run_secrets_generator do + secrets = <<-end_of_secrets + production: + api_key: 00112233445566778899aabbccddeeff… + end_of_secrets + + Rails::Secrets.write(secrets) + + Rails::Secrets.read_for_editing do |tmp_path| + assert_equal(secrets.dup.force_encoding(Encoding::ASCII_8BIT), IO.binread(tmp_path)) + end + + app("production") + + assert_equal "00112233445566778899aabbccddeeff…", Rails.application.secrets.api_key + end + end + + private + def run_secrets_generator + Dir.chdir(app_path) do + File.write("config/secrets.yml.key", "f731758c639da2604dfb6bf3d1025de8") + File.write("config/secrets.yml.enc", "sEB0mHxDbeP1/KdnMk00wyzPFACl9K6t0cZWn5/Mfx/YbTHvnI07vrneqHg9kaH3wOS7L6pIQteu1P077OtE4BSx/ZRc/sgQPHyWu/tXsrfHqnPNpayOF/XZqizE91JacSFItNMWpuPsp9ynbzz+7cGhoB1S4aPNIU6u0doMrzdngDbijsaAFJmsHIQh6t/QHoJx--8aMoE0PvUWmw1Iqz--ldFqnM/K0g9k17M8PKoN/Q==") + + add_to_config <<-RUBY + config.read_encrypted_secrets = true + RUBY + + yield + end + end +end diff --git a/railties/test/test_unit/reporter_test.rb b/railties/test/test_unit/reporter_test.rb index e22c939981..ad852d0f35 100644 --- a/railties/test/test_unit/reporter_test.rb +++ b/railties/test/test_unit/reporter_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" require "rails/test_unit/reporter" require "minitest/mock" @@ -16,7 +18,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase @reporter.record(failed_test) @reporter.report - assert_match %r{^bin/rails test .*test/test_unit/reporter_test.rb:\d+$}, @output.string + assert_match %r{^bin/rails test .*test/test_unit/reporter_test\.rb:\d+$}, @output.string assert_rerun_snippet_count 1 end @@ -52,7 +54,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase @reporter.record(failed_test) @reporter.report - assert_match %r{^bin/test .*test/test_unit/reporter_test.rb:\d+$}, @output.string + assert_match %r{^bin/test .*test/test_unit/reporter_test\.rb:\d+$}, @output.string ensure Rails::TestUnitReporter.executable = original_executable end @@ -62,7 +64,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase @reporter.record(failed_test) @reporter.report - expect = %r{\AF\n\nFailure:\nTestUnitReporterTest::ExampleTest#woot \[[^\]]+\]:\nboo\n\nbin/rails test test/test_unit/reporter_test.rb:\d+\n\n\z} + expect = %r{\AF\n\nFailure:\nTestUnitReporterTest::ExampleTest#woot \[[^\]]+\]:\nboo\n\nbin/rails test test/test_unit/reporter_test\.rb:\d+\n\n\z} assert_match expect, @output.string end @@ -70,7 +72,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase @reporter.record(errored_test) @reporter.report - expect = %r{\AE\n\nError:\nTestUnitReporterTest::ExampleTest#woot:\nArgumentError: wups\n No backtrace\n\nbin/rails test .*test/test_unit/reporter_test.rb:\d+\n\n\z} + expect = %r{\AE\n\nError:\nTestUnitReporterTest::ExampleTest#woot:\nArgumentError: wups\n \n\nbin/rails test .*test/test_unit/reporter_test\.rb:\d+\n\n\z} assert_match expect, @output.string end @@ -79,7 +81,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase verbose.record(skipped_test) verbose.report - expect = %r{\ATestUnitReporterTest::ExampleTest#woot = 10\.00 s = S\n\n\nSkipped:\nTestUnitReporterTest::ExampleTest#woot \[[^\]]+\]:\nskipchurches, misstemples\n\nbin/rails test test/test_unit/reporter_test.rb:\d+\n\n\z} + expect = %r{\ATestUnitReporterTest::ExampleTest#woot = 10\.00 s = S\n\n\nSkipped:\nTestUnitReporterTest::ExampleTest#woot \[[^\]]+\]:\nskipchurches, misstemples\n\nbin/rails test test/test_unit/reporter_test\.rb:\d+\n\n\z} assert_match expect, @output.string end @@ -150,7 +152,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase colored = Rails::TestUnitReporter.new @output, color: true, output_inline: true colored.record(errored_test) - expected = %r{\e\[31mE\e\[0m\n\n\e\[31mError:\nTestUnitReporterTest::ExampleTest#woot:\nArgumentError: wups\n No backtrace\n\e\[0m} + expected = %r{\e\[31mE\e\[0m\n\n\e\[31mError:\nTestUnitReporterTest::ExampleTest#woot:\nArgumentError: wups\n \n\e\[0m} assert_match expected, @output.string end end @@ -171,8 +173,11 @@ class TestUnitReporterTest < ActiveSupport::TestCase end def errored_test + error = ArgumentError.new("wups") + error.set_backtrace([ "some_test.rb:4" ]) + et = ExampleTest.new(:woot) - et.failures << Minitest::UnexpectedError.new(ArgumentError.new("wups")) + et.failures << Minitest::UnexpectedError.new(error) et end diff --git a/railties/test/version_test.rb b/railties/test/version_test.rb index 86a482e091..17a024fe7f 100644 --- a/railties/test/version_test.rb +++ b/railties/test/version_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "abstract_unit" class VersionTest < ActiveSupport::TestCase |