diff --git a/Gemfile.lock b/Gemfile.lock
index 8af9c3c6e5..812a1e0146 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -613,7 +613,7 @@ GEM
       activesupport (>= 3.0.0)
     raabro (1.4.0)
     racc (1.8.1)
-    rack (2.2.12)
+    rack (2.2.13)
     rack-attack (6.7.0)
       rack (>= 1.0, < 4)
     rack-cors (2.0.2)
diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb
index 1b64eb4ef2..b90036a5cd 100644
--- a/app/controllers/api/v1/accounts/credentials_controller.rb
+++ b/app/controllers/api/v1/accounts/credentials_controller.rb
@@ -14,7 +14,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
     @account = current_account
     UpdateAccountService.new.call(@account, account_params, raise_error: true)
     current_user.update(user_params) if user_params
-    ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
+    ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
     render json: @account, serializer: REST::CredentialAccountSerializer
   rescue ActiveRecord::RecordInvalid => e
     render json: ValidationErrorFormatter.new(e).as_json, status: 422
diff --git a/app/controllers/api/v1/profile/avatars_controller.rb b/app/controllers/api/v1/profile/avatars_controller.rb
index bc4d01a597..e6c954ed63 100644
--- a/app/controllers/api/v1/profile/avatars_controller.rb
+++ b/app/controllers/api/v1/profile/avatars_controller.rb
@@ -7,7 +7,7 @@ class Api::V1::Profile::AvatarsController < Api::BaseController
   def destroy
     @account = current_account
     UpdateAccountService.new.call(@account, { avatar: nil }, raise_error: true)
-    ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
+    ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
     render json: @account, serializer: REST::CredentialAccountSerializer
   end
 end
diff --git a/app/controllers/api/v1/profile/headers_controller.rb b/app/controllers/api/v1/profile/headers_controller.rb
index 9f4daa2f77..4472a01b05 100644
--- a/app/controllers/api/v1/profile/headers_controller.rb
+++ b/app/controllers/api/v1/profile/headers_controller.rb
@@ -7,7 +7,7 @@ class Api::V1::Profile::HeadersController < Api::BaseController
   def destroy
     @account = current_account
     UpdateAccountService.new.call(@account, { header: nil }, raise_error: true)
-    ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
+    ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
     render json: @account, serializer: REST::CredentialAccountSerializer
   end
 end
diff --git a/app/controllers/settings/pictures_controller.rb b/app/controllers/settings/pictures_controller.rb
index 58a4325307..7e61e6d580 100644
--- a/app/controllers/settings/pictures_controller.rb
+++ b/app/controllers/settings/pictures_controller.rb
@@ -8,7 +8,7 @@ module Settings
     def destroy
       if valid_picture?
         if UpdateAccountService.new.call(@account, { @picture => nil, "#{@picture}_remote_url" => '' })
-          ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
+          ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
           redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg'), status: 303
         else
           redirect_to settings_profile_path
diff --git a/app/controllers/settings/privacy_controller.rb b/app/controllers/settings/privacy_controller.rb
index a5bb3b884f..96efa03ccf 100644
--- a/app/controllers/settings/privacy_controller.rb
+++ b/app/controllers/settings/privacy_controller.rb
@@ -8,7 +8,7 @@ class Settings::PrivacyController < Settings::BaseController
   def update
     if UpdateAccountService.new.call(@account, account_params.except(:settings))
       current_user.update!(settings_attributes: account_params[:settings])
-      ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
+      ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
       redirect_to settings_privacy_path, notice: I18n.t('generic.changes_saved_msg')
     else
       render :show
diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb
index 458f4148cc..efd8eb1440 100644
--- a/app/controllers/settings/profiles_controller.rb
+++ b/app/controllers/settings/profiles_controller.rb
@@ -9,7 +9,7 @@ class Settings::ProfilesController < Settings::BaseController
 
   def update
     if UpdateAccountService.new.call(@account, account_params)
-      ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
+      ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
       redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg')
     else
       @account.build_fields
