Browse Source

Update Mastodon to Rails 6.1 (#15910)

* Update devise-two-factor to unreleased fork for Rails 6 support

Update tests to match new `rotp` version.

* Update nsa gem to unreleased fork for Rails 6 support

* Update rails to 6.1.3 and rails-i18n to 6.0

* Update to unreleased fork of pluck_each for Ruby 6 support

* Run "rails app:update"

* Add missing ActiveStorage config file

* Use config.ssl_options instead of removed ApplicationController#force_ssl

Disabled force_ssl-related tests as they do not seem to be easily testable
anymore.

* Fix nonce directives by removing Rails 5 specific monkey-patching

* Fix fixture_file_upload deprecation warning

* Fix yield-based test failing with Rails 6

* Use Rails 6's index_with when possible

* Use ActiveRecord::Cache::Store#delete_multi from Rails 6

This will yield better performances when deleting an account

* Disable Rails 6.1's automatic preload link headers

Since Rails 6.1, ActionView adds preload links for javascript files
in the Links header per default.

In our case, that will bloat headers too much and potentially cause
issues with reverse proxies. Furhermore, we don't need those links,
as we already output them as HTML link tags.

* Switch to Rails 6.0 default config

* Switch to Rails 6.1 default config

* Do not include autoload paths in the load path
Claire 3 years ago
parent
commit
cbd0ee1d07

+ 5 - 5
Gemfile

@@ -6,7 +6,7 @@ ruby '>= 2.5.0', '< 3.0.0'
 gem 'pkg-config', '~> 1.4'
 gem 'pkg-config', '~> 1.4'
 
 
 gem 'puma', '~> 5.2'
 gem 'puma', '~> 5.2'
-gem 'rails', '~> 5.2.4.5'
+gem 'rails', '~> 6.1.3'
 gem 'sprockets', '~> 3.7.2'
 gem 'sprockets', '~> 3.7.2'
 gem 'thor', '~> 1.1'
 gem 'thor', '~> 1.1'
 gem 'rack', '~> 2.2.3'
 gem 'rack', '~> 2.2.3'
@@ -34,7 +34,7 @@ gem 'iso-639'
 gem 'chewy', '~> 5.2'
 gem 'chewy', '~> 5.2'
 gem 'cld3', '~> 3.4.1'
 gem 'cld3', '~> 3.4.1'
 gem 'devise', '~> 4.7'
 gem 'devise', '~> 4.7'
-gem 'devise-two-factor', '~> 3.1'
+gem 'devise-two-factor', git: 'https://github.com/ClearlyClaire/devise-two-factor', ref: '594bb8a32e6f94df7e5ba7c9399eaf9ff25bac0d'
 
 
 group :pam_authentication, optional: true do
 group :pam_authentication, optional: true do
   gem 'devise_pam_authenticatable2', '~> 9.2'
   gem 'devise_pam_authenticatable2', '~> 9.2'
@@ -65,7 +65,7 @@ gem 'link_header', '~> 0.0'
 gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar'
 gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar'
 gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
 gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
 gem 'nokogiri', '~> 1.11'
 gem 'nokogiri', '~> 1.11'
-gem 'nsa', '~> 0.2'
+gem 'nsa', git: 'https://github.com/Gargron/nsa', ref: 'd1079e0cdafdfed7f9f35478d13b9bdaa65965c0'
 gem 'oj', '~> 3.11'
 gem 'oj', '~> 3.11'
 gem 'ox', '~> 2.14'
 gem 'ox', '~> 2.14'
 gem 'parslet'
 gem 'parslet'
@@ -75,7 +75,7 @@ gem 'pundit', '~> 2.1'
 gem 'premailer-rails'
 gem 'premailer-rails'
 gem 'rack-attack', '~> 6.5'
 gem 'rack-attack', '~> 6.5'
 gem 'rack-cors', '~> 1.1', require: 'rack/cors'
 gem 'rack-cors', '~> 1.1', require: 'rack/cors'
-gem 'rails-i18n', '~> 5.1'
+gem 'rails-i18n', '~> 6.0'
 gem 'rails-settings-cached', '~> 0.6'
 gem 'rails-settings-cached', '~> 0.6'
 gem 'redis', '~> 4.2', require: ['redis', 'redis/connection/hiredis']
 gem 'redis', '~> 4.2', require: ['redis', 'redis/connection/hiredis']
 gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
 gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
@@ -159,4 +159,4 @@ gem 'concurrent-ruby', require: false
 gem 'connection_pool', require: false
 gem 'connection_pool', require: false
 
 
 gem 'xorcist', '~> 1.1'
 gem 'xorcist', '~> 1.1'
-gem 'pluck_each', '~> 0.1.3'
+gem 'pluck_each', git: 'https://github.com/nsommer/pluck_each', ref: '73be0947c52fc54bf6d7085378db008358aac5eb'

+ 109 - 72
Gemfile.lock

@@ -1,3 +1,26 @@
+GIT
+  remote: https://github.com/ClearlyClaire/devise-two-factor
+  revision: 594bb8a32e6f94df7e5ba7c9399eaf9ff25bac0d
+  ref: 594bb8a32e6f94df7e5ba7c9399eaf9ff25bac0d
+  specs:
+    devise-two-factor (3.1.0)
+      activesupport (< 7.0)
+      attr_encrypted (>= 1.3, < 4, != 2)
+      devise
+      railties (< 7.0)
+      rotp (~> 6)
+
+GIT
+  remote: https://github.com/Gargron/nsa
+  revision: d1079e0cdafdfed7f9f35478d13b9bdaa65965c0
+  ref: d1079e0cdafdfed7f9f35478d13b9bdaa65965c0
+  specs:
+    nsa (0.2.8)
+      activesupport (>= 4.2, < 7)
+      concurrent-ruby (~> 1.0, >= 1.0.2)
+      sidekiq (>= 3.5)
+      statsd-ruby (~> 1.4, >= 1.4.0)
+
 GIT
 GIT
   remote: https://github.com/ianheggie/health_check
   remote: https://github.com/ianheggie/health_check
   revision: 0b799ead604f900ed50685e9b2d469cd2befba5b
   revision: 0b799ead604f900ed50685e9b2d469cd2befba5b
@@ -6,6 +29,15 @@ GIT
     health_check (4.0.0.pre)
     health_check (4.0.0.pre)
       rails (>= 4.0)
       rails (>= 4.0)
 
 
+GIT
+  remote: https://github.com/nsommer/pluck_each
+  revision: 73be0947c52fc54bf6d7085378db008358aac5eb
+  ref: 73be0947c52fc54bf6d7085378db008358aac5eb
+  specs:
+    pluck_each (0.1.3)
+      activerecord (>= 6.1.0)
+      activesupport (>= 6.1.0)
+
 GIT
 GIT
   remote: https://github.com/witgo/nilsimsa
   remote: https://github.com/witgo/nilsimsa
   revision: fd184883048b922b176939f851338d0a4971a532
   revision: fd184883048b922b176939f851338d0a4971a532
@@ -16,53 +48,71 @@ GIT
 GEM
 GEM
   remote: https://rubygems.org/
   remote: https://rubygems.org/
   specs:
   specs:
-    actioncable (5.2.4.5)
-      actionpack (= 5.2.4.5)
+    actioncable (6.1.3)
+      actionpack (= 6.1.3)
+      activesupport (= 6.1.3)
       nio4r (~> 2.0)
       nio4r (~> 2.0)
       websocket-driver (>= 0.6.1)
       websocket-driver (>= 0.6.1)
-    actionmailer (5.2.4.5)
-      actionpack (= 5.2.4.5)
-      actionview (= 5.2.4.5)
-      activejob (= 5.2.4.5)
+    actionmailbox (6.1.3)
+      actionpack (= 6.1.3)
+      activejob (= 6.1.3)
+      activerecord (= 6.1.3)
+      activestorage (= 6.1.3)
+      activesupport (= 6.1.3)
+      mail (>= 2.7.1)
+    actionmailer (6.1.3)
+      actionpack (= 6.1.3)
+      actionview (= 6.1.3)
+      activejob (= 6.1.3)
+      activesupport (= 6.1.3)
       mail (~> 2.5, >= 2.5.4)
       mail (~> 2.5, >= 2.5.4)
       rails-dom-testing (~> 2.0)
       rails-dom-testing (~> 2.0)
-    actionpack (5.2.4.5)
-      actionview (= 5.2.4.5)
-      activesupport (= 5.2.4.5)
-      rack (~> 2.0, >= 2.0.8)
+    actionpack (6.1.3)
+      actionview (= 6.1.3)
+      activesupport (= 6.1.3)
+      rack (~> 2.0, >= 2.0.9)
       rack-test (>= 0.6.3)
       rack-test (>= 0.6.3)
       rails-dom-testing (~> 2.0)
       rails-dom-testing (~> 2.0)
-      rails-html-sanitizer (~> 1.0, >= 1.0.2)
-    actionview (5.2.4.5)
-      activesupport (= 5.2.4.5)
+      rails-html-sanitizer (~> 1.0, >= 1.2.0)
+    actiontext (6.1.3)
+      actionpack (= 6.1.3)
+      activerecord (= 6.1.3)
+      activestorage (= 6.1.3)
+      activesupport (= 6.1.3)
+      nokogiri (>= 1.8.5)
+    actionview (6.1.3)
+      activesupport (= 6.1.3)
       builder (~> 3.1)
       builder (~> 3.1)
       erubi (~> 1.4)
       erubi (~> 1.4)
       rails-dom-testing (~> 2.0)
       rails-dom-testing (~> 2.0)
-      rails-html-sanitizer (~> 1.0, >= 1.0.3)
+      rails-html-sanitizer (~> 1.1, >= 1.2.0)
     active_model_serializers (0.10.12)
     active_model_serializers (0.10.12)
       actionpack (>= 4.1, < 6.2)
       actionpack (>= 4.1, < 6.2)
       activemodel (>= 4.1, < 6.2)
       activemodel (>= 4.1, < 6.2)
       case_transform (>= 0.2)
       case_transform (>= 0.2)
       jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
       jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
     active_record_query_trace (1.8)
     active_record_query_trace (1.8)
-    activejob (5.2.4.5)
-      activesupport (= 5.2.4.5)
+    activejob (6.1.3)
+      activesupport (= 6.1.3)
       globalid (>= 0.3.6)
       globalid (>= 0.3.6)
-    activemodel (5.2.4.5)
-      activesupport (= 5.2.4.5)
-    activerecord (5.2.4.5)
-      activemodel (= 5.2.4.5)
-      activesupport (= 5.2.4.5)
-      arel (>= 9.0)
-    activestorage (5.2.4.5)
-      actionpack (= 5.2.4.5)
-      activerecord (= 5.2.4.5)
+    activemodel (6.1.3)
+      activesupport (= 6.1.3)
+    activerecord (6.1.3)
+      activemodel (= 6.1.3)
+      activesupport (= 6.1.3)
+    activestorage (6.1.3)
+      actionpack (= 6.1.3)
+      activejob (= 6.1.3)
+      activerecord (= 6.1.3)
+      activesupport (= 6.1.3)
       marcel (~> 0.3.1)
       marcel (~> 0.3.1)
-    activesupport (5.2.4.5)
+      mimemagic (~> 0.3.2)
+    activesupport (6.1.3)
       concurrent-ruby (~> 1.0, >= 1.0.2)
       concurrent-ruby (~> 1.0, >= 1.0.2)
-      i18n (>= 0.7, < 2)
-      minitest (~> 5.1)
-      tzinfo (~> 1.1)
+      i18n (>= 1.6, < 2)
+      minitest (>= 5.1)
+      tzinfo (~> 2.0)
+      zeitwerk (~> 2.3)
     addressable (2.7.0)
     addressable (2.7.0)
       public_suffix (>= 2.0.2, < 5.0)
       public_suffix (>= 2.0.2, < 5.0)
     airbrussh (1.4.0)
     airbrussh (1.4.0)
@@ -71,7 +121,6 @@ GEM
     annotate (3.1.1)
     annotate (3.1.1)
       activerecord (>= 3.2, < 7.0)
       activerecord (>= 3.2, < 7.0)
       rake (>= 10.4, < 14.0)
       rake (>= 10.4, < 14.0)
-    arel (9.0.0)
     ast (2.4.2)
     ast (2.4.2)
     attr_encrypted (3.1.0)
     attr_encrypted (3.1.0)
       encryptor (~> 3.0.0)
       encryptor (~> 3.0.0)
@@ -175,12 +224,6 @@ GEM
       railties (>= 4.1.0)
       railties (>= 4.1.0)
       responders
       responders
       warden (~> 1.2.3)
       warden (~> 1.2.3)
-    devise-two-factor (3.1.0)
-      activesupport (< 6.1)
-      attr_encrypted (>= 1.3, < 4, != 2)
-      devise (~> 4.0)
-      railties (< 6.1)
-      rotp (~> 2.0)
     devise_pam_authenticatable2 (9.2.0)
     devise_pam_authenticatable2 (9.2.0)
       devise (>= 4.0.0)
       devise (>= 4.0.0)
       rpam2 (~> 4.0)
       rpam2 (~> 4.0)
@@ -370,11 +413,6 @@ GEM
       racc (~> 1.4)
       racc (~> 1.4)
     nokogumbo (2.0.4)
     nokogumbo (2.0.4)
       nokogiri (~> 1.8, >= 1.8.4)
       nokogiri (~> 1.8, >= 1.8.4)
-    nsa (0.2.7)
-      activesupport (>= 4.2, < 6)
-      concurrent-ruby (~> 1.0, >= 1.0.2)
-      sidekiq (>= 3.5)
-      statsd-ruby (~> 1.4, >= 1.4.0)
     oj (3.11.3)
     oj (3.11.3)
     omniauth (1.9.1)
     omniauth (1.9.1)
       hashie (>= 3.4.6)
       hashie (>= 3.4.6)
@@ -414,9 +452,6 @@ GEM
     pghero (2.8.0)
     pghero (2.8.0)
       activerecord (>= 5)
       activerecord (>= 5)
     pkg-config (1.4.5)
     pkg-config (1.4.5)
-    pluck_each (0.1.3)
-      activerecord (> 3.2.0)
-      activesupport (> 3.0.0)
     posix-spawn (0.3.15)
     posix-spawn (0.3.15)
     premailer (1.14.2)
     premailer (1.14.2)
       addressable
       addressable
@@ -450,18 +485,20 @@ GEM
       rack
       rack
     rack-test (1.1.0)
     rack-test (1.1.0)
       rack (>= 1.0, < 3)
       rack (>= 1.0, < 3)
-    rails (5.2.4.5)
-      actioncable (= 5.2.4.5)
-      actionmailer (= 5.2.4.5)
-      actionpack (= 5.2.4.5)
-      actionview (= 5.2.4.5)
-      activejob (= 5.2.4.5)
-      activemodel (= 5.2.4.5)
-      activerecord (= 5.2.4.5)
-      activestorage (= 5.2.4.5)
-      activesupport (= 5.2.4.5)
-      bundler (>= 1.3.0)
-      railties (= 5.2.4.5)
+    rails (6.1.3)
+      actioncable (= 6.1.3)
+      actionmailbox (= 6.1.3)
+      actionmailer (= 6.1.3)
+      actionpack (= 6.1.3)
+      actiontext (= 6.1.3)
+      actionview (= 6.1.3)
+      activejob (= 6.1.3)
+      activemodel (= 6.1.3)
+      activerecord (= 6.1.3)
+      activestorage (= 6.1.3)
+      activesupport (= 6.1.3)
+      bundler (>= 1.15.0)
+      railties (= 6.1.3)
       sprockets-rails (>= 2.0.0)
       sprockets-rails (>= 2.0.0)
     rails-controller-testing (1.0.5)
     rails-controller-testing (1.0.5)
       actionpack (>= 5.0.1.rc1)
       actionpack (>= 5.0.1.rc1)
@@ -472,17 +509,17 @@ GEM
       nokogiri (>= 1.6)
       nokogiri (>= 1.6)
     rails-html-sanitizer (1.3.0)
     rails-html-sanitizer (1.3.0)
       loofah (~> 2.3)
       loofah (~> 2.3)
-    rails-i18n (5.1.3)
+    rails-i18n (6.0.0)
       i18n (>= 0.7, < 2)
       i18n (>= 0.7, < 2)
-      railties (>= 5.0, < 6)
+      railties (>= 6.0.0, < 7)
     rails-settings-cached (0.6.6)
     rails-settings-cached (0.6.6)
       rails (>= 4.2.0)
       rails (>= 4.2.0)
-    railties (5.2.4.5)
-      actionpack (= 5.2.4.5)
-      activesupport (= 5.2.4.5)
+    railties (6.1.3)
+      actionpack (= 6.1.3)
+      activesupport (= 6.1.3)
       method_source
       method_source
       rake (>= 0.8.7)
       rake (>= 0.8.7)
-      thor (>= 0.19.0, < 2.0)
+      thor (~> 1.0)
     rainbow (3.0.0)
     rainbow (3.0.0)
     rake (13.0.3)
     rake (13.0.3)
     rdf (3.1.13)
     rdf (3.1.13)
@@ -500,7 +537,7 @@ GEM
       actionpack (>= 5.0)
       actionpack (>= 5.0)
       railties (>= 5.0)
       railties (>= 5.0)
     rexml (3.2.4)
     rexml (3.2.4)
-    rotp (2.1.2)
+    rotp (6.2.0)
     rpam2 (4.0.2)
     rpam2 (4.0.2)
     rqrcode (1.2.0)
     rqrcode (1.2.0)
       chunky_png (~> 1.0)
       chunky_png (~> 1.0)
@@ -600,7 +637,7 @@ GEM
       net-scp (>= 1.1.2)
       net-scp (>= 1.1.2)
       net-ssh (>= 2.8.0)
       net-ssh (>= 2.8.0)
     stackprof (0.2.16)
     stackprof (0.2.16)
-    statsd-ruby (1.4.0)
+    statsd-ruby (1.5.0)
     stoplight (2.2.1)
     stoplight (2.2.1)
     streamio-ffmpeg (3.0.2)
     streamio-ffmpeg (3.0.2)
       multi_json (~> 1.8)
       multi_json (~> 1.8)
@@ -612,7 +649,6 @@ GEM
     terrapin (0.6.0)
     terrapin (0.6.0)
       climate_control (>= 0.0.3, < 1.0)
       climate_control (>= 0.0.3, < 1.0)
     thor (1.1.0)
     thor (1.1.0)
-    thread_safe (0.3.6)
     thwait (0.2.0)
     thwait (0.2.0)
       e2mmap
       e2mmap
     tilt (2.0.10)
     tilt (2.0.10)
@@ -632,8 +668,8 @@ GEM
     twitter-text (3.1.0)
     twitter-text (3.1.0)
       idn-ruby
       idn-ruby
       unf (~> 0.1.0)
       unf (~> 0.1.0)
-    tzinfo (1.2.9)
-      thread_safe (~> 0.1)
+    tzinfo (2.0.4)
+      concurrent-ruby (~> 1.0)
     tzinfo-data (1.2021.1)
     tzinfo-data (1.2021.1)
       tzinfo (>= 1.0.0)
       tzinfo (>= 1.0.0)
     unf (0.1.4)
     unf (0.1.4)
@@ -672,6 +708,7 @@ GEM
     xorcist (1.1.2)
     xorcist (1.1.2)
     xpath (3.2.0)
     xpath (3.2.0)
       nokogiri (~> 1.8)
       nokogiri (~> 1.8)
+    zeitwerk (2.4.2)
 
 
 PLATFORMS
 PLATFORMS
   ruby
   ruby
@@ -703,7 +740,7 @@ DEPENDENCIES
   concurrent-ruby
   concurrent-ruby
   connection_pool
   connection_pool
   devise (~> 4.7)
   devise (~> 4.7)
-  devise-two-factor (~> 3.1)
+  devise-two-factor!
   devise_pam_authenticatable2 (~> 9.2)
   devise_pam_authenticatable2 (~> 9.2)
   discard (~> 1.2)
   discard (~> 1.2)
   doorkeeper (~> 5.5)
   doorkeeper (~> 5.5)
@@ -741,7 +778,7 @@ DEPENDENCIES
   net-ldap (~> 0.17)
   net-ldap (~> 0.17)
   nilsimsa!
   nilsimsa!
   nokogiri (~> 1.11)
   nokogiri (~> 1.11)
-  nsa (~> 0.2)
+  nsa!
   oj (~> 3.11)
   oj (~> 3.11)
   omniauth (~> 1.9)
   omniauth (~> 1.9)
   omniauth-cas (~> 2.0)
   omniauth-cas (~> 2.0)
@@ -756,7 +793,7 @@ DEPENDENCIES
   pg (~> 1.2)
   pg (~> 1.2)
   pghero (~> 2.8)
   pghero (~> 2.8)
   pkg-config (~> 1.4)
   pkg-config (~> 1.4)
-  pluck_each (~> 0.1.3)
+  pluck_each!
   posix-spawn
   posix-spawn
   premailer-rails
   premailer-rails
   private_address_check (~> 0.5)
   private_address_check (~> 0.5)
@@ -767,9 +804,9 @@ DEPENDENCIES
   rack (~> 2.2.3)
   rack (~> 2.2.3)
   rack-attack (~> 6.5)
   rack-attack (~> 6.5)
   rack-cors (~> 1.1)
   rack-cors (~> 1.1)
-  rails (~> 5.2.4.5)
+  rails (~> 6.1.3)
   rails-controller-testing (~> 1.0)
   rails-controller-testing (~> 1.0)
-  rails-i18n (~> 5.1)
+  rails-i18n (~> 6.0)
   rails-settings-cached (~> 0.6)
   rails-settings-cached (~> 0.6)
   rdf-normalize (~> 0.4)
   rdf-normalize (~> 0.4)
   redis (~> 4.2)
   redis (~> 4.2)

+ 0 - 6
app/controllers/application_controller.rb

@@ -5,8 +5,6 @@ class ApplicationController < ActionController::Base
   # For APIs, you may want to use :null_session instead.
   # For APIs, you may want to use :null_session instead.
   protect_from_forgery with: :exception
   protect_from_forgery with: :exception
 
 
-  force_ssl if: :https_enabled?
-
   include Localized
   include Localized
   include UserTrackingConcern
   include UserTrackingConcern
   include SessionTrackingConcern
   include SessionTrackingConcern
@@ -42,10 +40,6 @@ class ApplicationController < ActionController::Base
 
 
   private
   private
 
 
-  def https_enabled?
-    Rails.env.production? && !request.path.start_with?('/health') && !request.headers["Host"].end_with?(".onion")
-  end
-
   def authorized_fetch_mode?
   def authorized_fetch_mode?
     ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.whitelist_mode
     ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.whitelist_mode
   end
   end

+ 1 - 1
app/lib/delivery_failure_tracker.rb

@@ -29,7 +29,7 @@ class DeliveryFailureTracker
 
 
   class << self
   class << self
     def without_unavailable(urls)
     def without_unavailable(urls)
-      unavailable_domains_map = Rails.cache.fetch('unavailable_domains') { UnavailableDomain.pluck(:domain).each_with_object({}) { |domain, hash| hash[domain] = true } }
+      unavailable_domains_map = Rails.cache.fetch('unavailable_domains') { UnavailableDomain.pluck(:domain).index_with(true) }
 
 
       urls.reject do |url|
       urls.reject do |url|
         host = Addressable::URI.parse(url).normalized_host
         host = Addressable::URI.parse(url).normalized_host

+ 6 - 6
app/lib/feed_manager.rb

@@ -533,12 +533,12 @@ class FeedManager
       arr
       arr
     end
     end
 
 
-    crutches[:following]       = Follow.where(account_id: receiver_id, target_account_id: statuses.map(&:in_reply_to_account_id).compact).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
-    crutches[:hiding_reblogs]  = Follow.where(account_id: receiver_id, target_account_id: statuses.map { |s| s.account_id if s.reblog? }.compact, show_reblogs: false).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
-    crutches[:blocking]        = Block.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
-    crutches[:muting]          = Mute.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
-    crutches[:domain_blocking] = AccountDomainBlock.where(account_id: receiver_id, domain: statuses.map { |s| s.reblog&.account&.domain }.compact).pluck(:domain).each_with_object({}) { |domain, mapping| mapping[domain] = true }
-    crutches[:blocked_by]      = Block.where(target_account_id: receiver_id, account_id: statuses.map { |s| s.reblog&.account_id }.compact).pluck(:account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
+    crutches[:following]       = Follow.where(account_id: receiver_id, target_account_id: statuses.map(&:in_reply_to_account_id).compact).pluck(:target_account_id).index_with(true)
+    crutches[:hiding_reblogs]  = Follow.where(account_id: receiver_id, target_account_id: statuses.map { |s| s.account_id if s.reblog? }.compact, show_reblogs: false).pluck(:target_account_id).index_with(true)
+    crutches[:blocking]        = Block.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).index_with(true)
+    crutches[:muting]          = Mute.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).index_with(true)
+    crutches[:domain_blocking] = AccountDomainBlock.where(account_id: receiver_id, domain: statuses.map { |s| s.reblog&.account&.domain }.compact).pluck(:domain).index_with(true)
+    crutches[:blocked_by]      = Block.where(target_account_id: receiver_id, account_id: statuses.map { |s| s.reblog&.account_id }.compact).pluck(:account_id).index_with(true)
 
 
     crutches
     crutches
   end
   end

