about summary refs log tree commit diff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/admin/account_moderation_notes_controller_spec.rb2
-rw-r--r--spec/controllers/admin/accounts_controller_spec.rb52
-rw-r--r--spec/controllers/admin/action_logs_controller_spec.rb2
-rw-r--r--spec/controllers/admin/base_controller_spec.rb7
-rw-r--r--spec/controllers/admin/change_email_controller_spec.rb2
-rw-r--r--spec/controllers/admin/confirmations_controller_spec.rb2
-rw-r--r--spec/controllers/admin/custom_emojis_controller_spec.rb2
-rw-r--r--spec/controllers/admin/dashboard_controller_spec.rb2
-rw-r--r--spec/controllers/admin/disputes/appeals_controller_spec.rb4
-rw-r--r--spec/controllers/admin/domain_allows_controller_spec.rb2
-rw-r--r--spec/controllers/admin/domain_blocks_controller_spec.rb2
-rw-r--r--spec/controllers/admin/email_domain_blocks_controller_spec.rb2
-rw-r--r--spec/controllers/admin/export_domain_allows_controller_spec.rb2
-rw-r--r--spec/controllers/admin/export_domain_blocks_controller_spec.rb2
-rw-r--r--spec/controllers/admin/instances_controller_spec.rb8
-rw-r--r--spec/controllers/admin/invites_controller_spec.rb2
-rw-r--r--spec/controllers/admin/report_notes_controller_spec.rb2
-rw-r--r--spec/controllers/admin/reports_controller_spec.rb2
-rw-r--r--spec/controllers/admin/resets_controller_spec.rb2
-rw-r--r--spec/controllers/admin/roles_controller_spec.rb244
-rw-r--r--spec/controllers/admin/settings_controller_spec.rb2
-rw-r--r--spec/controllers/admin/statuses_controller_spec.rb2
-rw-r--r--spec/controllers/admin/tags_controller_spec.rb2
-rw-r--r--spec/controllers/admin/users/roles_controller.rb81
-rw-r--r--spec/controllers/admin/users/two_factor_authentications_controller_spec.rb (renamed from spec/controllers/admin/two_factor_authentications_controller_spec.rb)5
-rw-r--r--spec/controllers/api/v1/admin/account_actions_controller_spec.rb6
-rw-r--r--spec/controllers/api/v1/admin/accounts_controller_spec.rb20
-rw-r--r--spec/controllers/api/v1/admin/domain_allows_controller_spec.rb118
-rw-r--r--spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb20
-rw-r--r--spec/controllers/api/v1/admin/reports_controller_spec.rb16
-rw-r--r--spec/controllers/api/v1/filters/keywords_controller_spec.rb142
-rw-r--r--spec/controllers/api/v1/filters_controller_spec.rb27
-rw-r--r--spec/controllers/api/v1/followed_tags_controller_spec.rb23
-rw-r--r--spec/controllers/api/v1/reports_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/statuses_controller_spec.rb52
-rw-r--r--spec/controllers/api/v1/tags_controller_spec.rb82
-rw-r--r--spec/controllers/api/v2/admin/accounts_controller_spec.rb6
-rw-r--r--spec/controllers/api/v2/filters_controller_spec.rb121
-rw-r--r--spec/controllers/application_controller_spec.rb64
-rw-r--r--spec/controllers/auth/sessions_controller_spec.rb26
-rw-r--r--spec/controllers/disputes/appeals_controller_spec.rb2
-rw-r--r--spec/controllers/invites_controller_spec.rb40
-rw-r--r--spec/fabricators/custom_filter_keyword_fabricator.rb4
-rw-r--r--spec/fabricators/login_activity_fabricator.rb10
-rw-r--r--spec/fabricators/tag_follow_fabricator.rb4
-rw-r--r--spec/fabricators/user_role_fabricator.rb5
-rw-r--r--spec/lib/activitypub/activity/flag_spec.rb88
-rw-r--r--spec/lib/feed_manager_spec.rb32
-rw-r--r--spec/lib/hashtag_normalizer_spec.rb29
-rw-r--r--spec/models/account_spec.rb14
-rw-r--r--spec/models/admin/account_action_spec.rb2
-rw-r--r--spec/models/custom_filter_keyword_spec.rb4
-rw-r--r--spec/models/tag_follow_spec.rb4
-rw-r--r--spec/models/tag_spec.rb8
-rw-r--r--spec/models/user_role_spec.rb189
-rw-r--r--spec/models/user_spec.rb159
-rw-r--r--spec/policies/account_moderation_note_policy_spec.rb4
-rw-r--r--spec/policies/account_policy_spec.rb8
-rw-r--r--spec/policies/custom_emoji_policy_spec.rb2
-rw-r--r--spec/policies/domain_block_policy_spec.rb2
-rw-r--r--spec/policies/email_domain_block_policy_spec.rb2
-rw-r--r--spec/policies/instance_policy_spec.rb2
-rw-r--r--spec/policies/invite_policy_spec.rb54
-rw-r--r--spec/policies/relay_policy_spec.rb2
-rw-r--r--spec/policies/report_note_policy_spec.rb5
-rw-r--r--spec/policies/report_policy_spec.rb2
-rw-r--r--spec/policies/settings_policy_spec.rb2
-rw-r--r--spec/policies/status_policy_spec.rb2
-rw-r--r--spec/policies/tag_policy_spec.rb2
-rw-r--r--spec/policies/user_policy_spec.rb55
-rw-r--r--spec/presenters/status_relationships_presenter_spec.rb29
-rw-r--r--spec/services/report_service_spec.rb25
72 files changed, 1438 insertions, 516 deletions
diff --git a/spec/controllers/admin/account_moderation_notes_controller_spec.rb b/spec/controllers/admin/account_moderation_notes_controller_spec.rb
index 410ce6543..d3f3263f8 100644
--- a/spec/controllers/admin/account_moderation_notes_controller_spec.rb
+++ b/spec/controllers/admin/account_moderation_notes_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Admin::AccountModerationNotesController, type: :controller do
   render_views
 
-  let(:user) { Fabricate(:user, admin: true) }
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
   let(:target_account) { Fabricate(:account) }
 
   before do
diff --git a/spec/controllers/admin/accounts_controller_spec.rb b/spec/controllers/admin/accounts_controller_spec.rb
index 1779fb7c0..1bd51a0c8 100644
--- a/spec/controllers/admin/accounts_controller_spec.rb
+++ b/spec/controllers/admin/accounts_controller_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
   before { sign_in current_user, scope: :user }
 
   describe 'GET #index' do
-    let(:current_user) { Fabricate(:user, admin: true) }
+    let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
     around do |example|
       default_per_page = Account.default_per_page
@@ -60,7 +60,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
   end
 
   describe 'GET #show' do
-    let(:current_user) { Fabricate(:user, admin: true) }
+    let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
     let(:account) { Fabricate(:account) }
 
     it 'returns http success' do
@@ -72,15 +72,15 @@ RSpec.describe Admin::AccountsController, type: :controller do
   describe 'POST #memorialize' do
     subject { post :memorialize, params: { id: account.id } }
 
-    let(:current_user) { Fabricate(:user, admin: current_user_admin) }
+    let(:current_user) { Fabricate(:user, role: current_role) }
     let(:account) { user.account }
-    let(:user) { Fabricate(:user, admin: target_user_admin) }
+    let(:user) { Fabricate(:user, role: target_role) }
 
     context 'when user is admin' do
-      let(:current_user_admin) { true }
+      let(:current_role) { UserRole.find_by(name: 'Admin') }
 
       context 'when target user is admin' do
-        let(:target_user_admin) { true }
+        let(:target_role) { UserRole.find_by(name: 'Admin') }
 
         it 'fails to memorialize account' do
           is_expected.to have_http_status :forbidden
@@ -89,7 +89,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
       end
 
       context 'when target user is not admin' do
-        let(:target_user_admin) { false }
+        let(:target_role) { UserRole.find_by(name: 'Moderator') }
 
         it 'succeeds in memorializing account' do
           is_expected.to redirect_to admin_account_path(account.id)
@@ -99,10 +99,10 @@ RSpec.describe Admin::AccountsController, type: :controller do
     end
 
     context 'when user is not admin' do
-      let(:current_user_admin) { false }
+      let(:current_role) { UserRole.find_by(name: 'Moderator') }
 
       context 'when target user is admin' do
-        let(:target_user_admin) { true }
+        let(:target_role) { UserRole.find_by(name: 'Admin') }
 
         it 'fails to memorialize account' do
           is_expected.to have_http_status :forbidden
@@ -111,7 +111,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
       end
 
       context 'when target user is not admin' do
-        let(:target_user_admin) { false }
+        let(:target_role) { UserRole.find_by(name: 'Moderator') }
 
         it 'fails to memorialize account' do
           is_expected.to have_http_status :forbidden
@@ -124,12 +124,12 @@ RSpec.describe Admin::AccountsController, type: :controller do
   describe 'POST #enable' do
     subject { post :enable, params: { id: account.id } }
 
-    let(:current_user) { Fabricate(:user, admin: admin) }
+    let(:current_user) { Fabricate(:user, role: role) }
     let(:account) { user.account }
     let(:user) { Fabricate(:user, disabled: true) }
 
     context 'when user is admin' do
-      let(:admin) { true }
+      let(:role) { UserRole.find_by(name: 'Admin') }
 
       it 'succeeds in enabling account' do
         is_expected.to redirect_to admin_account_path(account.id)
@@ -138,7 +138,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
     end
 
     context 'when user is not admin' do
-      let(:admin) { false }
+      let(:role) { UserRole.everyone }
 
       it 'fails to enable account' do
         is_expected.to have_http_status :forbidden
@@ -150,19 +150,23 @@ RSpec.describe Admin::AccountsController, type: :controller do
   describe 'POST #redownload' do
     subject { post :redownload, params: { id: account.id } }
 
-    let(:current_user) { Fabricate(:user, admin: admin) }
-    let(:account) { Fabricate(:account) }
+    let(:current_user) { Fabricate(:user, role: role) }
+    let(:account) { Fabricate(:account, domain: 'example.com') }
+
+    before do
+      allow_any_instance_of(ResolveAccountService).to receive(:call)
+    end
 
     context 'when user is admin' do
-      let(:admin) { true }
+      let(:role) { UserRole.find_by(name: 'Admin') }
 
-      it 'succeeds in redownloadin' do
+      it 'succeeds in redownloading' do
         is_expected.to redirect_to admin_account_path(account.id)
       end
     end
 
     context 'when user is not admin' do
-      let(:admin) { false }
+      let(:role) { UserRole.everyone }
 
       it 'fails to redownload' do
         is_expected.to have_http_status :forbidden
@@ -173,11 +177,11 @@ RSpec.describe Admin::AccountsController, type: :controller do
   describe 'POST #remove_avatar' do
     subject { post :remove_avatar, params: { id: account.id } }
 
-    let(:current_user) { Fabricate(:user, admin: admin) }
+    let(:current_user) { Fabricate(:user, role: role) }
     let(:account) { Fabricate(:account) }
 
     context 'when user is admin' do
-      let(:admin) { true }
+      let(:role) { UserRole.find_by(name: 'Admin') }
 
       it 'succeeds in removing avatar' do
         is_expected.to redirect_to admin_account_path(account.id)
@@ -185,7 +189,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
     end
 
     context 'when user is not admin' do
-      let(:admin) { false }
+      let(:role) { UserRole.everyone }
 
       it 'fails to remove avatar' do
         is_expected.to have_http_status :forbidden
@@ -196,12 +200,12 @@ RSpec.describe Admin::AccountsController, type: :controller do
   describe 'POST #unblock_email' do
     subject { post :unblock_email, params: { id: account.id } }
 
-    let(:current_user) { Fabricate(:user, admin: admin) }
+    let(:current_user) { Fabricate(:user, role: role) }
     let(:account) { Fabricate(:account, suspended: true) }
     let!(:email_block) { Fabricate(:canonical_email_block, reference_account: account) }
 
     context 'when user is admin' do
-      let(:admin) { true }
+      let(:role) { UserRole.find_by(name: 'Admin') }
 
       it 'succeeds in removing email blocks' do
         expect { subject }.to change { CanonicalEmailBlock.where(reference_account: account).count }.from(1).to(0)
@@ -214,7 +218,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
     end
 
     context 'when user is not admin' do
-      let(:admin) { false }
+      let(:role) { UserRole.everyone }
 
       it 'fails to remove avatar' do
         subject
diff --git a/spec/controllers/admin/action_logs_controller_spec.rb b/spec/controllers/admin/action_logs_controller_spec.rb
index 4720ed2e2..c1957258f 100644
--- a/spec/controllers/admin/action_logs_controller_spec.rb
+++ b/spec/controllers/admin/action_logs_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 describe Admin::ActionLogsController, type: :controller do
   describe 'GET #index' do
     it 'returns 200' do
-      sign_in Fabricate(:user, admin: true)
+      sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin'))
       get :index, params: { page: 1 }
 
       expect(response).to have_http_status(200)
diff --git a/spec/controllers/admin/base_controller_spec.rb b/spec/controllers/admin/base_controller_spec.rb
index 9ac833623..44be91951 100644
--- a/spec/controllers/admin/base_controller_spec.rb
+++ b/spec/controllers/admin/base_controller_spec.rb
@@ -5,13 +5,14 @@ require 'rails_helper'
 describe Admin::BaseController, type: :controller do
   controller do
     def success
+      authorize :dashboard, :index?
       render 'admin/reports/show'
     end
   end
 
   it 'requires administrator or moderator' do
     routes.draw { get 'success' => 'admin/base#success' }
-    sign_in(Fabricate(:user, admin: false, moderator: false))
+    sign_in(Fabricate(:user))
     get :success
 
     expect(response).to have_http_status(:forbidden)