diff --git a/app/controllers/settings/verifications_controller.rb b/app/controllers/settings/verifications_controller.rb
index bed29dbeec..4b949ca72d 100644
--- a/app/controllers/settings/verifications_controller.rb
+++ b/app/controllers/settings/verifications_controller.rb
@@ -8,7 +8,7 @@ class Settings::VerificationsController < Settings::BaseController
 
   def update
     if UpdateAccountService.new.call(@account, account_params)
-      ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
+      ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
       redirect_to settings_verification_path, notice: I18n.t('generic.changes_saved_msg')
     else
       render :show
diff --git a/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx b/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx
index 43c2899373..e277e300f8 100644
--- a/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx
+++ b/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx
@@ -1,4 +1,4 @@
-import { useCallback } from 'react';
+import { useMemo } from 'react';
 
 import { defineMessages, useIntl } from 'react-intl';
 
@@ -28,28 +28,30 @@ export const ActionBar = () => {
   const dispatch = useDispatch();
   const intl = useIntl();
 
-  const handleLogoutClick = useCallback(() => {
-    dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
-  }, [dispatch]);
+  const menu = useMemo(() => {
+    const handleLogoutClick = () => {
+      dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
+    };
 
-  let menu = [];
-
-  menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
-  menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
-  menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
-  menu.push(null);
-  menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
-  menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
-  menu.push({ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' });
-  menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
-  menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' });
-  menu.push(null);
-  menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
-  menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
-  menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
-  menu.push({ text: intl.formatMessage(messages.filters), href: '/filters' });
-  menu.push(null);
-  menu.push({ text: intl.formatMessage(messages.logout), action: handleLogoutClick });
+    return ([
+      { text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' },
+      { text: intl.formatMessage(messages.preferences), href: '/settings/preferences' },
+      { text: intl.formatMessage(messages.pins), to: '/pinned' },
+      null,
+      { text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' },
+      { text: intl.formatMessage(messages.favourites), to: '/favourites' },
+      { text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' },
+      { text: intl.formatMessage(messages.lists), to: '/lists' },
+      { text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' },
+      null,
+      { text: intl.formatMessage(messages.mutes), to: '/mutes' },
+      { text: intl.formatMessage(messages.blocks), to: '/blocks' },
+      { text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' },
+      { text: intl.formatMessage(messages.filters), href: '/filters' },
+      null,
+      { text: intl.formatMessage(messages.logout), action: handleLogoutClick },
+    ]);
+  }, [intl, dispatch]);
 
   return (
     <DropdownMenuContainer
diff --git a/app/javascript/mastodon/features/compose/components/action_bar.jsx b/app/javascript/mastodon/features/compose/components/action_bar.jsx
index 6c2f27b01b..f7339141ad 100644
--- a/app/javascript/mastodon/features/compose/components/action_bar.jsx
+++ b/app/javascript/mastodon/features/compose/components/action_bar.jsx
@@ -1,4 +1,4 @@
-import { useCallback } from 'react';
+import { useMemo } from 'react';
 
 import { defineMessages, useIntl } from 'react-intl';
 
@@ -28,28 +28,30 @@ export const ActionBar = () => {
   const dispatch = useDispatch();
   const intl = useIntl();
 
-  const handleLogoutClick = useCallback(() => {
-    dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
-  }, [dispatch]);
+  const menu = useMemo(() => {
+    const handleLogoutClick = () => {
+      dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
+    };
 
-  let menu = [];
-
-  menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
-  menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
-  menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
-  menu.push(null);
-  menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
-  menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
-  menu.push({ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' });
-  menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
-  menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' });
-  menu.push(null);
-  menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
-  menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
-  menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
-  menu.push({ text: intl.formatMessage(messages.filters), href: '/filters' });
-  menu.push(null);
-  menu.push({ text: intl.formatMessage(messages.logout), action: handleLogoutClick });
+    return ([
+      { text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' },
+      { text: intl.formatMessage(messages.preferences), href: '/settings/preferences' },
+      { text: intl.formatMessage(messages.pins), to: '/pinned' },
+      null,
+      { text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' },
+      { text: intl.formatMessage(messages.favourites), to: '/favourites' },
+      { text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' },
+      { text: intl.formatMessage(messages.lists), to: '/lists' },
+      { text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' },
+      null,
+      { text: intl.formatMessage(messages.mutes), to: '/mutes' },
+      { text: intl.formatMessage(messages.blocks), to: '/blocks' },
+      { text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' },
+      { text: intl.formatMessage(messages.filters), href: '/filters' },
+      null,
+      { text: intl.formatMessage(messages.logout), action: handleLogoutClick },
+    ]);
+  }, [intl, dispatch]);
 
   return (
     <DropdownMenuContainer
diff --git a/app/workers/activitypub/update_distribution_worker.rb b/app/workers/activitypub/update_distribution_worker.rb
index a04ac621f3..9a418f0f3d 100644
--- a/app/workers/activitypub/update_distribution_worker.rb
+++ b/app/workers/activitypub/update_distribution_worker.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class ActivityPub::UpdateDistributionWorker < ActivityPub::RawDistributionWorker
+  DEBOUNCE_DELAY = 5.seconds
+
   sidekiq_options queue: 'push', lock: :until_executed, lock_ttl: 1.day.to_i
 
   # Distribute an profile update to servers that might have a copy
diff --git a/config/application.rb b/config/application.rb
index 88c7b029bf..e1af98b448 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -34,11 +34,11 @@ require_relative '../lib/paperclip/transcoder'
 require_relative '../lib/paperclip/type_corrector'
 require_relative '../lib/paperclip/response_with_limit_adapter'
 require_relative '../lib/terrapin/multi_pipe_extensions'
+require_relative '../lib/mastodon/middleware/public_file_server'
+require_relative '../lib/mastodon/middleware/socket_cleanup'
 require_relative '../lib/mastodon/snowflake'
 require_relative '../lib/mastodon/feature'
 require_relative '../lib/mastodon/version'
-require_relative '../lib/mastodon/rack_middleware'
-require_relative '../lib/public_file_server_middleware'
 require_relative '../lib/devise/strategies/two_factor_ldap_authenticatable'
 require_relative '../lib/devise/strategies/two_factor_pam_authenticatable'
 require_relative '../lib/elasticsearch/client_extensions'
@@ -88,9 +88,9 @@ module Mastodon
     # We use our own middleware for this
     config.public_file_server.enabled = false
 
-    config.middleware.use PublicFileServerMiddleware if Rails.env.local? || ENV['RAILS_SERVE_STATIC_FILES'] == 'true'
+    config.middleware.use Mastodon::Middleware::PublicFileServer if Rails.env.local? || ENV['RAILS_SERVE_STATIC_FILES'] == 'true'
     config.middleware.use Rack::Attack
-    config.middleware.use Mastodon::RackMiddleware
+    config.middleware.use Mastodon::Middleware::SocketCleanup
 
     config.before_configuration do
       require 'mastodon/redis_configuration'
diff --git a/config/mastodon.yml b/config/mastodon.yml
index 8256761e86..ea67306d5b 100644
--- a/config/mastodon.yml
+++ b/config/mastodon.yml
@@ -10,3 +10,5 @@ shared:
   version:
     metadata: <%= ['glitch', ENV.fetch('MASTODON_VERSION_METADATA', nil)].compact_blank.join('.') %>
     prerelease: <%= ENV.fetch('MASTODON_VERSION_PRERELEASE', nil) %>
+test:
+  experimental_features: <%= [ENV.fetch('EXPERIMENTAL_FEATURES', nil), 'testing_only'].compact.join(',') %>
diff --git a/lib/mastodon/feature.rb b/lib/mastodon/feature.rb
index 18e6dc9639..650f1d7b8c 100644
--- a/lib/mastodon/feature.rb
+++ b/lib/mastodon/feature.rb
@@ -19,8 +19,8 @@ module Mastodon::Feature
       super
     end
 
-    def respond_to_missing?(name)
-      name.to_s.end_with?('_enabled?')
+    def respond_to_missing?(name, include_all = false)
+      name.to_s.end_with?('_enabled?') || super
     end
   end
 end
diff --git a/lib/mastodon/middleware/public_file_server.rb b/lib/mastodon/middleware/public_file_server.rb
new file mode 100644
index 0000000000..b9a1edb9f5
--- /dev/null
+++ b/lib/mastodon/middleware/public_file_server.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'action_dispatch/middleware/static'
+
+module Mastodon
+  module Middleware
+    class PublicFileServer
+      SERVICE_WORKER_TTL = 7.days.to_i
+      CACHE_TTL          = 28.days.to_i
+
+      def initialize(app)
+        @app = app
+        @file_handler = ActionDispatch::FileHandler.new(Rails.application.paths['public'].first)
+      end
+
+      def call(env)
+        file = @file_handler.attempt(env)
+
+        # If the request is not a static file, move on!
+        return @app.call(env) if file.nil?
+
+        status, headers, response = file
+
+        # Set cache headers on static files. Some paths require different cache headers
+        headers['Cache-Control'] = begin
+          request_path = env['REQUEST_PATH']
+
+          if request_path.start_with?('/sw.js')
+            "public, max-age=#{SERVICE_WORKER_TTL}, must-revalidate"
+          elsif request_path.start_with?(paperclip_root_url)
+            "public, max-age=#{CACHE_TTL}, immutable"
+          else
+            "public, max-age=#{CACHE_TTL}, must-revalidate"
+          end
+        end
+
+        # Override the default CSP header set by the CSP middleware
+        headers['Content-Security-Policy'] = "default-src 'none'; form-action 'none'" if request_path.start_with?(paperclip_root_url)
+
+        headers['X-Content-Type-Options'] = 'nosniff'
+
+        [status, headers, response]
+      end
+
+      private
+
+      def paperclip_root_url
+        ENV.fetch('PAPERCLIP_ROOT_URL', '/system')
+      end
+    end
+  end
+end
diff --git a/lib/mastodon/middleware/socket_cleanup.rb b/lib/mastodon/middleware/socket_cleanup.rb
new file mode 100644
index 0000000000..8b33cb0cec
--- /dev/null
+++ b/lib/mastodon/middleware/socket_cleanup.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Mastodon
+  module Middleware
+    class SocketCleanup
+      def initialize(app)
+        @app = app
+      end
+
+      def call(env)
+        @app.call(env)
+      ensure
+        clean_up_sockets!
+      end
+
+      private
+
+      def clean_up_sockets!
+        clean_up_redis_socket!
+        clean_up_statsd_socket!
+      end
+
+      def clean_up_redis_socket!
+        RedisConnection.pool.checkin if Thread.current[:redis]
+        Thread.current[:redis] = nil
+      end
+
+      def clean_up_statsd_socket!
+        Thread.current[:statsd_socket]&.close
+        Thread.current[:statsd_socket] = nil
+      end
+    end
+  end
+end
diff --git a/lib/mastodon/rack_middleware.rb b/lib/mastodon/rack_middleware.rb
deleted file mode 100644
index 0e452f06d6..0000000000
--- a/lib/mastodon/rack_middleware.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-class Mastodon::RackMiddleware
-  def initialize(app)
-    @app = app
-  end
-
-  def call(env)
-    @app.call(env)
-  ensure
-    clean_up_sockets!
-  end
-
-  private
-
-  def clean_up_sockets!
-    clean_up_redis_socket!
-    clean_up_statsd_socket!
-  end
-
-  def clean_up_redis_socket!
-    RedisConnection.pool.checkin if Thread.current[:redis]
-    Thread.current[:redis] = nil
-  end
-
-  def clean_up_statsd_socket!
-    Thread.current[:statsd_socket]&.close
-    Thread.current[:statsd_socket] = nil
-  end
-end
diff --git a/lib/public_file_server_middleware.rb b/lib/public_file_server_middleware.rb
deleted file mode 100644
index 7e02e37a08..0000000000
--- a/lib/public_file_server_middleware.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# frozen_string_literal: true
-
-require 'action_dispatch/middleware/static'
-
-class PublicFileServerMiddleware
-  SERVICE_WORKER_TTL = 7.days.to_i
-  CACHE_TTL          = 28.days.to_i
-
-  def initialize(app)
-    @app = app
-    @file_handler = ActionDispatch::FileHandler.new(Rails.application.paths['public'].first)
-  end
-
-  def call(env)
-    file = @file_handler.attempt(env)
-
-    # If the request is not a static file, move on!
-    return @app.call(env) if file.nil?
-
-    status, headers, response = file
-
-    # Set cache headers on static files. Some paths require different cache headers
-    headers['Cache-Control'] = begin
-      request_path = env['REQUEST_PATH']
-
-      if request_path.start_with?('/sw.js')
-        "public, max-age=#{SERVICE_WORKER_TTL}, must-revalidate"
-      elsif request_path.start_with?(paperclip_root_url)
-        "public, max-age=#{CACHE_TTL}, immutable"
-      else
-        "public, max-age=#{CACHE_TTL}, must-revalidate"
-      end
-    end
-
-    # Override the default CSP header set by the CSP middleware
-    headers['Content-Security-Policy'] = "default-src 'none'; form-action 'none'" if request_path.start_with?(paperclip_root_url)
-
-    headers['X-Content-Type-Options'] = 'nosniff'
-
-    [status, headers, response]
-  end
-
-  private
-
-  def paperclip_root_url
-    ENV.fetch('PAPERCLIP_ROOT_URL', '/system')
-  end
-end
diff --git a/spec/controllers/admin/settings/branding_controller_spec.rb b/spec/controllers/admin/settings/branding_controller_spec.rb
deleted file mode 100644
index 6b3621bb8a..0000000000
--- a/spec/controllers/admin/settings/branding_controller_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Admin::Settings::BrandingController do
-  render_views
-
-  describe 'When signed in as an admin' do
-    before do
-      sign_in Fabricate(:admin_user), scope: :user
-    end
-
-    describe 'PUT #update' do
-      it 'cannot create a setting value for a non-admin key' do
-        expect(Setting.new_setting_key).to be_blank
-
-        patch :update, params: { form_admin_settings: { new_setting_key: 'New key value' } }
-
-        expect(response)
-          .to have_http_status(400)
-        expect(Setting.new_setting_key).to be_nil
-      end
-    end
-  end
-end
diff --git a/spec/lib/mastodon/feature_spec.rb b/spec/lib/mastodon/feature_spec.rb
index a7fe4fe90b..f8236d8959 100644
--- a/spec/lib/mastodon/feature_spec.rb
+++ b/spec/lib/mastodon/feature_spec.rb
@@ -3,28 +3,23 @@
 require 'rails_helper'
 
 RSpec.describe Mastodon::Feature do
-  around do |example|
-    original_value = Rails.configuration.x.mastodon.experimental_features
-    Rails.configuration.x.mastodon.experimental_features = 'fasp,fetch_all_replies'
-    example.run
-    Rails.configuration.x.mastodon.experimental_features = original_value
-  end
-
-  describe '::fasp_enabled?' do
-    subject { described_class.fasp_enabled? }
-
-    it { is_expected.to be true }
-  end
-
-  describe '::fetch_all_replies_enabled?' do
-    subject { described_class.fetch_all_replies_enabled? }
+  describe '::testing_only_enabled?' do
+    subject { described_class.testing_only_enabled? }
 
     it { is_expected.to be true }
   end
 
   describe '::unspecified_feature_enabled?' do
-    subject { described_class.unspecified_feature_enabled? }
+    context 'when example is not tagged with a feature' do
+      subject { described_class.unspecified_feature_enabled? }
 
-    it { is_expected.to be false }
+      it { is_expected.to be false }
+    end
+
+    context 'when example is tagged with a feature', feature: 'unspecified_feature' do
+      subject { described_class.unspecified_feature_enabled? }
+
+      it { is_expected.to be true }
+    end
   end
 end
diff --git a/spec/requests/admin/settings/branding_spec.rb b/spec/requests/admin/settings/branding_spec.rb
new file mode 100644
index 0000000000..e5206f056f
--- /dev/null
+++ b/spec/requests/admin/settings/branding_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe 'Admin Settings Branding' do
+  describe 'When signed in as an admin' do
+    before { sign_in Fabricate(:admin_user) }
+
+    describe 'PUT /admin/settings/branding' do
+      it 'cannot create a setting value for a non-admin key' do
+        expect { put admin_settings_branding_path, params: { form_admin_settings: { new_setting_key: 'New key value' } } }
+          .to_not change(Setting, :new_setting_key).from(nil)
+
+        expect(response)
+          .to have_http_status(400)
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/v1/accounts/credentials_spec.rb b/spec/requests/api/v1/accounts/credentials_spec.rb
index c92f4c7973..68ea259481 100644
--- a/spec/requests/api/v1/accounts/credentials_spec.rb
+++ b/spec/requests/api/v1/accounts/credentials_spec.rb
@@ -53,8 +53,6 @@ RSpec.describe 'credentials API' do
       patch '/api/v1/accounts/update_credentials', headers: headers, params: params
     end
 
-    before { allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async) }
-
     let(:params) do
       {
         avatar: fixture_file_upload('avatar.gif', 'image/gif'),
@@ -113,7 +111,7 @@ RSpec.describe 'credentials API' do
       })
 
       expect(ActivityPub::UpdateDistributionWorker)
-        .to have_received(:perform_async).with(user.account_id)
+        .to have_enqueued_sidekiq_job(user.account_id)
     end
 
     def expect_account_updates
diff --git a/spec/requests/api/v1/profiles_spec.rb b/spec/requests/api/v1/profiles_spec.rb
index fd3ab4bf58..de7a20b133 100644
--- a/spec/requests/api/v1/profiles_spec.rb
+++ b/spec/requests/api/v1/profiles_spec.rb
@@ -15,10 +15,6 @@ RSpec.describe 'Deleting profile images' do
   let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
 
   describe 'DELETE /api/v1/profile' do
-    before do
-      allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
-    end
-
     context 'when deleting an avatar' do
       context 'with wrong scope' do
         before do
@@ -38,7 +34,8 @@ RSpec.describe 'Deleting profile images' do
         account.reload
         expect(account.avatar).to_not exist
         expect(account.header).to exist
-        expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id)
+        expect(ActivityPub::UpdateDistributionWorker)
+          .to have_enqueued_sidekiq_job(account.id)
       end
     end
 
@@ -61,7 +58,8 @@ RSpec.describe 'Deleting profile images' do
         account.reload
         expect(account.avatar).to exist
         expect(account.header).to_not exist
-        expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id)
+        expect(ActivityPub::UpdateDistributionWorker)
+          .to have_enqueued_sidekiq_job(account.id)
       end
     end
   end
diff --git a/spec/support/feature_flags.rb b/spec/support/feature_flags.rb
new file mode 100644
index 0000000000..186711163b
--- /dev/null
+++ b/spec/support/feature_flags.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+RSpec.configure do |config|
+  config.before(:example, :feature) do |example|
+    feature = example.metadata[:feature]
+    allow(Mastodon::Feature).to receive(:"#{feature}_enabled?").and_return(true)
+  end
+end
diff --git a/spec/support/stories/profile_stories.rb b/spec/support/stories/profile_stories.rb
index 43a8e170cc..6e7d53454d 100644
--- a/spec/support/stories/profile_stories.rb
+++ b/spec/support/stories/profile_stories.rb
@@ -22,7 +22,11 @@ module ProfileStories
   def as_a_logged_in_user
     as_a_registered_user
     visit new_user_session_path
+    expect(page)
+      .to have_title(I18n.t('auth.login'))
     fill_in_auth_details(email, password)
+    expect(page)
+      .to have_css('.app-holder')
   end
 
   def as_a_logged_in_admin
diff --git a/spec/system/new_statuses_spec.rb b/spec/system/new_statuses_spec.rb
index 317508a0bb..480c77cf87 100644
--- a/spec/system/new_statuses_spec.rb
+++ b/spec/system/new_statuses_spec.rb
@@ -5,21 +5,15 @@ require 'rails_helper'
 RSpec.describe 'NewStatuses', :inline_jobs, :js, :streaming do
   include ProfileStories
 
-  subject { page }
-
   let(:email)               { 'test@example.com' }
   let(:password)            { 'password' }
   let(:confirmed_at)        { Time.zone.now }
   let(:finished_onboarding) { true }
 
-  before do
-    as_a_logged_in_user
-    visit root_path
-  end
+  before { as_a_logged_in_user }
 
   it 'can be posted' do
-    expect(subject).to have_css('div.app-holder')
-
+    visit_homepage
     status_text = 'This is a new status!'
 
     within('.compose-form') do
@@ -27,12 +21,12 @@ RSpec.describe 'NewStatuses', :inline_jobs, :js, :streaming do
       click_on 'Post'
     end
 
-    expect(subject).to have_css('.status__content__text', text: status_text)
+    expect(page)
+      .to have_css('.status__content__text', text: status_text)
   end
 
   it 'can be posted again' do
-    expect(subject).to have_css('div.app-holder')
-
+    visit_homepage
     status_text = 'This is a second status!'
 
     within('.compose-form') do
@@ -40,6 +34,15 @@ RSpec.describe 'NewStatuses', :inline_jobs, :js, :streaming do
       click_on 'Post'
     end
 
-    expect(subject).to have_css('.status__content__text', text: status_text)
+    expect(page)
+      .to have_css('.status__content__text', text: status_text)
+  end
+
+  def visit_homepage
+    visit root_path
+
+    expect(page)
+      .to have_css('div.app-holder')
+      .and have_css('form.compose-form')
   end
 end
diff --git a/spec/system/report_interface_spec.rb b/spec/system/report_interface_spec.rb
index 6a90aa5bc6..3df6b3714b 100644
--- a/spec/system/report_interface_spec.rb
+++ b/spec/system/report_interface_spec.rb
@@ -40,5 +40,7 @@ RSpec.describe 'report interface', :attachment_processing, :js, :streaming do
     within '.report-actions' do
       click_on I18n.t('admin.reports.mark_as_resolved')
     end
+    expect(page)
+      .to have_content(I18n.t('admin.reports.resolved_msg'))
   end
 end
diff --git a/spec/system/settings/privacy_spec.rb b/spec/system/settings/privacy_spec.rb
index 8cc2196d13..5e1498613e 100644
--- a/spec/system/settings/privacy_spec.rb
+++ b/spec/system/settings/privacy_spec.rb
@@ -11,8 +11,6 @@ RSpec.describe 'Settings Privacy' do
     before { user.account.update(discoverable: false) }
 
     context 'with a successful update' do
-      before { allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async) }
-
       it 'updates user profile information' do
         # View settings page
         visit settings_privacy_path
@@ -29,14 +27,13 @@ RSpec.describe 'Settings Privacy' do
           .to have_content(I18n.t('privacy.title'))
           .and have_content(success_message)
         expect(ActivityPub::UpdateDistributionWorker)
-          .to have_received(:perform_async).with(user.account.id)
+          .to have_enqueued_sidekiq_job(user.account.id)
       end
     end
 
     context 'with a failed update' do
       before do
         allow(UpdateAccountService).to receive(:new).and_return(failing_update_service)
-        allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
       end
 
       it 'updates user profile information' do
@@ -54,7 +51,7 @@ RSpec.describe 'Settings Privacy' do
         expect(page)
           .to have_content(I18n.t('privacy.title'))
         expect(ActivityPub::UpdateDistributionWorker)
-          .to_not have_received(:perform_async)
+          .to_not have_enqueued_sidekiq_job(anything)
       end
 
       private
diff --git a/spec/system/settings/profiles_spec.rb b/spec/system/settings/profiles_spec.rb
index 322f5faa91..23d9ab2fda 100644
--- a/spec/system/settings/profiles_spec.rb
+++ b/spec/system/settings/profiles_spec.rb
@@ -7,7 +7,6 @@ RSpec.describe 'Settings profile page' do
   let(:account) { user.account }
 
   before do
-    allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
     sign_in user
   end
 
@@ -24,7 +23,7 @@ RSpec.describe 'Settings profile page' do
       .to change { account.reload.display_name }.to('New name')
       .and(change { account.reload.avatar.instance.avatar_file_name }.from(nil).to(be_present))
     expect(ActivityPub::UpdateDistributionWorker)
-      .to have_received(:perform_async).with(account.id)
+      .to have_enqueued_sidekiq_job(account.id)
   end
 
   def display_name_field