+ 1 - 1
app/lib/settings/scoped_settings.rb

@@ -63,7 +63,7 @@ module Settings
 
 
     class << self
     class << self
       def default_settings
       def default_settings
-        defaulting = DEFAULTING_TO_UNSCOPED.each_with_object({}) { |k, h| h[k] = Setting[k] }
+        defaulting = DEFAULTING_TO_UNSCOPED.index_with { |k| Setting[k] }
         Setting.default_settings.merge!(defaulting)
         Setting.default_settings.merge!(defaulting)
       end
       end
     end
     end

+ 1 - 1
app/models/concerns/account_interactions.rb

@@ -67,7 +67,7 @@ module AccountInteractions
     private
     private
 
 
     def follow_mapping(query, field)
     def follow_mapping(query, field)
-      query.pluck(field).each_with_object({}) { |id, mapping| mapping[id] = true }
+      query.pluck(field).index_with(true)
     end
     end
   end
   end
 
 

+ 1 - 1
app/models/report.rb

@@ -32,7 +32,7 @@ class Report < ApplicationRecord
 
 
   scope :unresolved, -> { where(action_taken: false) }
   scope :unresolved, -> { where(action_taken: false) }
   scope :resolved,   -> { where(action_taken: true) }
   scope :resolved,   -> { where(action_taken: true) }