@@ -19,14 +20,14 @@ describe Admin::BaseController, type: :controller do
 
   it 'renders admin layout as a moderator' do
     routes.draw { get 'success' => 'admin/base#success' }
-    sign_in(Fabricate(:user, moderator: true))
+    sign_in(Fabricate(:user, role: UserRole.find_by(name: 'Moderator')))
     get :success
     expect(response).to render_template layout: 'admin'
   end
 
   it 'renders admin layout as an admin' do
     routes.draw { get 'success' => 'admin/base#success' }
-    sign_in(Fabricate(:user, admin: true))
+    sign_in(Fabricate(:user, role: UserRole.find_by(name: 'Admin')))
     get :success
     expect(response).to render_template layout: 'admin'
   end
diff --git a/spec/controllers/admin/change_email_controller_spec.rb b/spec/controllers/admin/change_email_controller_spec.rb
index e7f3f7c97..cf8a27d39 100644
--- a/spec/controllers/admin/change_email_controller_spec.rb
+++ b/spec/controllers/admin/change_email_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Admin::ChangeEmailsController, type: :controller do
   render_views
 
-  let(:admin) { Fabricate(:user, admin: true) }
+  let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
   before do
     sign_in admin
diff --git a/spec/controllers/admin/confirmations_controller_spec.rb b/spec/controllers/admin/confirmations_controller_spec.rb
index 5b4f7e925..6268903c4 100644
--- a/spec/controllers/admin/confirmations_controller_spec.rb
+++ b/spec/controllers/admin/confirmations_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Admin::ConfirmationsController, type: :controller do
   render_views
 
   before do
-    sign_in Fabricate(:user, admin: true), scope: :user
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
   end
 
   describe 'POST #create' do
diff --git a/spec/controllers/admin/custom_emojis_controller_spec.rb b/spec/controllers/admin/custom_emojis_controller_spec.rb
index a8d96948c..06cd0c22d 100644
--- a/spec/controllers/admin/custom_emojis_controller_spec.rb
+++ b/spec/controllers/admin/custom_emojis_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 describe Admin::CustomEmojisController do
   render_views
 
-  let(:user) { Fabricate(:user, admin: true) }
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
   before do
     sign_in user, scope: :user
diff --git a/spec/controllers/admin/dashboard_controller_spec.rb b/spec/controllers/admin/dashboard_controller_spec.rb
index 7824854f9..6231a09a2 100644
--- a/spec/controllers/admin/dashboard_controller_spec.rb
+++ b/spec/controllers/admin/dashboard_controller_spec.rb
@@ -12,7 +12,7 @@ describe Admin::DashboardController, type: :controller do
         Admin::SystemCheck::Message.new(:rules_check, nil, admin_rules_path),
         Admin::SystemCheck::Message.new(:sidekiq_process_check, 'foo, bar'),
       ])
-      sign_in Fabricate(:user, admin: true)
+      sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin'))
     end
 
     it 'returns 200' do
diff --git a/spec/controllers/admin/disputes/appeals_controller_spec.rb b/spec/controllers/admin/disputes/appeals_controller_spec.rb
index 6a06f9406..712657791 100644
--- a/spec/controllers/admin/disputes/appeals_controller_spec.rb
+++ b/spec/controllers/admin/disputes/appeals_controller_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Admin::Disputes::AppealsController, type: :controller do
   end
 
   describe 'POST #approve' do
-    let(:current_user) { Fabricate(:user, admin: true) }
+    let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
     before do
       allow(UserMailer).to receive(:appeal_approved).and_return(double('email', deliver_later: nil))
@@ -35,7 +35,7 @@ RSpec.describe Admin::Disputes::AppealsController, type: :controller do
   end
 
   describe 'POST #reject' do
-    let(:current_user) { Fabricate(:user, admin: true) }
+    let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
     before do
       allow(UserMailer).to receive(:appeal_rejected).and_return(double('email', deliver_later: nil))
diff --git a/spec/controllers/admin/domain_allows_controller_spec.rb b/spec/controllers/admin/domain_allows_controller_spec.rb
index 8bacdd3e4..6c4e67787 100644
--- a/spec/controllers/admin/domain_allows_controller_spec.rb
+++ b/spec/controllers/admin/domain_allows_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Admin::DomainAllowsController, type: :controller do
   render_views
 
   before do
-    sign_in Fabricate(:user, admin: true), scope: :user
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
   end
 
   describe 'GET #new' do
diff --git a/spec/controllers/admin/domain_blocks_controller_spec.rb b/spec/controllers/admin/domain_blocks_controller_spec.rb
index a35b2fb3b..98cda5004 100644
--- a/spec/controllers/admin/domain_blocks_controller_spec.rb
+++ b/spec/controllers/admin/domain_blocks_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do
   render_views
 
   before do
-    sign_in Fabricate(:user, admin: true), scope: :user
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
   end
 
   describe 'GET #new' do
diff --git a/spec/controllers/admin/email_domain_blocks_controller_spec.rb b/spec/controllers/admin/email_domain_blocks_controller_spec.rb
index cf194579d..e9cef4a94 100644
--- a/spec/controllers/admin/email_domain_blocks_controller_spec.rb
+++ b/spec/controllers/admin/email_domain_blocks_controller_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Admin::EmailDomainBlocksController, type: :controller do
   render_views
 
   before do
-    sign_in Fabricate(:user, admin: true), scope: :user
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
   end
 
   describe 'GET #index' do
diff --git a/spec/controllers/admin/export_domain_allows_controller_spec.rb b/spec/controllers/admin/export_domain_allows_controller_spec.rb
index f6275c2d6..1e1a5ae7d 100644
--- a/spec/controllers/admin/export_domain_allows_controller_spec.rb
+++ b/spec/controllers/admin/export_domain_allows_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Admin::ExportDomainAllowsController, type: :controller do
   render_views
 
   before do
-    sign_in Fabricate(:user, admin: true), scope: :user
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
   end
 
   describe 'GET #export' do
diff --git a/spec/controllers/admin/export_domain_blocks_controller_spec.rb b/spec/controllers/admin/export_domain_blocks_controller_spec.rb
index 0493df859..8697e0c21 100644
--- a/spec/controllers/admin/export_domain_blocks_controller_spec.rb
+++ b/spec/controllers/admin/export_domain_blocks_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Admin::ExportDomainBlocksController, type: :controller do
   render_views
 
   before do
-    sign_in Fabricate(:user, admin: true), scope: :user
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
   end
 
   describe 'GET #export' do
diff --git a/spec/controllers/admin/instances_controller_spec.rb b/spec/controllers/admin/instances_controller_spec.rb
index 53427b874..337f7a80c 100644
--- a/spec/controllers/admin/instances_controller_spec.rb
+++ b/spec/controllers/admin/instances_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Admin::InstancesController, type: :controller do
   render_views
 
-  let(:current_user) { Fabricate(:user, admin: true) }
+  let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
   let!(:account)     { Fabricate(:account, domain: 'popular') }
   let!(:account2)    { Fabricate(:account, domain: 'popular') }
@@ -35,11 +35,11 @@ RSpec.describe Admin::InstancesController, type: :controller do
   describe 'DELETE #destroy' do
     subject { delete :destroy, params: { id: Instance.first.id } }
 
-    let(:current_user) { Fabricate(:user, admin: admin) }
+    let(:current_user) { Fabricate(:user, role: role) }
     let(:account) { Fabricate(:account) }
 
     context 'when user is admin' do
-      let(:admin) { true }
+      let(:role) { UserRole.find_by(name: 'Admin') }
 
       it 'succeeds in purging instance' do
         is_expected.to redirect_to admin_instances_path
@@ -47,7 +47,7 @@ RSpec.describe Admin::InstancesController, type: :controller do
     end
 
     context 'when user is not admin' do
-      let(:admin) { false }
+      let(:role) { nil }
 
       it 'fails to purge instance' do
         is_expected.to have_http_status :forbidden
diff --git a/spec/controllers/admin/invites_controller_spec.rb b/spec/controllers/admin/invites_controller_spec.rb
index 449a699e4..1fb488742 100644
--- a/spec/controllers/admin/invites_controller_spec.rb
+++ b/spec/controllers/admin/invites_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 describe Admin::InvitesController do
   render_views
 
-  let(:user) { Fabricate(:user, admin: true) }
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
   before do
     sign_in user, scope: :user
diff --git a/spec/controllers/admin/report_notes_controller_spec.rb b/spec/controllers/admin/report_notes_controller_spec.rb
index c0013f41a..fa7572d18 100644
--- a/spec/controllers/admin/report_notes_controller_spec.rb
+++ b/spec/controllers/admin/report_notes_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 describe Admin::ReportNotesController do
   render_views
 
-  let(:user) { Fabricate(:user, admin: true) }
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
   before do
     sign_in user, scope: :user
diff --git a/spec/controllers/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb
index d421f0739..4cd1524bf 100644
--- a/spec/controllers/admin/reports_controller_spec.rb
+++ b/spec/controllers/admin/reports_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 describe Admin::ReportsController do
   render_views
 
-  let(:user) { Fabricate(:user, admin: true) }
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
   before do
     sign_in user, scope: :user
   end
diff --git a/spec/controllers/admin/resets_controller_spec.rb b/spec/controllers/admin/resets_controller_spec.rb
index 28510b5af..aeb172318 100644
--- a/spec/controllers/admin/resets_controller_spec.rb
+++ b/spec/controllers/admin/resets_controller_spec.rb
@@ -5,7 +5,7 @@ describe Admin::ResetsController do
 
   let(:account) { Fabricate(:account) }
   before do
-    sign_in Fabricate(:user, admin: true), scope: :user
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
   end
 
   describe 'POST #create' do
diff --git a/spec/controllers/admin/roles_controller_spec.rb b/spec/controllers/admin/roles_controller_spec.rb
index 8e0de73cb..8ff891205 100644
--- a/spec/controllers/admin/roles_controller_spec.rb
+++ b/spec/controllers/admin/roles_controller_spec.rb
@@ -3,31 +3,247 @@ require 'rails_helper'
 describe Admin::RolesController do
   render_views
 
-  let(:admin) { Fabricate(:user, admin: true) }
+  let(:permissions)  { UserRole::Flags::NONE }
+  let(:current_role) { UserRole.create(name: 'Foo', permissions: permissions, position: 10) }
+  let(:current_user) { Fabricate(:user, role: current_role) }
 
   before do
-    sign_in admin, scope: :user
+    sign_in current_user, scope: :user
   end
 
-  describe 'POST #promote' do
-    subject { post :promote, params: { account_id: user.account_id } }
+  describe 'GET #index' do
+    before do
+      get :index
+    end
+
+    context 'when user does not have permission to manage roles' do
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(:forbidden)
+      end
+    end
 
-    let(:user) { Fabricate(:user, moderator: false, admin: false) }
+    context 'when user has permission to manage roles' do
+      let(:permissions) { UserRole::FLAGS[:manage_roles] }
 
-    it 'promotes user' do
-      expect(subject).to redirect_to admin_account_path(user.account_id)
-      expect(user.reload).to be_moderator
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
+      end
     end
   end
 