-  scope :with_accounts, -> { includes([:account, :target_account, :action_taken_by_account, :assigned_account].each_with_object({}) { |k, h| h[k] = { user: [:invite_request, :invite] } }) }
+  scope :with_accounts, -> { includes([:account, :target_account, :action_taken_by_account, :assigned_account].index_with({ user: [:invite_request, :invite] })) }
 
 
   validates :comment, length: { maximum: 1000 }
   validates :comment, length: { maximum: 1000 }
 
 

+ 1 - 2
app/services/delete_account_service.rb

@@ -188,8 +188,7 @@ class DeleteAccountService < BaseService
       ids = favourites.pluck(:status_id)
       ids = favourites.pluck(:status_id)
       StatusStat.where(status_id: ids).update_all('favourites_count = GREATEST(0, favourites_count - 1)')
       StatusStat.where(status_id: ids).update_all('favourites_count = GREATEST(0, favourites_count - 1)')
       Chewy.strategy.current.update(StatusesIndex::Status, ids) if Chewy.enabled?
       Chewy.strategy.current.update(StatusesIndex::Status, ids) if Chewy.enabled?
-      # Rails.cache.delete_multi would be better, but we don't have it yet
-      ids.each { |id| Rails.cache.delete("statuses/#{id}") }
+      Rails.cache.delete_multi(ids.map { |id| "statuses/#{id}" })
       favourites.delete_all
       favourites.delete_all
     end
     end
   end
   end