-  describe 'POST #demote' do
-    subject { post :demote, params: { account_id: user.account_id } }
+  describe 'GET #new' do
+    before do
+      get :new
+    end
+
+    context 'when user does not have permission to manage roles' do
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(:forbidden)
+      end
+    end
+
+    context 'when user has permission to manage roles' do
+      let(:permissions) { UserRole::FLAGS[:manage_roles] }
+
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
+      end
+    end
+  end
+
+  describe 'POST #create' do
+    let(:selected_position) { 1 }
+    let(:selected_permissions_as_keys) { %w(manage_roles) }
+
+    before do
+      post :create, params: { user_role: { name: 'Bar', position: selected_position, permissions_as_keys: selected_permissions_as_keys } }
+    end
+
+    context 'when user has permission to manage roles' do
+      let(:permissions) { UserRole::FLAGS[:manage_roles] }
+
+      context 'when new role\'s does not elevate above the user\'s role' do
+        let(:selected_position) { 1 }
+        let(:selected_permissions_as_keys) { %w(manage_roles) }
+
+        it 'redirects to roles page' do
+          expect(response).to redirect_to(admin_roles_path)
+        end
+
+        it 'creates new role' do
+          expect(UserRole.find_by(name: 'Bar')).to_not be_nil
+        end
+      end
+
+      context 'when new role\'s position is higher than user\'s role' do
+        let(:selected_position) { 100 }
+        let(:selected_permissions_as_keys) { %w(manage_roles) }
+
+        it 'renders new template' do
+          expect(response).to render_template(:new)
+        end
+
+        it 'does not create new role' do
+          expect(UserRole.find_by(name: 'Bar')).to be_nil
+        end
+      end
+
+      context 'when new role has permissions the user does not have' do
+        let(:selected_position) { 1 }
+        let(:selected_permissions_as_keys) { %w(manage_roles manage_users manage_reports) }
+
+        it 'renders new template' do
+          expect(response).to render_template(:new)
+        end
+
+        it 'does not create new role' do
+          expect(UserRole.find_by(name: 'Bar')).to be_nil
+        end
+      end
+
+      context 'when user has administrator permission' do
+        let(:permissions) { UserRole::FLAGS[:administrator] }
+
+        let(:selected_position) { 1 }
+        let(:selected_permissions_as_keys) { %w(manage_roles manage_users manage_reports) }
+
+        it 'redirects to roles page' do
+          expect(response).to redirect_to(admin_roles_path)
+        end
+
+        it 'creates new role' do
+          expect(UserRole.find_by(name: 'Bar')).to_not be_nil
+        end
+      end
+    end
+  end
+
+  describe 'GET #edit' do
+    let(:role_position) { 8 }
+    let(:role) { UserRole.create(name: 'Bar', permissions: UserRole::FLAGS[:manage_users], position: role_position) }
+
+    before do
+      get :edit, params: { id: role.id }
+    end
+
+    context 'when user does not have permission to manage roles' do
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(:forbidden)
+      end
+    end
+
+    context 'when user has permission to manage roles' do
+      let(:permissions) { UserRole::FLAGS[:manage_roles] }
+
+      context 'when user outranks the role' do
+        it 'returns http success' do
+          expect(response).to have_http_status(:success)
+        end
+      end
+
+      context 'when role outranks user' do
+        let(:role_position) { current_role.position + 1 }
+
+        it 'returns http forbidden' do
+          expect(response).to have_http_status(:forbidden)
+        end
+      end
+    end
+  end
+
+  describe 'PUT #update' do
+    let(:role_position) { 8 }
+    let(:role_permissions) { UserRole::FLAGS[:manage_users] }
+    let(:role) { UserRole.create(name: 'Bar', permissions: role_permissions, position: role_position) }
+
+    let(:selected_position) { 8 }
+    let(:selected_permissions_as_keys) { %w(manage_users) }
+
+    before do
+      put :update, params: { id: role.id, user_role: { name: 'Baz', position: selected_position, permissions_as_keys: selected_permissions_as_keys } }
+    end
+
+    context 'when user does not have permission to manage roles' do
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(:forbidden)
+      end
+
+      it 'does not update the role' do
+        expect(role.reload.name).to eq 'Bar'
+      end
+    end
+
+    context 'when user has permission to manage roles' do
+      let(:permissions) { UserRole::FLAGS[:manage_roles] }
+
+      context 'when role has permissions the user doesn\'t' do
+        it 'renders edit template' do
+          expect(response).to render_template(:edit)
+        end
+
+        it 'does not update the role' do
+          expect(role.reload.name).to eq 'Bar'
+        end
+      end
+
+      context 'when user has all permissions of the role' do
+        let(:permissions) { UserRole::FLAGS[:manage_roles] | UserRole::FLAGS[:manage_users] }
+
+        context 'when user outranks the role' do
+          it 'redirects to roles page' do
+            expect(response).to redirect_to(admin_roles_path)
+          end
+
+          it 'updates the role' do
+            expect(role.reload.name).to eq 'Baz'
+          end
+        end
+
+        context 'when role outranks user' do
+          let(:role_position) { current_role.position + 1 }
+
+          it 'returns http forbidden' do
+            expect(response).to have_http_status(:forbidden)
+          end
+
+          it 'does not update the role' do
+            expect(role.reload.name).to eq 'Bar'
+          end
+        end
+      end
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    let(:role_position) { 8 }
+    let(:role) { UserRole.create(name: 'Bar', permissions: UserRole::FLAGS[:manage_users], position: role_position) }
+
+    before do
+      delete :destroy, params: { id: role.id }
+    end
+
+    context 'when user does not have permission to manage roles' do
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(:forbidden)
+      end
+    end
+
+    context 'when user has permission to manage roles' do
+      let(:permissions) { UserRole::FLAGS[:manage_roles] }
+
+      context 'when user outranks the role' do
+        it 'redirects to roles page' do
+          expect(response).to redirect_to(admin_roles_path)
+        end
+      end
 
-    let(:user) { Fabricate(:user, moderator: true, admin: false) }
+      context 'when role outranks user' do
+        let(:role_position) { current_role.position + 1 }
 
-    it 'demotes user' do
-      expect(subject).to redirect_to admin_account_path(user.account_id)
-      expect(user.reload).not_to be_moderator
+        it 'returns http forbidden' do
+          expect(response).to have_http_status(:forbidden)
+        end
+      end
     end
   end
 end
diff --git a/spec/controllers/admin/settings_controller_spec.rb b/spec/controllers/admin/settings_controller_spec.rb
index 6cf0ee20a..46749f76c 100644
--- a/spec/controllers/admin/settings_controller_spec.rb
+++ b/spec/controllers/admin/settings_controller_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Admin::SettingsController, type: :controller do
 
   describe 'When signed in as an admin' do
     before do
-      sign_in Fabricate(:user, admin: true), scope: :user
+      sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
     end
 
     describe 'GET #edit' do
diff --git a/spec/controllers/admin/statuses_controller_spec.rb b/spec/controllers/admin/statuses_controller_spec.rb
index de32fd18e..227688e23 100644
--- a/spec/controllers/admin/statuses_controller_spec.rb
+++ b/spec/controllers/admin/statuses_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 describe Admin::StatusesController do
   render_views
 
-  let(:user) { Fabricate(:user, admin: true) }
+  let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
   let(:account) { Fabricate(:account) }
   let!(:status) { Fabricate(:status, account: account) }
   let(:media_attached_status) { Fabricate(:status, account: account, sensitive: !sensitive) }
diff --git a/spec/controllers/admin/tags_controller_spec.rb b/spec/controllers/admin/tags_controller_spec.rb
index 85c801a9c..52fd09eb1 100644
--- a/spec/controllers/admin/tags_controller_spec.rb
+++ b/spec/controllers/admin/tags_controller_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Admin::TagsController, type: :controller do
   render_views
 
   before do
-    sign_in Fabricate(:user, admin: true)
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin'))
   end
 
   describe 'GET #show' do
diff --git a/spec/controllers/admin/users/roles_controller.rb b/spec/controllers/admin/users/roles_controller.rb
new file mode 100644
index 000000000..bd6a3fa67
--- /dev/null
+++ b/spec/controllers/admin/users/roles_controller.rb
@@ -0,0 +1,81 @@
+require 'rails_helper'
+
+describe Admin::Users::RolesController do
+  render_views
+
+  let(:current_role) { UserRole.create(name: 'Foo', permissions: UserRole::FLAGS[:manage_roles], position: 10) }
+  let(:current_user) { Fabricate(:user, role: current_role) }
+
+  let(:previous_role) { nil }
+  let(:user) { Fabricate(:user, role: previous_role) }
+
+  before do
+    sign_in current_user, scope: :user
+  end
+
+  describe 'GET #show' do
+    before do
+      get :show, params: { user_id: user.id }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(:success)
+    end
+
+    context 'when target user is higher ranked than current user' do
+      let(:previous_role) { UserRole.create(name: 'Baz', permissions: UserRole::FLAGS[:administrator], position: 100) }
+
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(:forbidden)
+      end
+    end
+  end
+
+  describe 'PUT #update' do
+    let(:selected_role) { UserRole.create(name: 'Bar', permissions: permissions, position: position) }
+
+    before do
+      put :update, params: { user_id: user.id, user: { role_id: selected_role.id } }
+    end
+
+    context do
+      let(:permissions) { UserRole::FLAGS[:manage_roles] }
+      let(:position) { 1 }
+
+      it 'updates user role' do
+        expect(user.reload.role_id).to eq selected_role&.id
+      end
+
+      it 'redirects back to account page' do
+        expect(response).to redirect_to(admin_account_path(user.account_id))
+      end
+    end
+
+    context 'when selected role has higher position than current user\'s role' do
+      let(:permissions) { UserRole::FLAGS[:administrator] }
+      let(:position) { 100 }
+
+      it 'does not update user role' do
+        expect(user.reload.role_id).to eq previous_role&.id
+      end
+
+      it 'renders edit form' do
+        expect(response).to render_template(:show)
+      end
+    end
+
+    context 'when target user is higher ranked than current user' do
+      let(:previous_role) { UserRole.create(name: 'Baz', permissions: UserRole::FLAGS[:administrator], position: 100) }
+      let(:permissions) { UserRole::FLAGS[:manage_roles] }
+      let(:position) { 1 }
+
+      it 'does not update user role' do
+        expect(user.reload.role_id).to eq previous_role&.id
+      end
+
+      it 'returns http forbidden' do
+        expect(response).to have_http_status(:forbidden)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/admin/two_factor_authentications_controller_spec.rb b/spec/controllers/admin/users/two_factor_authentications_controller_spec.rb
index c65095729..e56264ef6 100644
--- a/spec/controllers/admin/two_factor_authentications_controller_spec.rb
+++ b/spec/controllers/admin/users/two_factor_authentications_controller_spec.rb
@@ -1,12 +1,13 @@
 require 'rails_helper'
 require 'webauthn/fake_client'
 
-describe Admin::TwoFactorAuthenticationsController do
+describe Admin::Users::TwoFactorAuthenticationsController do
   render_views
 
   let(:user) { Fabricate(:user) }
+
   before do
-    sign_in Fabricate(:user, admin: true), scope: :user
+    sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
   end
 
   describe 'DELETE #destroy' do
diff --git a/spec/controllers/api/v1/admin/account_actions_controller_spec.rb b/spec/controllers/api/v1/admin/account_actions_controller_spec.rb
index 601290b82..199395f55 100644
--- a/spec/controllers/api/v1/admin/account_actions_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/account_actions_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::Admin::AccountActionsController, type: :controller do
   render_views
 
-  let(:role)   { 'moderator' }
+  let(:role)   { UserRole.find_by(name: 'Moderator') }
   let(:user)   { Fabricate(:user, role: role) }
   let(:scopes) { 'admin:read admin:write' }
   let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
@@ -22,7 +22,7 @@ RSpec.describe Api::V1::Admin::AccountActionsController, type: :controller do
   end
 
   shared_examples 'forbidden for wrong role' do |wrong_role|
-    let(:role) { wrong_role }
+    let(:role) { UserRole.find_by(name: wrong_role) }
 
     it 'returns http forbidden' do
       expect(response).to have_http_status(403)
@@ -35,7 +35,7 @@ RSpec.describe Api::V1::Admin::AccountActionsController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
+    it_behaves_like 'forbidden for wrong role', ''
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
diff --git a/spec/controllers/api/v1/admin/accounts_controller_spec.rb b/spec/controllers/api/v1/admin/accounts_controller_spec.rb
index b69595f7e..cd38030e0 100644
--- a/spec/controllers/api/v1/admin/accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/accounts_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
   render_views
 
-  let(:role)   { 'moderator' }
+  let(:role)   { UserRole.find_by(name: 'Moderator') }
   let(:user)   { Fabricate(:user, role: role) }
   let(:scopes) { 'admin:read admin:write' }
   let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
@@ -22,7 +22,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
   end
 
   shared_examples 'forbidden for wrong role' do |wrong_role|
-    let(:role) { wrong_role }
+    let(:role) { UserRole.find_by(name: wrong_role) }
 
     it 'returns http forbidden' do
       expect(response).to have_http_status(403)