+ 2 - 2
app/services/import_service.rb

@@ -45,7 +45,7 @@ class ImportService < BaseService
     items = @data.take(ROWS_PROCESSING_LIMIT).map { |row| row['#domain'].strip }
     items = @data.take(ROWS_PROCESSING_LIMIT).map { |row| row['#domain'].strip }
 
 
     if @import.overwrite?
     if @import.overwrite?
-      presence_hash = items.each_with_object({}) { |id, mapping| mapping[id] = true }
+      presence_hash = items.index_with(true)
 
 
       @account.domain_blocks.find_each do |domain_block|
       @account.domain_blocks.find_each do |domain_block|
         if presence_hash[domain_block.domain]
         if presence_hash[domain_block.domain]
@@ -96,7 +96,7 @@ class ImportService < BaseService
     items = @data.take(ROWS_PROCESSING_LIMIT).map { |row| row['#uri'].strip }
     items = @data.take(ROWS_PROCESSING_LIMIT).map { |row| row['#uri'].strip }
 
 
     if @import.overwrite?
     if @import.overwrite?
-      presence_hash = items.each_with_object({}) { |id, mapping| mapping[id] = true }
+      presence_hash = items.index_with(true)
 
 
       @account.bookmarks.find_each do |bookmark|
       @account.bookmarks.find_each do |bookmark|
         if presence_hash[bookmark.status.uri]
         if presence_hash[bookmark.status.uri]

+ 9 - 7
bin/setup

@@ -1,6 +1,5 @@
 #!/usr/bin/env ruby
 #!/usr/bin/env ruby
-require 'fileutils'
-include FileUtils
+require "fileutils"
 
 
 # path to your application root.
 # path to your application root.
 APP_ROOT = File.expand_path('..', __dir__)
 APP_ROOT = File.expand_path('..', __dir__)
@@ -9,22 +8,25 @@ def system!(*args)
   system(*args) || abort("\n== Command #{args} failed ==")
   system(*args) || abort("\n== Command #{args} failed ==")
 end
 end
 
 
-chdir APP_ROOT do
-  # This script is a starting point to setup your application.
+FileUtils.chdir APP_ROOT do
+  # This script is a way to set up or update your development environment automatically.
+  # This script is idempotent, so that you can run it at any time and get an expectable outcome.
   # Add necessary setup steps to this file.
   # Add necessary setup steps to this file.
 
 
   puts '== Installing dependencies =='
   puts '== Installing dependencies =='
   system! 'gem install bundler --conservative'
   system! 'gem install bundler --conservative'
   system('bundle check') || system!('bundle install')
   system('bundle check') || system!('bundle install')
-  system!('yarn install')
+
+  # Install JavaScript dependencies
+  system! 'bin/yarn'
 
 
   # puts "\n== Copying sample files =="
   # puts "\n== Copying sample files =="
   # unless File.exist?('config/database.yml')
   # unless File.exist?('config/database.yml')