@@ -46,7 +46,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
+    it_behaves_like 'forbidden for wrong role', ''
 
     [
       [{ active: 'true', local: 'true', staff: 'true' }, [:admin_account]],
@@ -77,7 +77,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
+    it_behaves_like 'forbidden for wrong role', ''
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
@@ -91,7 +91,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
+    it_behaves_like 'forbidden for wrong role', ''
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
@@ -109,7 +109,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
+    it_behaves_like 'forbidden for wrong role', ''
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
@@ -127,7 +127,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
+    it_behaves_like 'forbidden for wrong role', ''
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
@@ -145,7 +145,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
+    it_behaves_like 'forbidden for wrong role', ''
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
@@ -163,7 +163,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
+    it_behaves_like 'forbidden for wrong role', ''
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
@@ -181,7 +181,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
+    it_behaves_like 'forbidden for wrong role', ''
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
diff --git a/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb b/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb
new file mode 100644
index 000000000..26a391a60
--- /dev/null
+++ b/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb
@@ -0,0 +1,118 @@
+require 'rails_helper'
+
+RSpec.describe Api::V1::Admin::DomainAllowsController, type: :controller do
+  render_views
+
+  let(:role)   { UserRole.find_by(name: 'Admin') }
+  let(:user)   { Fabricate(:user, role: role) }
+  let(:scopes) { 'admin:read admin:write' }
+  let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
+
+  before do
+    allow(controller).to receive(:doorkeeper_token) { token }
+  end
+
+  shared_examples 'forbidden for wrong scope' do |wrong_scope|
+    let(:scopes) { wrong_scope }
+
+    it 'returns http forbidden' do
+      expect(response).to have_http_status(403)
+    end
+  end
+
+  shared_examples 'forbidden for wrong role' do |wrong_role|
+    let(:role) { UserRole.find_by(name: wrong_role) }
+
+    it 'returns http forbidden' do
+      expect(response).to have_http_status(403)
+    end
+  end
+
+  describe 'GET #index' do
+    let!(:domain_allow) { Fabricate(:domain_allow) }
+
+    before do
+      get :index
+    end
+
+    it_behaves_like 'forbidden for wrong scope', 'write:statuses'
+    it_behaves_like 'forbidden for wrong role', ''
+    it_behaves_like 'forbidden for wrong role', 'Moderator'
+
+    it 'returns http success' do
+      expect(response).to have_http_status(200)
+    end
+
+    it 'returns the expected domain allows' do
+      json = body_as_json
+      expect(json.length).to eq 1
+      expect(json[0][:id].to_i).to eq domain_allow.id
+    end
+  end
+
+  describe 'GET #show' do
+    let!(:domain_allow) { Fabricate(:domain_allow) }
+
+    before do
+      get :show, params: { id: domain_allow.id }
+    end
+
+    it_behaves_like 'forbidden for wrong scope', 'write:statuses'
+    it_behaves_like 'forbidden for wrong role', ''
+    it_behaves_like 'forbidden for wrong role', 'Moderator'
+
+    it 'returns http success' do
+      expect(response).to have_http_status(200)
+    end
+
+    it 'returns expected domain name' do
+      json = body_as_json
+      expect(json[:domain]).to eq domain_allow.domain
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    let!(:domain_allow) { Fabricate(:domain_allow) }
+
+    before do
+      delete :destroy, params: { id: domain_allow.id }
+    end
+
+    it_behaves_like 'forbidden for wrong scope', 'write:statuses'
+    it_behaves_like 'forbidden for wrong role', ''
+    it_behaves_like 'forbidden for wrong role', 'Moderator'
+
+    it 'returns http success' do
+      expect(response).to have_http_status(200)
+    end
+
+    it 'deletes the block' do
+      expect(DomainAllow.find_by(id: domain_allow.id)).to be_nil
+    end
+  end
+
+  describe 'POST #create' do
+    let!(:domain_allow) { Fabricate(:domain_allow, domain: 'example.com') }
+
+    before do
+      post :create, params: { domain: 'foo.bar.com' }
+    end
+
+    it_behaves_like 'forbidden for wrong scope', 'write:statuses'
+    it_behaves_like 'forbidden for wrong role', ''
+    it_behaves_like 'forbidden for wrong role', 'Moderator'
+
+    it 'returns http success' do
+      expect(response).to have_http_status(200)
+    end
+
+    it 'returns expected domain name' do
+      json = body_as_json
+      expect(json[:domain]).to eq 'foo.bar.com'
+    end
+
+    it 'creates a domain block' do
+      expect(DomainAllow.find_by(domain: 'foo.bar.com')).to_not be_nil
+    end
+  end
+end
diff --git a/spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb b/spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb
index 196f6dc28..f12285b2a 100644
--- a/spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::Admin::DomainBlocksController, type: :controller do
   render_views
 
-  let(:role)   { 'admin' }
+  let(:role)   { UserRole.find_by(name: 'Admin') }
   let(:user)   { Fabricate(:user, role: role) }
   let(:scopes) { 'admin:read admin:write' }
   let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
@@ -21,7 +21,7 @@ RSpec.describe Api::V1::Admin::DomainBlocksController, type: :controller do
   end
 
   shared_examples 'forbidden for wrong role' do |wrong_role|
-    let(:role) { wrong_role }
+    let(:role) { UserRole.find_by(name: wrong_role) }
 
     it 'returns http forbidden' do
       expect(response).to have_http_status(403)
@@ -36,8 +36,8 @@ RSpec.describe Api::V1::Admin::DomainBlocksController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
-    it_behaves_like 'forbidden for wrong role', 'moderator'
+    it_behaves_like 'forbidden for wrong role', ''
+    it_behaves_like 'forbidden for wrong role', 'Moderator'
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
@@ -58,8 +58,8 @@ RSpec.describe Api::V1::Admin::DomainBlocksController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
-    it_behaves_like 'forbidden for wrong role', 'moderator'
+    it_behaves_like 'forbidden for wrong role', ''
+    it_behaves_like 'forbidden for wrong role', 'Moderator'
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
@@ -79,8 +79,8 @@ RSpec.describe Api::V1::Admin::DomainBlocksController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
-    it_behaves_like 'forbidden for wrong role', 'moderator'
+    it_behaves_like 'forbidden for wrong role', ''
+    it_behaves_like 'forbidden for wrong role', 'Moderator'
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
@@ -100,8 +100,8 @@ RSpec.describe Api::V1::Admin::DomainBlocksController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
-    it_behaves_like 'forbidden for wrong role', 'moderator'
+    it_behaves_like 'forbidden for wrong role', ''
+    it_behaves_like 'forbidden for wrong role', 'Moderator'
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
diff --git a/spec/controllers/api/v1/admin/reports_controller_spec.rb b/spec/controllers/api/v1/admin/reports_controller_spec.rb
index b6df53048..880e72030 100644
--- a/spec/controllers/api/v1/admin/reports_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/reports_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
   render_views
 
-  let(:role)   { 'moderator' }
+  let(:role)   { UserRole.find_by(name: 'Moderator') }
   let(:user)   { Fabricate(:user, role: role) }
   let(:scopes) { 'admin:read admin:write' }
   let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
@@ -22,7 +22,7 @@ RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
   end
 
   shared_examples 'forbidden for wrong role' do |wrong_role|
-    let(:role) { wrong_role }
+    let(:role) { UserRole.find_by(name: wrong_role) }
 
     it 'returns http forbidden' do
       expect(response).to have_http_status(403)
@@ -35,7 +35,7 @@ RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
+    it_behaves_like 'forbidden for wrong role', ''
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
@@ -48,7 +48,7 @@ RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
+    it_behaves_like 'forbidden for wrong role', ''
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
@@ -61,7 +61,7 @@ RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
+    it_behaves_like 'forbidden for wrong role', ''
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
@@ -74,7 +74,7 @@ RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
+    it_behaves_like 'forbidden for wrong role', ''
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
@@ -87,7 +87,7 @@ RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
+    it_behaves_like 'forbidden for wrong role', ''
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
@@ -100,7 +100,7 @@ RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
+    it_behaves_like 'forbidden for wrong role', ''
 
     it 'returns http success' do
       expect(response).to have_http_status(200)
diff --git a/spec/controllers/api/v1/filters/keywords_controller_spec.rb b/spec/controllers/api/v1/filters/keywords_controller_spec.rb
new file mode 100644
index 000000000..aecb4e41c
--- /dev/null
+++ b/spec/controllers/api/v1/filters/keywords_controller_spec.rb
@@ -0,0 +1,142 @@
+require 'rails_helper'
+
+RSpec.describe Api::V1::Filters::KeywordsController, type: :controller do
+  render_views
+
+  let(:user)         { Fabricate(:user) }
+  let(:token)        { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
+  let(:filter)       { Fabricate(:custom_filter, account: user.account) }
+  let(:other_user)   { Fabricate(:user) }
+  let(:other_filter) { Fabricate(:custom_filter, account: other_user.account) }
+
+  before do
+    allow(controller).to receive(:doorkeeper_token) { token }
+  end
+
+  describe 'GET #index' do
+    let(:scopes) { 'read:filters' }
+    let!(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) }
+
+    it 'returns http success' do
+      get :index, params: { filter_id: filter.id }
+      expect(response).to have_http_status(200)
+    end
+
+    context "when trying to access another's user filters" do
+      it 'returns http not found' do
+        get :index, params: { filter_id: other_filter.id }
+        expect(response).to have_http_status(404)
+      end
+    end
+  end
+
+  describe 'POST #create' do
+    let(:scopes)    { 'write:filters' }
+    let(:filter_id) { filter.id }
+
+    before do
+      post :create, params: { filter_id: filter_id, keyword: 'magic', whole_word: false }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(200)
+    end
+
+    it 'returns a keyword' do
+      json = body_as_json
+      expect(json[:keyword]).to eq 'magic'
+      expect(json[:whole_word]).to eq false
+    end
+
+    it 'creates a keyword' do
+      filter = user.account.custom_filters.first
+      expect(filter).to_not be_nil
+      expect(filter.keywords.pluck(:keyword)).to eq ['magic']
+    end
+
+    context "when trying to add to another another's user filters" do
+      let(:filter_id) { other_filter.id }
+
+      it 'returns http not found' do
+        expect(response).to have_http_status(404)
+      end
+    end
+  end
+
+  describe 'GET #show' do
+    let(:scopes)  { 'read:filters' }
+    let(:keyword) { Fabricate(:custom_filter_keyword, keyword: 'foo', whole_word: false, custom_filter: filter) }
+
+    before do
+      get :show, params: { id: keyword.id }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(200)
+    end
+
+    it 'returns expected data' do
+      json = body_as_json
+      expect(json[:keyword]).to eq 'foo'
+      expect(json[:whole_word]).to eq false
+    end
+
+    context "when trying to access another user's filter keyword" do
+      let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: other_filter) }
+
+      it 'returns http not found' do
+        expect(response).to have_http_status(404)
+      end
+    end
+  end
+
+  describe 'PUT #update' do
+    let(:scopes)  { 'write:filters' }
+    let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) }
+
+    before do
+      get :update, params: { id: keyword.id, keyword: 'updated' }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(200)
+    end
+
+    it 'updates the keyword' do
+      expect(keyword.reload.keyword).to eq 'updated'
+    end
+
+    context "when trying to update another user's filter keyword" do
+      let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: other_filter) }
+
+      it 'returns http not found' do
+        expect(response).to have_http_status(404)
+      end
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    let(:scopes)  { 'write:filters' }
+    let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) }
+
+    before do
+      delete :destroy, params: { id: keyword.id }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(200)
+    end
+
+    it 'removes the filter' do
+      expect { keyword.reload }.to raise_error ActiveRecord::RecordNotFound
+    end
+
+    context "when trying to update another user's filter keyword" do
+      let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: other_filter) }
+
+      it 'returns http not found' do
+        expect(response).to have_http_status(404)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/api/v1/filters_controller_spec.rb b/spec/controllers/api/v1/filters_controller_spec.rb
index 5948809e3..af1951f0b 100644
--- a/spec/controllers/api/v1/filters_controller_spec.rb
+++ b/spec/controllers/api/v1/filters_controller_spec.rb
@@ -34,7 +34,7 @@ RSpec.describe Api::V1::FiltersController, type: :controller do
     it 'creates a filter' do
       filter = user.account.custom_filters.first
       expect(filter).to_not be_nil
-      expect(filter.phrase).to eq 'magic'
+      expect(filter.keywords.pluck(:keyword)).to eq ['magic']
       expect(filter.context).to eq %w(home)
       expect(filter.irreversible?).to be true
       expect(filter.expires_at).to be_nil
@@ -42,21 +42,23 @@ RSpec.describe Api::V1::FiltersController, type: :controller do
   end
 
   describe 'GET #show' do
-    let(:scopes) { 'read:filters' }
-    let(:filter) { Fabricate(:custom_filter, account: user.account) }
+    let(:scopes)  { 'read:filters' }
+    let(:filter)  { Fabricate(:custom_filter, account: user.account) }
+    let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) }
 
     it 'returns http success' do
-      get :show, params: { id: filter.id }
+      get :show, params: { id: keyword.id }
       expect(response).to have_http_status(200)
     end
   end
 
   describe 'PUT #update' do
-    let(:scopes) { 'write:filters' }
-    let(:filter) { Fabricate(:custom_filter, account: user.account) }
+    let(:scopes)  { 'write:filters' }
+    let(:filter)  { Fabricate(:custom_filter, account: user.account) }
+    let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) }
 
     before do
-      put :update, params: { id: filter.id, phrase: 'updated' }
+      put :update, params: { id: keyword.id, phrase: 'updated' }
     end
 
     it 'returns http success' do
@@ -64,16 +66,17 @@ RSpec.describe Api::V1::FiltersController, type: :controller do
     end
 
     it 'updates the filter' do
-      expect(filter.reload.phrase).to eq 'updated'
+      expect(keyword.reload.phrase).to eq 'updated'
     end
   end
 
   describe 'DELETE #destroy' do
-    let(:scopes) { 'write:filters' }
-    let(:filter) { Fabricate(:custom_filter, account: user.account) }
+    let(:scopes)  { 'write:filters' }
+    let(:filter)  { Fabricate(:custom_filter, account: user.account) }
+    let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) }
 
     before do
-      delete :destroy, params: { id: filter.id }
+      delete :destroy, params: { id: keyword.id }
     end
 
     it 'returns http success' do
@@ -81,7 +84,7 @@ RSpec.describe Api::V1::FiltersController, type: :controller do
     end
 
     it 'removes the filter' do
-      expect { filter.reload }.to raise_error ActiveRecord::RecordNotFound
+      expect { keyword.reload }.to raise_error ActiveRecord::RecordNotFound
     end
   end
 end
diff --git a/spec/controllers/api/v1/followed_tags_controller_spec.rb b/spec/controllers/api/v1/followed_tags_controller_spec.rb
new file mode 100644
index 000000000..2191350ef
--- /dev/null
+++ b/spec/controllers/api/v1/followed_tags_controller_spec.rb
@@ -0,0 +1,23 @@
+require 'rails_helper'
+
+RSpec.describe Api::V1::FollowedTagsController, type: :controller do
+  render_views
+
+  let(:user)   { Fabricate(:user) }
+  let(:scopes) { 'read:follows' }
+  let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
+
+  before { allow(controller).to receive(:doorkeeper_token) { token } }
+
+  describe 'GET #index' do
+    let!(:tag_follows) { Fabricate.times(5, :tag_follow, account: user.account) }
+
+    before do
+      get :index, params: { limit: 1 }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(:success)
+    end
+  end
+end
diff --git a/spec/controllers/api/v1/reports_controller_spec.rb b/spec/controllers/api/v1/reports_controller_spec.rb
index b5baf60e1..dbc64e704 100644
--- a/spec/controllers/api/v1/reports_controller_spec.rb
+++ b/spec/controllers/api/v1/reports_controller_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Api::V1::ReportsController, type: :controller do
   end
 
   describe 'POST #create' do