-  #   cp 'config/database.yml.sample', 'config/database.yml'
+  #   FileUtils.cp 'config/database.yml.sample', 'config/database.yml'
   # end
   # end
 
 
   puts "\n== Preparing database =="
   puts "\n== Preparing database =="
-  system! 'bin/rails db:setup'
+  system! 'bin/rails db:prepare'
 
 
   puts "\n== Removing old logs and tempfiles =="
   puts "\n== Removing old logs and tempfiles =="
   system! 'bin/rails log:clear tmp:clear'
   system! 'bin/rails log:clear tmp:clear'

+ 9 - 3
bin/yarn

@@ -1,9 +1,15 @@
 #!/usr/bin/env ruby
 #!/usr/bin/env ruby
 APP_ROOT = File.expand_path('..', __dir__)
 APP_ROOT = File.expand_path('..', __dir__)
 Dir.chdir(APP_ROOT) do
 Dir.chdir(APP_ROOT) do
-  begin
-    exec "yarnpkg", *ARGV
-  rescue Errno::ENOENT
+  yarn = ENV["PATH"].split(File::PATH_SEPARATOR).
+    select { |dir| File.expand_path(dir) != __dir__ }.
+    product(["yarn", "yarn.cmd", "yarn.ps1"]).
+    map { |dir, file| File.expand_path(file, dir) }.
+    find { |file| File.executable?(file) }
+
+  if yarn
+    exec yarn, *ARGV
+  else
     $stderr.puts "Yarn executable was not detected in the system."
     $stderr.puts "Yarn executable was not detected in the system."
     $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
     $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
     exit 1
     exit 1

+ 2 - 1
config/application.rb

@@ -39,7 +39,8 @@ require_relative '../lib/mastodon/redis_config'
 module Mastodon
 module Mastodon
   class Application < Rails::Application
   class Application < Rails::Application
     # Initialize configuration defaults for originally generated Rails version.
     # Initialize configuration defaults for originally generated Rails version.
-    config.load_defaults 5.2
+    config.load_defaults 6.1
+    config.add_autoload_paths_to_load_path = false
 
 
     # Settings in config/environments/* take precedence over those specified here.
     # Settings in config/environments/* take precedence over those specified here.
     # Application configuration should go into files in config/initializers
     # Application configuration should go into files in config/initializers

+ 7 - 0
config/environments/production.rb

@@ -44,6 +44,13 @@ Rails.application.configure do
   # Allow to specify public IP of reverse proxy if it's needed
   # Allow to specify public IP of reverse proxy if it's needed
   config.action_dispatch.trusted_proxies = ENV['TRUSTED_PROXY_IP'].split.map { |item| IPAddr.new(item) } if ENV['TRUSTED_PROXY_IP'].present?
   config.action_dispatch.trusted_proxies = ENV['TRUSTED_PROXY_IP'].split.map { |item| IPAddr.new(item) } if ENV['TRUSTED_PROXY_IP'].present?
 
 
+  config.force_ssl = true
+  config.ssl_options = {
+    redirect: {
+      exclude: -> request { request.path.start_with?('/health') || request.headers["Host"].end_with?('.onion') }
+    }
+  }
+
   # Use the lowest log level to ensure availability of diagnostic information
   # Use the lowest log level to ensure availability of diagnostic information
   # when problems arise.
   # when problems arise.
   config.log_level = ENV.fetch('RAILS_LOG_LEVEL', 'info').to_sym
   config.log_level = ENV.fetch('RAILS_LOG_LEVEL', 'info').to_sym

+ 6 - 4
config/initializers/application_controller_renderer.rb

@@ -1,6 +1,8 @@
 # Be sure to restart your server when you modify this file.
 # Be sure to restart your server when you modify this file.
 
 
-# ApplicationController.renderer.defaults.merge!(
-#   http_host: 'example.org',
-#   https: false
-# )
+# ActiveSupport::Reloader.to_prepare do
+#   ApplicationController.renderer.defaults.merge!(
+#     http_host: 'example.org',
+#     https: false
+#   )
+# end

+ 4 - 3
config/initializers/backtrace_silencers.rb

@@ -1,7 +1,8 @@
 # Be sure to restart your server when you modify this file.
 # Be sure to restart your server when you modify this file.
 
 
 # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
 # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
-# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+# Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) }
 
 
-# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
-# Rails.backtrace_cleaner.remove_silencers!
+# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code
+# by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'".
+Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"]

+ 1 - 11
config/initializers/content_security_policy.rb

@@ -49,17 +49,7 @@ end
 
 
 Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
 Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
 
 
-# Monkey-patching Rails 5
-module ActionDispatch
-  class ContentSecurityPolicy
-    def nonce_directive?(directive)
-      directive == 'style-src'
-    end
-  end
-end
-
-# Rails 6 would require the following instead:
-# Rails.application.config.content_security_policy_nonce_directives = %w(style-src)
+Rails.application.config.content_security_policy_nonce_directives = %w(style-src)
 
 
 PgHero::HomeController.content_security_policy do |p|
 PgHero::HomeController.content_security_policy do |p|
   p.script_src :self, :unsafe_inline, assets_host
   p.script_src :self, :unsafe_inline, assets_host

+ 11 - 0
config/initializers/permissions_policy.rb

@@ -0,0 +1,11 @@
+# Define an application-wide HTTP permissions policy. For further
+# information see https://developers.google.com/web/updates/2018/06/feature-policy
+#
+# Rails.application.config.permissions_policy do |f|
+#   f.camera      :none
+#   f.gyroscope   :none
+#   f.microphone  :none
+#   f.usb         :none
+#   f.fullscreen  :self
+#   f.payment     :self, "https://secure.example.com"
+# end

+ 8 - 0
config/initializers/preload_link_headers.rb

@@ -0,0 +1,8 @@
+# Since Rails 6.1, ActionView adds preload links for javascript files
+# in the Links header per default.
+
+# In our case, that will bloat headers too much and potentially cause
+# issues with reverse proxies. Furhermore, we don't need those links,
+# as we already output them as HTML link tags.
+
+Rails.application.config.action_view.preload_links_header = false

+ 0 - 0
config/storage.yml


+ 1 - 1
lib/tasks/emojis.rake

@@ -69,7 +69,7 @@ namespace :emojis do
       end
       end
     end
     end
 
 
-    existence_maps = grouped_codes.map { |c| c.map { |cc| [cc, File.exist?(Rails.root.join('public', 'emoji', codepoints_to_filename(cc) + '.svg'))] }.to_h }
+    existence_maps = grouped_codes.map { |c| c.index_with { |cc| File.exist?(Rails.root.join('public', 'emoji', codepoints_to_filename(cc) + '.svg')) } }
     map = {}
     map = {}
 
 
     existence_maps.each do |group|
     existence_maps.each do |group|

+ 2 - 2
spec/controllers/api/v1/accounts/credentials_controller_spec.rb

@@ -30,8 +30,8 @@ describe Api::V1::Accounts::CredentialsController do
           patch :update, params: {
           patch :update, params: {
             display_name: "Alice Isn't Dead",
             display_name: "Alice Isn't Dead",
             note: "Hi!\n\nToot toot!",
             note: "Hi!\n\nToot toot!",
-            avatar: fixture_file_upload('files/avatar.gif', 'image/gif'),
-            header: fixture_file_upload('files/attachment.jpg', 'image/jpeg'),
+            avatar: fixture_file_upload('avatar.gif', 'image/gif'),
+            header: fixture_file_upload('attachment.jpg', 'image/jpeg'),
             source: {
             source: {
               privacy: 'unlisted',
               privacy: 'unlisted',
               sensitive: true,
               sensitive: true,

+ 5 - 5
spec/controllers/api/v1/media_controller_spec.rb

@@ -15,7 +15,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do
       context 'when imagemagick cant identify the file type' do
       context 'when imagemagick cant identify the file type' do
         before do
         before do
           expect_any_instance_of(Account).to receive_message_chain(:media_attachments, :create!).and_raise(Paperclip::Errors::NotIdentifiedByImageMagickError)
           expect_any_instance_of(Account).to receive_message_chain(:media_attachments, :create!).and_raise(Paperclip::Errors::NotIdentifiedByImageMagickError)
-          post :create, params: { file: fixture_file_upload('files/attachment.jpg', 'image/jpeg') }
+          post :create, params: { file: fixture_file_upload('attachment.jpg', 'image/jpeg') }
         end
         end
 
 
         it 'returns http 422' do
         it 'returns http 422' do
@@ -26,7 +26,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do
       context 'when there is a generic error' do
       context 'when there is a generic error' do
         before do
         before do
           expect_any_instance_of(Account).to receive_message_chain(:media_attachments, :create!).and_raise(Paperclip::Error)
           expect_any_instance_of(Account).to receive_message_chain(:media_attachments, :create!).and_raise(Paperclip::Error)
-          post :create, params: { file: fixture_file_upload('files/attachment.jpg', 'image/jpeg') }
+          post :create, params: { file: fixture_file_upload('attachment.jpg', 'image/jpeg') }
         end
         end
 
 
         it 'returns http 422' do
         it 'returns http 422' do
@@ -37,7 +37,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do
 
 
     context 'image/jpeg' do
     context 'image/jpeg' do
       before do
       before do
-        post :create, params: { file: fixture_file_upload('files/attachment.jpg', 'image/jpeg') }
+        post :create, params: { file: fixture_file_upload('attachment.jpg', 'image/jpeg') }
       end
       end
 
 
       it 'returns http success' do
       it 'returns http success' do
@@ -59,7 +59,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do
 
 
     context 'image/gif' do
     context 'image/gif' do
       before do
       before do
-        post :create, params: { file: fixture_file_upload('files/attachment.gif', 'image/gif') }
+        post :create, params: { file: fixture_file_upload('attachment.gif', 'image/gif') }
       end
       end
 
 
       it 'returns http success' do
       it 'returns http success' do
@@ -81,7 +81,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do
 
 
     context 'video/webm' do
     context 'video/webm' do
       before do
       before do
-        post :create, params: { file: fixture_file_upload('files/attachment.webm', 'video/webm') }
+        post :create, params: { file: fixture_file_upload('attachment.webm', 'video/webm') }
       end
       end
 
 
       it do
       it do

+ 0 - 14
spec/controllers/application_controller_spec.rb

@@ -42,20 +42,6 @@ describe ApplicationController, type: :controller do
     include_examples 'respond_with_error', 422
     include_examples 'respond_with_error', 422
   end
   end
 
 
-  it "does not force ssl if Rails.env.production? is not 'true'" do
-    routes.draw { get 'success' => 'anonymous#success' }
-    allow(Rails.env).to receive(:production?).and_return(false)
-    get 'success'
-    expect(response).to have_http_status(200)
-  end
-
-  it "forces ssl if Rails.env.production? is 'true'" do
-    routes.draw { get 'success' => 'anonymous#success' }
-    allow(Rails.env).to receive(:production?).and_return(true)
-    get 'success'
-    expect(response).to redirect_to('https://test.host/success')
-  end
-
   describe 'helper_method :current_account' do
   describe 'helper_method :current_account' do
     it 'returns nil if not signed in' do
     it 'returns nil if not signed in' do
       expect(controller.view_context.current_account).to be_nil
       expect(controller.view_context.current_account).to be_nil

+ 2 - 2
spec/controllers/settings/imports_controller_spec.rb

@@ -21,7 +21,7 @@ RSpec.describe Settings::ImportsController, type: :controller do
       post :create, params: {
       post :create, params: {
         import: {
         import: {
           type: 'following',
           type: 'following',
-          data: fixture_file_upload('files/imports.txt')
+          data: fixture_file_upload('imports.txt')
         }
         }
       }
       }
 
 
@@ -34,7 +34,7 @@ RSpec.describe Settings::ImportsController, type: :controller do
       post :create, params: {
       post :create, params: {
         import: {
         import: {
           type: 'blocking',
           type: 'blocking',
-          data: fixture_file_upload('files/imports.txt')
+          data: fixture_file_upload('imports.txt')
         }
         }
       }
       }
 
 

+ 2 - 2
spec/controllers/settings/profiles_controller_spec.rb

@@ -33,7 +33,7 @@ RSpec.describe Settings::ProfilesController, type: :controller do
       account = Fabricate(:account, user: @user, display_name: 'AvatarTest')
       account = Fabricate(:account, user: @user, display_name: 'AvatarTest')
       expect(account.avatar.instance.avatar_file_name).to be_nil
       expect(account.avatar.instance.avatar_file_name).to be_nil
 
 
-      put :update, params: { account: { avatar: fixture_file_upload('files/avatar.gif', 'image/gif') } }
+      put :update, params: { account: { avatar: fixture_file_upload('avatar.gif', 'image/gif') } }
       expect(response).to redirect_to(settings_profile_path)
       expect(response).to redirect_to(settings_profile_path)
       expect(account.reload.avatar.instance.avatar_file_name).not_to be_nil
       expect(account.reload.avatar.instance.avatar_file_name).not_to be_nil
       expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id)
       expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id)
@@ -44,7 +44,7 @@ RSpec.describe Settings::ProfilesController, type: :controller do
     it 'gives the user an error message' do
     it 'gives the user an error message' do
       allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
       allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
       account = Fabricate(:account, user: @user, display_name: 'AvatarTest')
       account = Fabricate(:account, user: @user, display_name: 'AvatarTest')
-      put :update, params: { account: { avatar: fixture_file_upload('files/4096x4097.png', 'image/png') } }
+      put :update, params: { account: { avatar: fixture_file_upload('4096x4097.png', 'image/png') } }
       expect(response.body).to include('images are not supported')
       expect(response.body).to include('images are not supported')
     end
     end
   end
   end

+ 1 - 1
spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb

@@ -11,7 +11,7 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
       subject
       subject
 
 
       expect(assigns(:confirmation)).to be_instance_of Form::TwoFactorConfirmation
       expect(assigns(:confirmation)).to be_instance_of Form::TwoFactorConfirmation
-      expect(assigns(:provision_url)).to eq 'otpauth://totp/local-part@domain?secret=thisisasecretforthespecofnewview&issuer=cb6e6126.ngrok.io'
+      expect(assigns(:provision_url)).to eq 'otpauth://totp/cb6e6126.ngrok.io:local-part%40domain?secret=thisisasecretforthespecofnewview&issuer=cb6e6126.ngrok.io'
       expect(assigns(:qrcode)).to be_instance_of RQRCode::QRCode
       expect(assigns(:qrcode)).to be_instance_of RQRCode::QRCode
       expect(response).to have_http_status(200)
       expect(response).to have_http_status(200)
       expect(response).to render_template(:new)
       expect(response).to render_template(:new)

+ 6 - 5
spec/models/setting_spec.rb

@@ -99,11 +99,12 @@ RSpec.describe Setting, type: :model do
         end
         end
 
 
         it 'does not query the database' do
         it 'does not query the database' do
-          expect do |callback|
-            ActiveSupport::Notifications.subscribed callback, 'sql.active_record' do
-              described_class[key]
-            end
-          end.not_to yield_control
+          callback = double
+          allow(callback).to receive(:call)
+          ActiveSupport::Notifications.subscribed callback, 'sql.active_record' do
+            described_class[key]
+          end
+          expect(callback).not_to have_received(:call)
         end
         end
 
 
         it 'returns the cached value' do
         it 'returns the cached value' do

+ 1 - 1
spec/models/user_spec.rb

@@ -175,7 +175,7 @@ RSpec.describe User, type: :model do
       user = Fabricate(:user)
       user = Fabricate(:user)
       ActiveJob::Base.queue_adapter = :test
       ActiveJob::Base.queue_adapter = :test
 
 
-      expect { user.send_confirmation_instructions }.to have_enqueued_job(ActionMailer::DeliveryJob)
+      expect { user.send_confirmation_instructions }.to have_enqueued_job(ActionMailer::MailDeliveryJob)
     end
     end
   end
   end