-    let!(:admin) { Fabricate(:user, admin: true) }
+    let!(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
     let(:scopes) { 'write:reports' }
     let(:status) { Fabricate(:status) }
diff --git a/spec/controllers/api/v1/statuses_controller_spec.rb b/spec/controllers/api/v1/statuses_controller_spec.rb
index 2eb30af74..4d104a198 100644
--- a/spec/controllers/api/v1/statuses_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses_controller_spec.rb
@@ -20,6 +20,58 @@ RSpec.describe Api::V1::StatusesController, type: :controller do
         get :show, params: { id: status.id }
         expect(response).to have_http_status(200)
       end
+
+      context 'when post includes filtered terms' do
+        let(:status) { Fabricate(:status, text: 'this toot is about that banned word') }
+
+        before do
+          user.account.custom_filters.create!(phrase: 'filter1', context: %w(home), action: :hide, keywords_attributes: [{ keyword: 'banned' }, { keyword: 'irrelevant' }])
+        end
+
+        it 'returns http success' do
+          get :show, params: { id: status.id }
+          expect(response).to have_http_status(200)
+        end
+
+        it 'returns filter information' do
+          get :show, params: { id: status.id }
+          json = body_as_json
+          expect(json[:filtered][0]).to include({
+            filter: a_hash_including({
+              id: user.account.custom_filters.first.id.to_s,
+              title: 'filter1',
+              filter_action: 'hide',
+            }),
+            keyword_matches: ['banned'],
+          })
+        end
+      end
+
+      context 'when reblog includes filtered terms' do
+        let(:status) { Fabricate(:status, reblog: Fabricate(:status, text: 'this toot is about that banned word')) }
+
+        before do
+          user.account.custom_filters.create!(phrase: 'filter1', context: %w(home), action: :hide, keywords_attributes: [{ keyword: 'banned' }, { keyword: 'irrelevant' }])
+        end
+
+        it 'returns http success' do
+          get :show, params: { id: status.id }
+          expect(response).to have_http_status(200)
+        end
+
+        it 'returns filter information' do
+          get :show, params: { id: status.id }
+          json = body_as_json
+          expect(json[:reblog][:filtered][0]).to include({
+            filter: a_hash_including({
+              id: user.account.custom_filters.first.id.to_s,
+              title: 'filter1',
+              filter_action: 'hide',
+            }),
+            keyword_matches: ['banned'],
+          })
+        end
+      end
     end
 
     describe 'GET #context' do
diff --git a/spec/controllers/api/v1/tags_controller_spec.rb b/spec/controllers/api/v1/tags_controller_spec.rb
new file mode 100644
index 000000000..ac42660df
--- /dev/null
+++ b/spec/controllers/api/v1/tags_controller_spec.rb
@@ -0,0 +1,82 @@
+require 'rails_helper'
+
+RSpec.describe Api::V1::TagsController, type: :controller do
+  render_views
+
+  let(:user)   { Fabricate(:user) }
+  let(:scopes) { 'write:follows' }
+  let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
+
+  before { allow(controller).to receive(:doorkeeper_token) { token } }
+
+  describe 'GET #show' do
+    before do
+      get :show, params: { id: name }
+    end
+
+    context 'with existing tag' do
+      let!(:tag) { Fabricate(:tag) }
+      let(:name) { tag.name }
+
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
+      end
+    end
+
+    context 'with non-existing tag' do
+      let(:name) { 'hoge' }
+
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
+      end
+    end
+  end
+
+  describe 'POST #follow' do
+    before do
+      post :follow, params: { id: name }
+    end
+
+    context 'with existing tag' do
+      let!(:tag) { Fabricate(:tag) }
+      let(:name) { tag.name }
+
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
+      end
+
+      it 'creates follow' do
+        expect(TagFollow.where(tag: tag, account: user.account).exists?).to be true
+      end
+    end
+
+    context 'with non-existing tag' do
+      let(:name) { 'hoge' }
+
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
+      end
+
+      it 'creates follow' do
+        expect(TagFollow.where(tag: Tag.find_by!(name: name), account: user.account).exists?).to be true
+      end
+    end
+  end
+
+  describe 'POST #unfollow' do
+    let!(:tag) { Fabricate(:tag, name: 'foo') }
+    let!(:tag_follow) { Fabricate(:tag_follow, account: user.account, tag: tag) }
+
+    before do
+      post :unfollow, params: { id: tag.name }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(:success)
+    end
+
+    it 'removes the follow' do
+      expect(TagFollow.where(tag: tag, account: user.account).exists?).to be false
+    end
+  end
+end
diff --git a/spec/controllers/api/v2/admin/accounts_controller_spec.rb b/spec/controllers/api/v2/admin/accounts_controller_spec.rb
index 3212ddb84..2508a9e05 100644
--- a/spec/controllers/api/v2/admin/accounts_controller_spec.rb
+++ b/spec/controllers/api/v2/admin/accounts_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V2::Admin::AccountsController, type: :controller do
   render_views
 
-  let(:role)   { 'moderator' }
+  let(:role)   { UserRole.find_by(name: 'Moderator') }
   let(:user)   { Fabricate(:user, role: role) }
   let(:scopes) { 'admin:read admin:write' }
   let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
@@ -22,7 +22,7 @@ RSpec.describe Api::V2::Admin::AccountsController, type: :controller do
   end
 
   shared_examples 'forbidden for wrong role' do |wrong_role|
-    let(:role) { wrong_role }
+    let(:role) { UserRole.find_by(name: wrong_role) }
 
     it 'returns http forbidden' do
       expect(response).to have_http_status(403)
@@ -46,7 +46,7 @@ RSpec.describe Api::V2::Admin::AccountsController, type: :controller do
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
-    it_behaves_like 'forbidden for wrong role', 'user'
+    it_behaves_like 'forbidden for wrong role', ''
 
     [
       [{ status: 'active', origin: 'local', permissions: 'staff' }, [:admin_account]],
diff --git a/spec/controllers/api/v2/filters_controller_spec.rb b/spec/controllers/api/v2/filters_controller_spec.rb
new file mode 100644
index 000000000..cc0070d57
--- /dev/null
+++ b/spec/controllers/api/v2/filters_controller_spec.rb
@@ -0,0 +1,121 @@
+require 'rails_helper'
+
+RSpec.describe Api::V2::FiltersController, type: :controller do
+  render_views
+
+  let(:user)  { Fabricate(:user) }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
+
+  before do
+    allow(controller).to receive(:doorkeeper_token) { token }
+  end
+
+  describe 'GET #index' do
+    let(:scopes) { 'read:filters' }
+    let!(:filter) { Fabricate(:custom_filter, account: user.account) }
+
+    it 'returns http success' do
+      get :index
+      expect(response).to have_http_status(200)
+    end
+  end
+
+  describe 'POST #create' do
+    let(:scopes) { 'write:filters' }
+
+    before do
+      post :create, params: { title: 'magic', context: %w(home), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(200)
+    end
+
+    it 'returns a filter with keywords' do
+      json = body_as_json
+      expect(json[:title]).to eq 'magic'
+      expect(json[:filter_action]).to eq 'hide'
+      expect(json[:context]).to eq ['home']
+      expect(json[:keywords].map { |keyword| keyword.slice(:keyword, :whole_word) }).to eq [{ keyword: 'magic', whole_word: true }]
+    end
+
+    it 'creates a filter' do
+      filter = user.account.custom_filters.first
+      expect(filter).to_not be_nil
+      expect(filter.keywords.pluck(:keyword)).to eq ['magic']
+      expect(filter.context).to eq %w(home)
+      expect(filter.irreversible?).to be true
+      expect(filter.expires_at).to be_nil
+    end
+  end
+
+  describe 'GET #show' do
+    let(:scopes)  { 'read:filters' }
+    let(:filter)  { Fabricate(:custom_filter, account: user.account) }
+
+    it 'returns http success' do
+      get :show, params: { id: filter.id }
+      expect(response).to have_http_status(200)
+    end
+  end
+
+  describe 'PUT #update' do
+    let(:scopes)   { 'write:filters' }
+    let!(:filter)  { Fabricate(:custom_filter, account: user.account) }
+    let!(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) }
+
+    context 'updating filter parameters' do
+      before do
+        put :update, params: { id: filter.id, title: 'updated', context: %w(home public) }
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(200)
+      end
+
+      it 'updates the filter title' do
+        expect(filter.reload.title).to eq 'updated'
+      end
+
+      it 'updates the filter context' do
+        expect(filter.reload.context).to eq %w(home public)
+      end
+    end
+
+    context 'updating keywords in bulk' do
+      before do
+        allow(redis).to receive_messages(publish: nil)
+        put :update, params: { id: filter.id, keywords_attributes: [{ id: keyword.id, keyword: 'updated' }] }
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(200)
+      end
+
+      it 'updates the keyword' do
+        expect(keyword.reload.keyword).to eq 'updated'
+      end
+
+      it 'sends exactly one filters_changed event' do
+        expect(redis).to have_received(:publish).with("timeline:#{user.account.id}", Oj.dump(event: :filters_changed)).once
+      end
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    let(:scopes)  { 'write:filters' }
+    let(:filter)  { Fabricate(:custom_filter, account: user.account) }
+
+    before do
+      delete :destroy, params: { id: filter.id }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(200)
+    end
+
+    it 'removes the filter' do
+      expect { filter.reload }.to raise_error ActiveRecord::RecordNotFound
+    end
+  end
+end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 851e58d60..2af12376d 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -187,70 +187,6 @@ describe ApplicationController, type: :controller do
     end
   end
 
-  describe 'require_admin!' do
-    controller do
-      before_action :require_admin!
-
-      def success
-        head 200
-      end
-    end
-
-    before do
-      routes.draw { get 'success' => 'anonymous#success' }
-    end
-
-    it 'returns a 403 if current user is not admin' do
-      sign_in(Fabricate(:user, admin: false))
-      get 'success'
-      expect(response).to have_http_status(403)
-    end
-
-    it 'returns a 403 if current user is only a moderator' do
-      sign_in(Fabricate(:user, moderator: true))
-      get 'success'
-      expect(response).to have_http_status(403)
-    end
-
-    it 'does nothing if current user is admin' do
-      sign_in(Fabricate(:user, admin: true))
-      get 'success'
-      expect(response).to have_http_status(200)
-    end
-  end
-
-  describe 'require_staff!' do
-    controller do
-      before_action :require_staff!
-
-      def success
-        head 200
-      end
-    end
-
-    before do
-      routes.draw { get 'success' => 'anonymous#success' }
-    end
-
-    it 'returns a 403 if current user is not admin or moderator' do
-      sign_in(Fabricate(:user, admin: false, moderator: false))
-      get 'success'
-      expect(response).to have_http_status(403)
-    end
-
-    it 'does nothing if current user is moderator' do
-      sign_in(Fabricate(:user, moderator: true))
-      get 'success'
-      expect(response).to have_http_status(200)
-    end
-
-    it 'does nothing if current user is admin' do
-      sign_in(Fabricate(:user, admin: true))
-      get 'success'
-      expect(response).to have_http_status(200)
-    end
-  end
-
   describe 'forbidden' do
     controller do
       def route_forbidden
diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb
index 1b8fd0b7b..d3db7aa1a 100644
--- a/spec/controllers/auth/sessions_controller_spec.rb
+++ b/spec/controllers/auth/sessions_controller_spec.rb
@@ -119,6 +119,32 @@ RSpec.describe Auth::SessionsController, type: :controller do
         end
       end
 
+      context 'using a valid password on a previously-used account with a new IP address' do
+        let(:previous_ip) { '1.2.3.4' }
+        let(:current_ip)  { '4.3.2.1' }
+
+        let!(:previous_login) { Fabricate(:login_activity, user: user, ip: previous_ip) }
+
+        before do
+          allow_any_instance_of(ActionDispatch::Request).to receive(:remote_ip).and_return(current_ip)
+          allow(UserMailer).to receive(:suspicious_sign_in).and_return(double('email', 'deliver_later!': nil))
+          user.update(current_sign_in_at: 1.month.ago)
+          post :create, params: { user: { email: user.email, password: user.password } }
+        end
+
+        it 'redirects to home' do
+          expect(response).to redirect_to(root_path)
+        end
+
+        it 'logs the user in' do
+          expect(controller.current_user).to eq user
+        end
+
+        it 'sends a suspicious sign-in mail' do
+          expect(UserMailer).to have_received(:suspicious_sign_in).with(user, current_ip, anything, anything)
+        end
+      end
+
       context 'using email with uppercase letters' do
         before do
           post :create, params: { user: { email: user.email.upcase, password: user.password } }
diff --git a/spec/controllers/disputes/appeals_controller_spec.rb b/spec/controllers/disputes/appeals_controller_spec.rb
index faa571fc9..90f222f49 100644
--- a/spec/controllers/disputes/appeals_controller_spec.rb
+++ b/spec/controllers/disputes/appeals_controller_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe Disputes::AppealsController, type: :controller do
 
   before { sign_in current_user, scope: :user }
 
-  let!(:admin) { Fabricate(:user, admin: true) }
+  let!(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
 
   describe '#create' do
     let(:current_user) { Fabricate(:user) }
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
index 76e617e6b..23b98fb12 100644
--- a/spec/controllers/invites_controller_spec.rb
+++ b/spec/controllers/invites_controller_spec.rb
@@ -7,30 +7,30 @@ describe InvitesController do
     sign_in user
   end
 
-  around do |example|
-    min_invite_role = Setting.min_invite_role
-    example.run
-    Setting.min_invite_role = min_invite_role
-  end
-
   describe 'GET #index' do
     subject { get :index }
 
-    let(:user) { Fabricate(:user, moderator: false, admin: false) }
+    let(:user) { Fabricate(:user) }
     let!(:invite) { Fabricate(:invite, user: user) }
 
-    context 'when user is a staff' do
+    context 'when everyone can invite' do
+      before do
+        UserRole.everyone.update(permissions: UserRole.everyone.permissions | UserRole::FLAGS[:invite_users])
+      end
+
       it 'renders index page' do
-        Setting.min_invite_role = 'user'
         expect(subject).to render_template :index
         expect(assigns(:invites)).to include invite
         expect(assigns(:invites).count).to eq 1
       end
     end
 
-    context 'when user is not a staff' do
+    context 'when not everyone can invite' do
+      before do
+        UserRole.everyone.update(permissions: UserRole.everyone.permissions & ~UserRole::FLAGS[:invite_users])
+      end
+
       it 'returns 403' do
-        Setting.min_invite_role = 'modelator'
         expect(subject).to have_http_status 403
       end
     end
@@ -39,8 +39,12 @@ describe InvitesController do
   describe 'POST #create' do
     subject { post :create, params: { invite: { max_uses: '10', expires_in: 1800 } } }
 
-    context 'when user is an admin' do
-      let(:user) { Fabricate(:user, moderator: false, admin: true) }
+    context 'when everyone can invite' do
+      let(:user) { Fabricate(:user) }
+
+      before do
+        UserRole.everyone.update(permissions: UserRole.everyone.permissions | UserRole::FLAGS[:invite_users])
+      end
 
       it 'succeeds to create a invite' do
         expect { subject }.to change { Invite.count }.by(1)
@@ -49,8 +53,12 @@ describe InvitesController do
       end
     end
 
-    context 'when user is not an admin' do
-      let(:user) { Fabricate(:user, moderator: true, admin: false) }
+    context 'when not everyone can invite' do
+      let(:user) { Fabricate(:user) }
+
+      before do
+        UserRole.everyone.update(permissions: UserRole.everyone.permissions & ~UserRole::FLAGS[:invite_users])
+      end
 
       it 'returns 403' do
         expect(subject).to have_http_status 403
@@ -61,8 +69,8 @@ describe InvitesController do
   describe 'DELETE #create' do
     subject { delete :destroy, params: { id: invite.id } }
 
+    let(:user) { Fabricate(:user) }
     let!(:invite) { Fabricate(:invite, user: user, expires_at: nil) }
-    let(:user) { Fabricate(:user, moderator: false, admin: true) }
 
     it 'expires invite' do
       expect(subject).to redirect_to invites_path
diff --git a/spec/fabricators/custom_filter_keyword_fabricator.rb b/spec/fabricators/custom_filter_keyword_fabricator.rb
new file mode 100644
index 000000000..0f101dcd1
--- /dev/null
+++ b/spec/fabricators/custom_filter_keyword_fabricator.rb
@@ -0,0 +1,4 @@
+Fabricator(:custom_filter_keyword) do
+  custom_filter
+  keyword       'discourse'
+end
diff --git a/spec/fabricators/login_activity_fabricator.rb b/spec/fabricators/login_activity_fabricator.rb
index 931d3082c..686fd6483 100644
--- a/spec/fabricators/login_activity_fabricator.rb
+++ b/spec/fabricators/login_activity_fabricator.rb
@@ -1,8 +1,8 @@
 Fabricator(:login_activity) do
   user
-  strategy       'password'
-  success        true
-  failure_reason nil
-  ip             { Faker::Internet.ip_v4_address }
-  user_agent     { Faker::Internet.user_agent }
+  authentication_method 'password'
+  success               true
+  failure_reason        nil
+  ip                    { Faker::Internet.ip_v4_address }
+  user_agent            { Faker::Internet.user_agent }
 end
diff --git a/spec/fabricators/tag_follow_fabricator.rb b/spec/fabricators/tag_follow_fabricator.rb
new file mode 100644
index 000000000..a2cccb07a
--- /dev/null
+++ b/spec/fabricators/tag_follow_fabricator.rb
@@ -0,0 +1,4 @@
+Fabricator(:tag_follow) do
+  tag
+  account
+end
diff --git a/spec/fabricators/user_role_fabricator.rb b/spec/fabricators/user_role_fabricator.rb
new file mode 100644
index 000000000..28f76c8c4
--- /dev/null
+++ b/spec/fabricators/user_role_fabricator.rb
@@ -0,0 +1,5 @@
+Fabricator(:user_role) do
+  name        "MyString"
+  color       "MyString"
+  permissions ""
+end
\ No newline at end of file
diff --git a/spec/lib/activitypub/activity/flag_spec.rb b/spec/lib/activitypub/activity/flag_spec.rb
index ec7359f2f..2f2d13876 100644
--- a/spec/lib/activitypub/activity/flag_spec.rb
+++ b/spec/lib/activitypub/activity/flag_spec.rb
@@ -1,7 +1,7 @@
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Flag do
-  let(:sender)  { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') }
+  let(:sender)  { Fabricate(:account, username: 'example.com', domain: 'example.com', uri: 'http://example.com/actor') }
   let(:flagged) { Fabricate(:account) }
   let(:status)  { Fabricate(:status, account: flagged, uri: 'foobar') }
   let(:flag_id) { nil }
@@ -23,16 +23,88 @@ RSpec.describe ActivityPub::Activity::Flag do
   describe '#perform' do
     subject { described_class.new(json, sender) }
 
-    before do
-      subject.perform
+    context 'when the reported status is public' do
+      before do
+        subject.perform
+      end
+
+      it 'creates a report' do
+        report = Report.find_by(account: sender, target_account: flagged)
+
+        expect(report).to_not be_nil
+        expect(report.comment).to eq 'Boo!!'
+        expect(report.status_ids).to eq [status.id]
+      end
     end
 
-    it 'creates a report' do
-      report = Report.find_by(account: sender, target_account: flagged)
+    context 'when the reported status is private and should not be visible to the remote server' do
+      let(:status) { Fabricate(:status, account: flagged, uri: 'foobar', visibility: :private) }
 
-      expect(report).to_not be_nil
-      expect(report.comment).to eq 'Boo!!'
-      expect(report.status_ids).to eq [status.id]
+      before do
+        subject.perform
+      end
+
+      it 'creates a report with no attached status' do
+        report = Report.find_by(account: sender, target_account: flagged)
+
+        expect(report).to_not be_nil
+        expect(report.comment).to eq 'Boo!!'
+        expect(report.status_ids).to eq []
+      end
+    end
+
+    context 'when the reported status is private and the author has a follower on the remote instance' do
+      let(:status) { Fabricate(:status, account: flagged, uri: 'foobar', visibility: :private) }
+      let(:follower) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/users/account') }
+
+      before do
+        follower.follow!(flagged)
+        subject.perform
+      end
+
+      it 'creates a report with the attached status' do
+        report = Report.find_by(account: sender, target_account: flagged)
+
+        expect(report).to_not be_nil
+        expect(report.comment).to eq 'Boo!!'
+        expect(report.status_ids).to eq [status.id]
+      end
+    end
+
+    context 'when the reported status is private and the author mentions someone else on the remote instance' do
+      let(:status) { Fabricate(:status, account: flagged, uri: 'foobar', visibility: :private) }
+      let(:mentioned) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/users/account') }
+
+      before do
+        status.mentions.create(account: mentioned)
+        subject.perform
+      end
+
+      it 'creates a report with the attached status' do
+        report = Report.find_by(account: sender, target_account: flagged)
+
+        expect(report).to_not be_nil
+        expect(report.comment).to eq 'Boo!!'
+        expect(report.status_ids).to eq [status.id]
+      end
+    end
+
+    context 'when the reported status is private and the author mentions someone else on the local instance' do
+      let(:status) { Fabricate(:status, account: flagged, uri: 'foobar', visibility: :private) }
+      let(:mentioned) { Fabricate(:account) }
+
+      before do
+        status.mentions.create(account: mentioned)
+        subject.perform
+      end
+
+      it 'creates a report with no attached status' do
+        report = Report.find_by(account: sender, target_account: flagged)
+
+        expect(report).to_not be_nil
+        expect(report.comment).to eq 'Boo!!'
+        expect(report.status_ids).to eq []
+      end
     end
   end
 
diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb
index 1b04916a1..4a43ae7e5 100644
--- a/spec/lib/feed_manager_spec.rb
+++ b/spec/lib/feed_manager_spec.rb
@@ -134,38 +134,6 @@ RSpec.describe FeedManager do
         reblog = Fabricate(:status, reblog: status, account: jeff)
         expect(FeedManager.instance.filter?(:home, reblog, alice)).to be true
       end
-
-      context 'for irreversibly muted phrases' do
-        it 'considers word boundaries when matching' do
-          alice.custom_filters.create!(phrase: 'bob', context: %w(home), irreversible: true)
-          alice.follow!(jeff)
-          status = Fabricate(:status, text: 'bobcats', account: jeff)
-          expect(FeedManager.instance.filter?(:home, status, alice)).to be_falsy
-        end
-
-        it 'returns true if phrase is contained' do
-          alice.custom_filters.create!(phrase: 'farts', context: %w(home public), irreversible: true)
-          alice.custom_filters.create!(phrase: 'pop tarts', context: %w(home), irreversible: true)
-          alice.follow!(jeff)
-          status = Fabricate(:status, text: 'i sure like POP TARts', account: jeff)
-          expect(FeedManager.instance.filter?(:home, status, alice)).to be true
-        end
-
-        it 'matches substrings if whole_word is false' do
-          alice.custom_filters.create!(phrase: 'take', context: %w(home), whole_word: false, irreversible: true)
-          alice.follow!(jeff)
-          status = Fabricate(:status, text: 'shiitake', account: jeff)
-          expect(FeedManager.instance.filter?(:home, status, alice)).to be true
-        end
-
-        it 'returns true if phrase is contained in a poll option' do
-          alice.custom_filters.create!(phrase: 'farts', context: %w(home public), irreversible: true)
-          alice.custom_filters.create!(phrase: 'pop tarts', context: %w(home), irreversible: true)
-          alice.follow!(jeff)
-          status = Fabricate(:status, text: 'what do you prefer', poll: Fabricate(:poll, options: %w(farts POP TARts)), account: jeff)
-          expect(FeedManager.instance.filter?(:home, status, alice)).to be true
-        end
-      end
     end
 
     context 'for mentions feed' do
diff --git a/spec/lib/hashtag_normalizer_spec.rb b/spec/lib/hashtag_normalizer_spec.rb
new file mode 100644
index 000000000..fbb9f37c0
--- /dev/null
+++ b/spec/lib/hashtag_normalizer_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe HashtagNormalizer do
+  subject { described_class.new }
+
+  describe '#normalize' do
+    it 'converts full-width Latin characters into basic Latin characters' do
+      expect(subject.normalize('Synthwave')).to eq 'synthwave'
+    end
+
+    it 'converts half-width Katakana into Kana characters' do
+      expect(subject.normalize('シーサイドライナー')).to eq 'シーサイドライナー'
+    end
+
+    it 'converts modified Latin characters into basic Latin characters' do
+      expect(subject.normalize('BLÅHAJ')).to eq 'blahaj'
+    end
+
+    it 'strips out invalid characters' do
+      expect(subject.normalize('#foo')).to eq 'foo'
+    end
+
+    it 'keeps valid characters' do
+      expect(subject.normalize('a·b')).to eq 'a·b'
+    end
+  end
+end
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index dc0ca3da3..467d41836 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -445,7 +445,7 @@ RSpec.describe Account, type: :model do
 
     it 'accepts arbitrary limits' do
       2.times.each { Fabricate(:account, display_name: "Display Name") }
-      results = Account.search_for("display", 1)
+      results = Account.search_for("display", limit: 1)
       expect(results.size).to eq 1
     end
 
@@ -473,7 +473,7 @@ RSpec.describe Account, type: :model do
         )
         account.follow!(match)
 
-        results = Account.advanced_search_for('A?l\i:c e', account, 10, true)
+        results = Account.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
         expect(results).to eq [match]
       end
 
@@ -485,7 +485,7 @@ RSpec.describe Account, type: :model do
           domain: 'example.com'
         )
 
-        results = Account.advanced_search_for('A?l\i:c e', account, 10, true)
+        results = Account.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
         expect(results).to eq []
       end
 
@@ -498,7 +498,7 @@ RSpec.describe Account, type: :model do
           suspended: true
         )
 
-        results = Account.advanced_search_for('username', account, 10, true)
+        results = Account.advanced_search_for('username', account, limit: 10, following: true)
         expect(results).to eq []
       end
 
@@ -511,7 +511,7 @@ RSpec.describe Account, type: :model do
 
         match.user.update(approved: false)
 
-        results = Account.advanced_search_for('username', account, 10, true)
+        results = Account.advanced_search_for('username', account, limit: 10, following: true)
         expect(results).to eq []
       end
 
@@ -524,7 +524,7 @@ RSpec.describe Account, type: :model do
 
         match.user.update(confirmed_at: nil)
 
-        results = Account.advanced_search_for('username', account, 10, true)
+        results = Account.advanced_search_for('username', account, limit: 10, following: true)
         expect(results).to eq []
       end
     end
@@ -588,7 +588,7 @@ RSpec.describe Account, type: :model do
 
     it 'accepts arbitrary limits' do
       2.times { Fabricate(:account, display_name: "Display Name") }
-      results = Account.advanced_search_for("display", account, 1)
+      results = Account.advanced_search_for("display", account, limit: 1)
       expect(results.size).to eq 1
     end
 
diff --git a/spec/models/admin/account_action_spec.rb b/spec/models/admin/account_action_spec.rb
index 809c7fc46..b6a052b76 100644
--- a/spec/models/admin/account_action_spec.rb
+++ b/spec/models/admin/account_action_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe Admin::AccountAction, type: :model do
 
   describe '#save!' do
     subject              { account_action.save! }
-    let(:account)        { Fabricate(:user, admin: true).account }
+    let(:account)        { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
     let(:target_account) { Fabricate(:account) }
     let(:type)           { 'disable' }
 
diff --git a/spec/models/custom_filter_keyword_spec.rb b/spec/models/custom_filter_keyword_spec.rb
new file mode 100644
index 000000000..e15b9dad5
--- /dev/null
+++ b/spec/models/custom_filter_keyword_spec.rb
@@ -0,0 +1,4 @@
+require 'rails_helper'
+
+RSpec.describe CustomFilterKeyword, type: :model do
+end
diff --git a/spec/models/tag_follow_spec.rb b/spec/models/tag_follow_spec.rb
new file mode 100644
index 000000000..50c04d2e4
--- /dev/null
+++ b/spec/models/tag_follow_spec.rb
@@ -0,0 +1,4 @@
+require 'rails_helper'
+
+RSpec.describe TagFollow, type: :model do
+end
diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb
index 3949dbce5..b16f99a79 100644
--- a/spec/models/tag_spec.rb
+++ b/spec/models/tag_spec.rb
@@ -91,7 +91,7 @@ RSpec.describe Tag, type: :model do
       upcase_string   = 'abcABCabcABCやゆよ'
       downcase_string = 'abcabcabcabcやゆよ';
 
-      tag = Fabricate(:tag, name: downcase_string)
+      tag = Fabricate(:tag, name: HashtagNormalizer.new.normalize(downcase_string))
       expect(Tag.find_normalized(upcase_string)).to eq tag
     end
   end
@@ -101,12 +101,12 @@ RSpec.describe Tag, type: :model do
       upcase_string   = 'abcABCabcABCやゆよ'
       downcase_string = 'abcabcabcabcやゆよ';
 
-      tag = Fabricate(:tag, name: downcase_string)
+      tag = Fabricate(:tag, name: HashtagNormalizer.new.normalize(downcase_string))
       expect(Tag.matches_name(upcase_string)).to eq [tag]
     end
 
     it 'uses the LIKE operator' do
-      expect(Tag.matches_name('100%abc').to_sql).to eq %q[SELECT "tags".* FROM "tags" WHERE LOWER("tags"."name") LIKE LOWER('100\\%abc%')]
+      expect(Tag.matches_name('100%abc').to_sql).to eq %q[SELECT "tags".* FROM "tags" WHERE LOWER("tags"."name") LIKE LOWER('100abc%')]
     end
   end
 
@@ -115,7 +115,7 @@ RSpec.describe Tag, type: :model do
       upcase_string   = 'abcABCabcABCやゆよ'
       downcase_string = 'abcabcabcabcやゆよ';
 
-      tag = Fabricate(:tag, name: downcase_string)
+      tag = Fabricate(:tag, name: HashtagNormalizer.new.normalize(downcase_string))
       expect(Tag.matching_name(upcase_string)).to eq [tag]
     end
   end
diff --git a/spec/models/user_role_spec.rb b/spec/models/user_role_spec.rb
new file mode 100644
index 000000000..28019593e
--- /dev/null
+++ b/spec/models/user_role_spec.rb
@@ -0,0 +1,189 @@
+require 'rails_helper'
+
+RSpec.describe UserRole, type: :model do
+  subject { described_class.create(name: 'Foo', position: 1) }
+
+  describe '#can?' do
+    context 'with a single flag' do
+      it 'returns true if any of them are present' do
+        subject.permissions = UserRole::FLAGS[:manage_reports]
+        expect(subject.can?(:manage_reports)).to be true
+      end
+
+      it 'returns false if it is not set' do
+        expect(subject.can?(:manage_reports)).to be false
+      end
+    end
+
+    context 'with multiple flags' do
+      it 'returns true if any of them are present' do
+        subject.permissions = UserRole::FLAGS[:manage_users]
+        expect(subject.can?(:manage_reports, :manage_users)).to be true
+      end
+
+      it 'returns false if none of them are present' do
+        expect(subject.can?(:manage_reports, :manage_users)).to be false
+      end
+    end
+
+    context 'with an unknown flag' do
+      it 'raises an error' do
+        expect { subject.can?(:foo) }.to raise_error ArgumentError
+      end
+    end
+  end
+
+  describe '#overrides?' do
+    it 'returns true if other role has lower position' do
+      expect(subject.overrides?(described_class.new(position: subject.position - 1))).to be true
+    end
+
+    it 'returns true if other role is nil' do
+      expect(subject.overrides?(nil)).to be true
+    end
+
+    it 'returns false if other role has higher position' do
+      expect(subject.overrides?(described_class.new(position: subject.position + 1))).to be false
+    end
+  end
+
+  describe '#permissions_as_keys' do
+    before do
+      subject.permissions = UserRole::FLAGS[:invite_users] | UserRole::FLAGS[:view_dashboard] | UserRole::FLAGS[:manage_reports]
+    end
+
+    it 'returns an array' do
+      expect(subject.permissions_as_keys).to match_array %w(invite_users view_dashboard manage_reports)
+    end
+  end
+
+  describe '#permissions_as_keys=' do
+    let(:input) { }
+
+    before do
+      subject.permissions_as_keys = input
+    end
+
+    context 'with a single value' do
+      let(:input) { %w(manage_users) }
+
+      it 'sets permission flags' do
+        expect(subject.permissions).to eq UserRole::FLAGS[:manage_users]
+      end
+    end
+
+    context 'with multiple values' do
+      let(:input) { %w(manage_users manage_reports) }
+
+      it 'sets permission flags' do
+        expect(subject.permissions).to eq UserRole::FLAGS[:manage_users] | UserRole::FLAGS[:manage_reports]
+      end
+    end
+
+    context 'with an unknown value' do
+      let(:input) { %w(foo) }
+
+      it 'does not set permission flags' do
+        expect(subject.permissions).to eq UserRole::Flags::NONE
+      end
+    end
+  end
+
+  describe '#computed_permissions' do
+    context 'when the role is nobody' do
+      let(:subject) { described_class.nobody }
+
+      it 'returns none' do
+        expect(subject.computed_permissions).to eq UserRole::Flags::NONE
+      end
+    end
+
+    context 'when the role is everyone' do
+      let(:subject) { described_class.everyone }
+
+      it 'returns permissions' do
+        expect(subject.computed_permissions).to eq subject.permissions
+      end
+    end
+
+    context 'when role has the administrator flag' do
+      before do
+        subject.permissions = UserRole::FLAGS[:administrator]
+      end
+
+      it 'returns all permissions' do
+        expect(subject.computed_permissions).to eq UserRole::Flags::ALL
+      end
+    end
+
+    context do
+      it 'returns permissions combined with the everyone role' do
+        expect(subject.computed_permissions).to eq described_class.everyone.permissions
+      end
+    end
+  end
+
+  describe '.everyone' do
+    subject { described_class.everyone }
+
+    it 'returns a role' do
+      expect(subject).to be_kind_of(described_class)
+    end
+
+    it 'is identified as the everyone role' do
+      expect(subject.everyone?).to be true
+    end
+
+    it 'has default permissions' do
+      expect(subject.permissions).to eq UserRole::FLAGS[:invite_users]
+    end
+
+    it 'has negative position' do
+      expect(subject.position).to eq -1
+    end
+  end
+
+  describe '.nobody' do
+    subject { described_class.nobody }
+
+    it 'returns a role' do
+      expect(subject).to be_kind_of(described_class)
+    end
+
+    it 'is identified as the nobody role' do
+      expect(subject.nobody?).to be true
+    end
+
+    it 'has no permissions' do
+      expect(subject.permissions).to eq UserRole::Flags::NONE
+    end
+
+    it 'has negative position' do
+      expect(subject.position).to eq -1
+    end
+  end
+
+  describe '#everyone?' do
+    it 'returns true when id is -99' do
+      subject.id = -99
+      expect(subject.everyone?).to be true
+    end
+
+    it 'returns false when id is not -99' do
+      subject.id = 123
+      expect(subject.everyone?).to be false
+    end
+  end
+
+  describe '#nobody?' do
+    it 'returns true when id is nil' do
+      subject.id = nil
+      expect(subject.nobody?).to be true
+    end
+
+    it 'returns false when id is not nil' do
+      subject.id = 123
+      expect(subject.nobody?).to be false
+    end
+  end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 1645ab59e..a7da31e60 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -56,14 +56,6 @@ RSpec.describe User, type: :model do
       end
     end
 
-    describe 'admins' do
-      it 'returns an array of users who are admin' do
-        user_1 = Fabricate(:user, admin: false)
-        user_2 = Fabricate(:user, admin: true)
-        expect(User.admins).to match_array([user_2])
-      end
-    end
-
     describe 'confirmed' do
       it 'returns an array of users who are confirmed' do
         user_1 = Fabricate(:user, confirmed_at: nil)
@@ -289,49 +281,6 @@ RSpec.describe User, type: :model do
     end
   end
 
-  describe '#role' do
-    it 'returns admin for admin' do
-      user = User.new(admin: true)
-      expect(user.role).to eq 'admin'
-    end
-
-    it 'returns moderator for moderator' do
-      user = User.new(moderator: true)
-      expect(user.role).to eq 'moderator'
-    end
-
-    it 'returns user otherwise' do
-      user = User.new
-      expect(user.role).to eq 'user'
-    end
-  end
-
-  describe '#role?' do
-    it 'returns false when invalid role requested' do
-      user = User.new(admin: true)
-      expect(user.role?('disabled')).to be false
-    end
-
-    it 'returns true when exact role match' do
-      user  = User.new
-      mod   = User.new(moderator: true)
-      admin = User.new(admin: true)
-
-      expect(user.role?('user')).to be true
-      expect(mod.role?('moderator')).to be true
-      expect(admin.role?('admin')).to be true
-    end
-
-    it 'returns true when role higher than needed' do
-      mod   = User.new(moderator: true)
-      admin = User.new(admin: true)
-
-      expect(mod.role?('user')).to be true
-      expect(admin.role?('user')).to be true
-      expect(admin.role?('moderator')).to be true
-    end
-  end
-
   describe '#disable!' do
     subject(:user) { Fabricate(:user, disabled: false, current_sign_in_at: current_sign_in_at, last_sign_in_at: nil) }
     let(:current_sign_in_at) { Time.zone.now }
@@ -420,110 +369,6 @@ RSpec.describe User, type: :model do
     end
   end
 
-  describe '#promote!' do
-    subject(:user) { Fabricate(:user, admin: is_admin, moderator: is_moderator) }
-
-    before do
-      user.promote!
-    end
-
-    context 'when user is an admin' do
-      let(:is_admin) { true }
-
-      context 'when user is a moderator' do
-        let(:is_moderator) { true }
-
-        it 'changes moderator filed false' do
-          expect(user).to be_admin
-          expect(user).not_to be_moderator
-        end
-      end
-
-      context 'when user is not a moderator' do
-        let(:is_moderator) { false }
-
-        it 'does not change status' do
-          expect(user).to be_admin
-          expect(user).not_to be_moderator
-        end
-      end
-    end
-
-    context 'when user is not admin' do
-      let(:is_admin) { false }
-
-      context 'when user is a moderator' do
-        let(:is_moderator) { true }
-
-        it 'changes user into an admin' do
-          expect(user).to be_admin
-          expect(user).not_to be_moderator
-        end
-      end
-
-      context 'when user is not a moderator' do
-        let(:is_moderator) { false }
-
-        it 'changes user into a moderator' do
-          expect(user).not_to be_admin
-          expect(user).to be_moderator
-        end
-      end
-    end
-  end
-
-  describe '#demote!' do
-    subject(:user) { Fabricate(:user, admin: admin, moderator: moderator) }
-
-    before do
-      user.demote!
-    end
-
-    context 'when user is an admin' do
-      let(:admin) { true }
-
-      context 'when user is a moderator' do
-        let(:moderator) { true }
-
-        it 'changes user into a moderator' do
-          expect(user).not_to be_admin
-          expect(user).to be_moderator
-        end
-      end
-
-      context 'when user is not a moderator' do
-        let(:moderator) { false }
-
-        it 'changes user into a moderator' do
-          expect(user).not_to be_admin
-          expect(user).to be_moderator
-        end
-      end
-    end
-
-    context 'when user is not an admin' do
-      let(:admin) { false }
-
-      context 'when user is a moderator' do
-        let(:moderator) { true }
-
-        it 'changes user into a plain user' do
-          expect(user).not_to be_admin
-          expect(user).not_to be_moderator
-        end
-      end
-
-      context 'when user is not a moderator' do
-        let(:moderator) { false }
-
-        it 'does not change any fields' do
-          expect(user).not_to be_admin
-          expect(user).not_to be_moderator
-        end
-      end
-    end
-  end
-
   describe '#active_for_authentication?' do
     subject { user.active_for_authentication? }
     let(:user) { Fabricate(:user, disabled: disabled, confirmed_at: confirmed_at) }
@@ -560,4 +405,8 @@ RSpec.describe User, type: :model do
       end
     end
   end
+
+  describe '.those_who_can' do
+    pending
+  end
 end
diff --git a/spec/policies/account_moderation_note_policy_spec.rb b/spec/policies/account_moderation_note_policy_spec.rb
index 39ec2008a..846747346 100644
--- a/spec/policies/account_moderation_note_policy_spec.rb
+++ b/spec/policies/account_moderation_note_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
 
 RSpec.describe AccountModerationNotePolicy do
   let(:subject) { described_class }
-  let(:admin)   { Fabricate(:user, admin: true).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :create? do
@@ -31,7 +31,7 @@ RSpec.describe AccountModerationNotePolicy do
 
     context 'admin' do
       it 'grants to destroy' do
-        expect(subject).to permit(admin, AccountModerationNotePolicy)
+        expect(subject).to permit(admin, account_moderation_note)
       end
     end
 
diff --git a/spec/policies/account_policy_spec.rb b/spec/policies/account_policy_spec.rb
index b55eb65a7..0f23fd97e 100644
--- a/spec/policies/account_policy_spec.rb
+++ b/spec/policies/account_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
 
 RSpec.describe AccountPolicy do
   let(:subject) { described_class }
-  let(:admin)   { Fabricate(:user, admin: true).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
   let(:alice)   { Fabricate(:account) }
 
@@ -55,7 +55,7 @@ RSpec.describe AccountPolicy do
     end
   end
 
-  permissions :redownload?, :subscribe?, :unsubscribe? do
+  permissions :redownload? do
     context 'admin' do
       it 'permits' do
         expect(subject).to permit(admin)
@@ -70,7 +70,7 @@ RSpec.describe AccountPolicy do
   end
 
   permissions :suspend?, :silence? do
-    let(:staff) { Fabricate(:user, admin: true).account }
+    let(:staff) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
 
     context 'staff' do
       context 'record is staff' do
@@ -94,7 +94,7 @@ RSpec.describe AccountPolicy do
   end
 
   permissions :memorialize? do
-    let(:other_admin) { Fabricate(:user, admin: true).account }
+    let(:other_admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
 
     context 'admin' do
       context 'record is admin' do
diff --git a/spec/policies/custom_emoji_policy_spec.rb b/spec/policies/custom_emoji_policy_spec.rb
index e4f1af3c1..6a6ef6694 100644
--- a/spec/policies/custom_emoji_policy_spec.rb
+++ b/spec/policies/custom_emoji_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
 
 RSpec.describe CustomEmojiPolicy do
   let(:subject) { described_class }
-  let(:admin)   { Fabricate(:user, admin: true).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :enable?, :disable? do
diff --git a/spec/policies/domain_block_policy_spec.rb b/spec/policies/domain_block_policy_spec.rb
index b24ed9e3a..01b97e823 100644
--- a/spec/policies/domain_block_policy_spec.rb
+++ b/spec/policies/domain_block_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
 
 RSpec.describe DomainBlockPolicy do
   let(:subject) { described_class }
-  let(:admin)   { Fabricate(:user, admin: true).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :show?, :create?, :destroy? do
diff --git a/spec/policies/email_domain_block_policy_spec.rb b/spec/policies/email_domain_block_policy_spec.rb
index 1ff55af8e..913075c3d 100644
--- a/spec/policies/email_domain_block_policy_spec.rb
+++ b/spec/policies/email_domain_block_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
 
 RSpec.describe EmailDomainBlockPolicy do
   let(:subject) { described_class }
-  let(:admin)   { Fabricate(:user, admin: true).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :create?, :destroy? do
diff --git a/spec/policies/instance_policy_spec.rb b/spec/policies/instance_policy_spec.rb
index 71ef1fe50..f6f51af06 100644
--- a/spec/policies/instance_policy_spec.rb
+++ b/spec/policies/instance_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
 
 RSpec.describe InstancePolicy do
   let(:subject) { described_class }
-  let(:admin)   { Fabricate(:user, admin: true).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :show?, :destroy? do
diff --git a/spec/policies/invite_policy_spec.rb b/spec/policies/invite_policy_spec.rb
index 122137804..01660322f 100644
--- a/spec/policies/invite_policy_spec.rb
+++ b/spec/policies/invite_policy_spec.rb
@@ -5,8 +5,8 @@ require 'pundit/rspec'
 
 RSpec.describe InvitePolicy do
   let(:subject) { described_class }
-  let(:admin)   { Fabricate(:user, admin: true).account }
-  let(:john)    { Fabricate(:account) }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
+  let(:john)    { Fabricate(:user).account }
 
   permissions :index? do
     context 'staff?' do
@@ -17,16 +17,22 @@ RSpec.describe InvitePolicy do
   end
 
   permissions :create? do
-    context 'min_required_role?' do
+    context 'has privilege' do
+      before do
+        UserRole.everyone.update(permissions: UserRole::FLAGS[:invite_users])
+      end
+
       it 'permits' do
-        allow_any_instance_of(described_class).to receive(:min_required_role?) { true }
         expect(subject).to permit(john, Invite)
       end
     end
 
-    context 'not min_required_role?' do
+    context 'does not have privilege' do
+      before do
+        UserRole.everyone.update(permissions: UserRole::Flags::NONE)
+      end
+
       it 'denies' do
-        allow_any_instance_of(described_class).to receive(:min_required_role?) { false }
         expect(subject).to_not permit(john, Invite)
       end
     end
@@ -54,39 +60,15 @@ RSpec.describe InvitePolicy do
     end
 
     context 'not owner?' do
-      context 'Setting.min_invite_role == "admin"' do
-        before do
-          Setting.min_invite_role = 'admin'
-        end
-
-        context 'admin?' do
-          it 'permits' do
-            expect(subject).to permit(admin, Fabricate(:invite))
-          end
-        end
-
-        context 'not admin?' do
-          it 'denies' do
-            expect(subject).to_not permit(john, Fabricate(:invite))
-          end
+      context 'admin?' do
+        it 'permits' do
+          expect(subject).to permit(admin, Fabricate(:invite))
         end
       end
 
-      context 'Setting.min_invite_role != "admin"' do
-        before do
-          Setting.min_invite_role = 'else'
-        end
-
-        context 'staff?' do
-          it 'permits' do
-            expect(subject).to permit(admin, Fabricate(:invite))
-          end
-        end
-
-        context 'not staff?' do
-          it 'denies' do
-            expect(subject).to_not permit(john, Fabricate(:invite))
-          end
+      context 'not admin?' do
+        it 'denies' do
+          expect(subject).to_not permit(john, Fabricate(:invite))
         end
       end
     end
diff --git a/spec/policies/relay_policy_spec.rb b/spec/policies/relay_policy_spec.rb
index 139d945dc..2c50ba1e9 100644
--- a/spec/policies/relay_policy_spec.rb
+++ b/spec/policies/relay_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
 
 RSpec.describe RelayPolicy do
   let(:subject) { described_class }
-  let(:admin)   { Fabricate(:user, admin: true).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :update? do
diff --git a/spec/policies/report_note_policy_spec.rb b/spec/policies/report_note_policy_spec.rb
index c34f99b71..99f5ffb8e 100644
--- a/spec/policies/report_note_policy_spec.rb
+++ b/spec/policies/report_note_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
 
 RSpec.describe ReportNotePolicy do
   let(:subject) { described_class }
-  let(:admin)   { Fabricate(:user, admin: true).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :create? do
@@ -25,7 +25,8 @@ RSpec.describe ReportNotePolicy do
   permissions :destroy? do
     context 'admin?' do
       it 'permit' do
-        expect(subject).to permit(admin, ReportNote)
+        report_note = Fabricate(:report_note, account: john)
+        expect(subject).to permit(admin, report_note)
       end
     end
 
diff --git a/spec/policies/report_policy_spec.rb b/spec/policies/report_policy_spec.rb
index 84c366d7f..8b005d8dd 100644
--- a/spec/policies/report_policy_spec.rb
+++ b/spec/policies/report_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
 
 RSpec.describe ReportPolicy do
   let(:subject) { described_class }
-  let(:admin)   { Fabricate(:user, admin: true).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :update?, :index?, :show? do
diff --git a/spec/policies/settings_policy_spec.rb b/spec/policies/settings_policy_spec.rb
index 3fa183c50..e16ee51a4 100644
--- a/spec/policies/settings_policy_spec.rb
+++ b/spec/policies/settings_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
 
 RSpec.describe SettingsPolicy do
   let(:subject) { described_class }
-  let(:admin)   { Fabricate(:user, admin: true).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :update?, :show? do
diff --git a/spec/policies/status_policy_spec.rb b/spec/policies/status_policy_spec.rb
index 865c693aa..c2dcc50df 100644
--- a/spec/policies/status_policy_spec.rb
+++ b/spec/policies/status_policy_spec.rb
@@ -6,7 +6,7 @@ require 'pundit/rspec'
 RSpec.describe StatusPolicy, type: :model do
   subject { described_class }
 
-  let(:admin) { Fabricate(:user, admin: true) }
+  let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
   let(:alice) { Fabricate(:account, username: 'alice') }
   let(:bob) { Fabricate(:account, username: 'bob') }
   let(:status) { Fabricate(:status, account: alice) }
diff --git a/spec/policies/tag_policy_spec.rb b/spec/policies/tag_policy_spec.rb
index 256e6786a..9be7140fc 100644
--- a/spec/policies/tag_policy_spec.rb
+++ b/spec/policies/tag_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
 
 RSpec.describe TagPolicy do
   let(:subject) { described_class }
-  let(:admin)   { Fabricate(:user, admin: true).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :index?, :show?, :update? do
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
index 731c041d1..ff0916674 100644
--- a/spec/policies/user_policy_spec.rb
+++ b/spec/policies/user_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
 
 RSpec.describe UserPolicy do
   let(:subject) { described_class }
-  let(:admin)   { Fabricate(:user, admin: true).account }
+  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
   let(:john)    { Fabricate(:account) }
 
   permissions :reset_password?, :change_email? do
@@ -111,57 +111,4 @@ RSpec.describe UserPolicy do
       end
     end
   end
-
-  permissions :promote? do
-    context 'admin?' do
-      context 'promotable?' do
-        it 'permits' do
-          expect(subject).to permit(admin, john.user)
-        end
-      end
-
-      context '!promotable?' do
-        it 'denies' do
-          expect(subject).to_not permit(admin, admin.user)
-        end
-      end
-    end
-
-    context '!admin?' do
-      it 'denies' do
-        expect(subject).to_not permit(john, User)
-      end
-    end
-  end
-
-  permissions :demote? do
-    context 'admin?' do
-      context '!record.admin?' do
-        context 'demoteable?' do
-          it 'permits' do
-            john.user.update(moderator: true)
-            expect(subject).to permit(admin, john.user)
-          end
-        end
-
-        context '!demoteable?' do
-          it 'denies' do
-            expect(subject).to_not permit(admin, john.user)
-          end
-        end
-      end
-
-      context 'record.admin?' do
-        it 'denies' do
-          expect(subject).to_not permit(admin, admin.user)
-        end
-      end
-    end
-
-    context '!admin?' do
-      it 'denies' do
-        expect(subject).to_not permit(john, User)
-      end
-    end
-  end
 end
diff --git a/spec/presenters/status_relationships_presenter_spec.rb b/spec/presenters/status_relationships_presenter_spec.rb
index 03296bd17..5cd4929a6 100644
--- a/spec/presenters/status_relationships_presenter_spec.rb
+++ b/spec/presenters/status_relationships_presenter_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe StatusRelationshipsPresenter do
   describe '.initialize' do
     before do
-      allow(Status).to receive(:reblogs_map).with(status_ids, current_account_id).and_return(default_map)
+      allow(Status).to receive(:reblogs_map).with(match_array(status_ids), current_account_id).and_return(default_map)
       allow(Status).to receive(:favourites_map).with(status_ids, current_account_id).and_return(default_map)
       allow(Status).to receive(:bookmarks_map).with(status_ids, current_account_id).and_return(default_map)
       allow(Status).to receive(:mutes_map).with(anything, current_account_id).and_return(default_map)
@@ -15,7 +15,7 @@ RSpec.describe StatusRelationshipsPresenter do
     let(:presenter)          { StatusRelationshipsPresenter.new(statuses, current_account_id, **options) }
     let(:current_account_id) { Fabricate(:account).id }
     let(:statuses)           { [Fabricate(:status)] }
-    let(:status_ids)         { statuses.map(&:id) }
+    let(:status_ids)         { statuses.map(&:id) + statuses.map(&:reblog_of_id).compact }
     let(:default_map)        { { 1 => true } }
 
     context 'options are not set' do
@@ -69,5 +69,30 @@ RSpec.describe StatusRelationshipsPresenter do
         expect(presenter.pins_map).to eq default_map.merge(options[:pins_map])
       end
     end
+
+    context 'when post includes filtered terms' do
+      let(:statuses) { [Fabricate(:status, text: 'this toot is about that banned word'), Fabricate(:status, reblog: Fabricate(:status, text: 'this toot is about an irrelevant word'))] }
+      let(:options) { {} }
+
+      before do
+        Account.find(current_account_id).custom_filters.create!(phrase: 'filter1', context: %w(home), action: :hide, keywords_attributes: [{ keyword: 'banned' }, { keyword: 'irrelevant' }])
+      end
+
+      it 'sets @filters_map to filter top-level status' do
+        matched_filters = presenter.filters_map[statuses[0].id]
+        expect(matched_filters.size).to eq 1
+
+        expect(matched_filters[0].filter.title).to eq 'filter1'
+        expect(matched_filters[0].keyword_matches).to eq ['banned']
+      end
+
+      it 'sets @filters_map to filter reblogged status' do
+        matched_filters = presenter.filters_map[statuses[1].reblog_of_id]
+        expect(matched_filters.size).to eq 1
+
+        expect(matched_filters[0].filter.title).to eq 'filter1'
+        expect(matched_filters[0].keyword_matches).to eq ['irrelevant']
+      end
+    end
   end
 end
diff --git a/spec/services/report_service_spec.rb b/spec/services/report_service_spec.rb
index 7e6a113e0..ea68b3344 100644
--- a/spec/services/report_service_spec.rb
+++ b/spec/services/report_service_spec.rb
@@ -28,6 +28,31 @@ RSpec.describe ReportService, type: :service do
     end
   end
 
+  context 'when the reported status is a DM' do
+    let(:target_account) { Fabricate(:account) }
+    let(:status) { Fabricate(:status, account: target_account, visibility: :direct) }
+
+    subject do
+      -> { described_class.new.call(source_account, target_account, status_ids: [status.id]) }
+    end
+
+    context 'when it is addressed to the reporter' do
+      before do
+        status.mentions.create(account: source_account)
+      end
+
+      it 'creates a report' do
+        is_expected.to change { target_account.targeted_reports.count }.from(0).to(1)
+      end
+    end
+
+    context 'when it is not addressed to the reporter' do
+      it 'errors out' do
+        is_expected.to raise_error
+      end
+    end
+  end
+
   context 'when other reports already exist for the same target' do
     let!(:target_account) { Fabricate(:account) }
     let!(:other_report)   { Fabricate(:report, target_account: target_account) }