Merge commit '935d54124e80e9fe5365c724e5c8827a2b3ed5b3' into glitch-soc/merge-upstream
This commit is contained in:
commit
c10a667ac2
@ -520,12 +520,6 @@ Style/ClassVars:
|
||||
Exclude:
|
||||
- 'config/initializers/devise.rb'
|
||||
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Style/CombinableLoops:
|
||||
Exclude:
|
||||
- 'app/models/form/custom_emoji_batch.rb'
|
||||
- 'app/models/form/ip_block_batch.rb'
|
||||
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: AllowedVars.
|
||||
Style/FetchEnvVar:
|
||||
|
40
CHANGELOG.md
40
CHANGELOG.md
@ -2,6 +2,46 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [4.2.1] - 2023-10-10
|
||||
|
||||
### Added
|
||||
|
||||
- Add redirection on `/deck` URLs for logged-out users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27128))
|
||||
- Add support for v4.2.0 migrations to `tootctl maintenance fix-duplicates` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27147))
|
||||
|
||||
### Changed
|
||||
|
||||
- Change some worker lock TTLs to be shorter-lived ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27246))
|
||||
- Change user archive export allowed period from 7 days to 6 days ([suddjian](https://github.com/mastodon/mastodon/pull/27200))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix duplicate reports being sent when reporting some remote posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27355))
|
||||
- Fix clicking on already-opened thread post scrolling to the top of the thread ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27331), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27338), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27350))
|
||||
- Fix some remote posts getting truncated ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27307))
|
||||
- Fix some cases of infinite scroll code trying to fetch inaccessible posts in a loop ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27286))
|
||||
- Fix `Vary` headers not being set on some redirects ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27272))
|
||||
- Fix mentions being matched in some URL query strings ([mjankowski](https://github.com/mastodon/mastodon/pull/25656))
|
||||
- Fix unexpected linebreak in version string in the Web UI ([vmstan](https://github.com/mastodon/mastodon/pull/26986))
|
||||
- Fix double scroll bars in some columns in advanced interface ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27187))
|
||||
- Fix boosts of local users being filtered in account timelines ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27204))
|
||||
- Fix multiple instances of the trend refresh scheduler sometimes running at once ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27253))
|
||||
- Fix importer returning negative row estimates ([jgillich](https://github.com/mastodon/mastodon/pull/27258))
|
||||
- Fix incorrectly keeping outdated update notices absent from the API endpoint ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27021))
|
||||
- Fix import progress not updating on certain failures ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27247))
|
||||
- Fix websocket connections being incorrectly decremented twice on errors ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/27238))
|
||||
- Fix explore prompt appearing because of posts being received out of order ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27211))
|
||||
- Fix explore prompt sometimes showing up when the home TL is loading ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27062))
|
||||
- Fix link handling of mentions in user profiles when logged out ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27185))
|
||||
- Fix filtering audit log for entries about disabling 2FA ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27186))
|
||||
- Fix notification toasts not respecting reduce-motion ([c960657](https://github.com/mastodon/mastodon/pull/27178))
|
||||
- Fix retention dashboard not displaying correct month ([vmstan](https://github.com/mastodon/mastodon/pull/27180))
|
||||
- Fix tIME chunk not being properly removed from PNG uploads ([TheEssem](https://github.com/mastodon/mastodon/pull/27111))
|
||||
- Fix division by zero in video in bitrate computation code ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27129))
|
||||
- Fix inefficient queries in “Follows and followers” as well as several admin pages ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27116), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27306))
|
||||
- Fix ActiveRecord using two connection pools when no replica is defined ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27061))
|
||||
- Fix the search documentation URL in system checks ([renchap](https://github.com/mastodon/mastodon/pull/27036))
|
||||
|
||||
## [4.2.0] - 2023-09-21
|
||||
|
||||
The following changelog entries focus on changes visible to users, administrators, client developers or federated software developers, but there has also been a lot of code modernization, refactoring, and tooling work, in particular by [@danielmbrasil](https://github.com/danielmbrasil), [@mjankowski](https://github.com/mjankowski), [@nschonni](https://github.com/nschonni), [@renchap](https://github.com/renchap), and [@takayamaki](https://github.com/takayamaki).
|
||||
|
@ -184,7 +184,9 @@ class SwitchingColumnsArea extends PureComponent {
|
||||
|
||||
{singleColumn ? <Redirect from='/deck' to='/home' exact /> : null}
|
||||
{singleColumn && pathName.startsWith('/deck/') ? <Redirect from={pathName} to={pathName.slice(5)} /> : null}
|
||||
{/* Redirect old bookmarks (without /deck) with home-like routes to the advanced interface */}
|
||||
{!singleColumn && pathName === '/getting-started' ? <Redirect from='/getting-started' to='/deck/getting-started' exact /> : null}
|
||||
{!singleColumn && pathName === '/home' ? <Redirect from='/home' to='/deck/getting-started' exact /> : null}
|
||||
|
||||
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
||||
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
||||
|
@ -101,6 +101,7 @@ const initialPath = document.querySelector("head meta[name=initialPath]")?.getAt
|
||||
/** @type {boolean} */
|
||||
export const hasMultiColumnPath = initialPath === '/'
|
||||
|| initialPath === '/getting-started'
|
||||
|| initialPath === '/home'
|
||||
|| initialPath.startsWith('/deck');
|
||||
|
||||
/**
|
||||
|
@ -34,7 +34,7 @@ class Form::CustomEmojiBatch
|
||||
end
|
||||
|
||||
def update!
|
||||
custom_emojis.each { |custom_emoji| authorize(custom_emoji, :update?) }
|
||||
verify_authorization(:update?)
|
||||
|
||||
category = if category_id.present?
|
||||
CustomEmojiCategory.find(category_id)
|
||||
@ -49,7 +49,7 @@ class Form::CustomEmojiBatch
|
||||
end
|
||||
|
||||
def list!
|
||||
custom_emojis.each { |custom_emoji| authorize(custom_emoji, :update?) }
|
||||
verify_authorization(:update?)
|
||||
|
||||
custom_emojis.each do |custom_emoji|
|
||||
custom_emoji.update(visible_in_picker: true)
|
||||
@ -58,7 +58,7 @@ class Form::CustomEmojiBatch
|
||||
end
|
||||
|
||||
def unlist!
|
||||
custom_emojis.each { |custom_emoji| authorize(custom_emoji, :update?) }
|
||||
verify_authorization(:update?)
|
||||
|
||||
custom_emojis.each do |custom_emoji|
|
||||
custom_emoji.update(visible_in_picker: false)
|
||||
@ -67,7 +67,7 @@ class Form::CustomEmojiBatch
|
||||
end
|
||||
|
||||
def enable!
|
||||
custom_emojis.each { |custom_emoji| authorize(custom_emoji, :enable?) }
|
||||
verify_authorization(:enable?)
|
||||
|
||||
custom_emojis.each do |custom_emoji|
|
||||
custom_emoji.update(disabled: false)
|
||||
@ -76,7 +76,7 @@ class Form::CustomEmojiBatch
|
||||
end
|
||||
|
||||
def disable!
|
||||
custom_emojis.each { |custom_emoji| authorize(custom_emoji, :disable?) }
|
||||
verify_authorization(:disable?)
|
||||
|
||||
custom_emojis.each do |custom_emoji|
|
||||
custom_emoji.update(disabled: true)
|
||||
@ -85,7 +85,7 @@ class Form::CustomEmojiBatch
|
||||
end
|
||||
|
||||
def copy!
|
||||
custom_emojis.each { |custom_emoji| authorize(custom_emoji, :copy?) }
|
||||
verify_authorization(:copy?)
|
||||
|
||||
custom_emojis.each do |custom_emoji|
|
||||
copied_custom_emoji = custom_emoji.copy!
|
||||
@ -94,11 +94,15 @@ class Form::CustomEmojiBatch
|
||||
end
|
||||
|
||||
def delete!
|
||||
custom_emojis.each { |custom_emoji| authorize(custom_emoji, :destroy?) }
|
||||
verify_authorization(:destroy?)
|
||||
|
||||
custom_emojis.each do |custom_emoji|
|
||||
custom_emoji.destroy
|
||||
log_action :destroy, custom_emoji
|
||||
end
|
||||
end
|
||||
|
||||
def verify_authorization(permission)
|
||||
custom_emojis.each { |custom_emoji| authorize(custom_emoji, permission) }
|
||||
end
|
||||
end
|
||||
|
@ -21,11 +21,15 @@ class Form::IpBlockBatch
|
||||
end
|
||||
|
||||
def delete!
|
||||
ip_blocks.each { |ip_block| authorize(ip_block, :destroy?) }
|
||||
verify_authorization(:destroy?)
|
||||
|
||||
ip_blocks.each do |ip_block|
|
||||
ip_block.destroy
|
||||
log_action :destroy, ip_block
|
||||
end
|
||||
end
|
||||
|
||||
def verify_authorization(permission)
|
||||
ip_blocks.each { |ip_block| authorize(ip_block, permission) }
|
||||
end
|
||||
end
|
||||
|
@ -11,16 +11,31 @@ class UnreservedUsernameValidator < ActiveModel::Validator
|
||||
|
||||
private
|
||||
|
||||
def pam_controlled?
|
||||
return false unless Devise.pam_authentication && Devise.pam_controlled_service
|
||||
|
||||
Rpam2.account(Devise.pam_controlled_service, @username).present?
|
||||
def reserved_username?
|
||||
pam_username_reserved? || settings_username_reserved?
|
||||
end
|
||||
|
||||
def reserved_username?
|
||||
return true if pam_controlled?
|
||||
return false unless Setting.reserved_usernames
|
||||
def pam_username_reserved?
|
||||
pam_controlled? && pam_reserves_username?
|
||||
end
|
||||
|
||||
def pam_controlled?
|
||||
Devise.pam_authentication && Devise.pam_controlled_service
|
||||
end
|
||||
|
||||
def pam_reserves_username?
|
||||
Rpam2.account(Devise.pam_controlled_service, @username)
|
||||
end
|
||||
|
||||
def settings_username_reserved?
|
||||
settings_has_reserved_usernames? && settings_reserves_username?
|
||||
end
|
||||
|
||||
def settings_has_reserved_usernames?
|
||||
Setting.reserved_usernames.present?
|
||||
end
|
||||
|
||||
def settings_reserves_username?
|
||||
Setting.reserved_usernames.include?(@username.downcase)
|
||||
end
|
||||
end
|
||||
|
41
app/views/admin/accounts/_buttons.html.haml
Normal file
41
app/views/admin/accounts/_buttons.html.haml
Normal file
@ -0,0 +1,41 @@
|
||||
- if account.suspended?
|
||||
%hr.spacer/
|
||||
- if account.suspension_origin_remote?
|
||||
%p.muted-hint= deletion_request.present? ? t('admin.accounts.remote_suspension_reversible_hint_html', date: content_tag(:strong, l(deletion_request.due_at.to_date))) : t('admin.accounts.remote_suspension_irreversible')
|
||||
- else
|
||||
%p.muted-hint= deletion_request.present? ? t('admin.accounts.suspension_reversible_hint_html', date: content_tag(:strong, l(deletion_request.due_at.to_date))) : t('admin.accounts.suspension_irreversible')
|
||||
= link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsuspend, account)
|
||||
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), method: :post, class: 'button' if can?(:redownload, account) && account.suspension_origin_remote?
|
||||
- if deletion_request.present?
|
||||
= link_to t('admin.accounts.delete'), admin_account_path(account.id), method: :delete, class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, account)
|
||||
- else
|
||||
.action-buttons
|
||||
%div
|
||||
- if account.local? && account.user_approved?
|
||||
= link_to t('admin.accounts.warn'), new_admin_account_action_path(account.id, type: 'none'), class: 'button' if can?(:warn, account)
|
||||
- if account.user_disabled?
|
||||
= link_to t('admin.accounts.enable'), enable_admin_account_path(account.id), method: :post, class: 'button' if can?(:enable, account.user)
|
||||
- else
|
||||
= link_to t('admin.accounts.disable'), new_admin_account_action_path(account.id, type: 'disable'), class: 'button' if can?(:disable, account.user)
|
||||
- if account.sensitized?
|
||||
= link_to t('admin.accounts.undo_sensitized'), unsensitive_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsensitive, account)
|
||||
- elsif !account.local? || account.user_approved?
|
||||
= link_to t('admin.accounts.sensitive'), new_admin_account_action_path(account.id, type: 'sensitive'), class: 'button' if can?(:sensitive, account)
|
||||
- if account.silenced?
|
||||
= link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsilence, account)
|
||||
- elsif !account.local? || account.user_approved?
|
||||
= link_to t('admin.accounts.silence'), new_admin_account_action_path(account.id, type: 'silence'), class: 'button' if can?(:silence, account)
|
||||
- if account.local?
|
||||
- if account.user_pending?
|
||||
= link_to t('admin.accounts.approve'), approve_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:approve, account.user)
|
||||
= link_to t('admin.accounts.reject'), reject_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, account.user)
|
||||
- unless account.user_confirmed?
|
||||
= link_to t('admin.accounts.confirm'), admin_account_confirmation_path(account.id), method: :post, class: 'button' if can?(:confirm, account.user)
|
||||
- if !account.local? || account.user_approved?
|
||||
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(account.id, type: 'suspend'), class: 'button' if can?(:suspend, account)
|
||||
%div
|
||||
- if account.local?
|
||||
- if !account.memorial? && account.user_approved?
|
||||
= link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, account)
|
||||
- else
|
||||
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), method: :post, class: 'button' if can?(:redownload, account)
|
43
app/views/admin/accounts/_counters.html.haml
Normal file
43
app/views/admin/accounts/_counters.html.haml
Normal file
@ -0,0 +1,43 @@
|
||||
.dashboard__counters.admin-account-counters
|
||||
%div
|
||||
= link_to admin_account_statuses_path(account.id) do
|
||||
.dashboard__counters__num= number_with_delimiter account.statuses_count
|
||||
.dashboard__counters__label= t 'admin.accounts.statuses'
|
||||
%div
|
||||
= link_to admin_account_statuses_path(account.id, { media: true }) do
|
||||
.dashboard__counters__num= number_to_human_size account.media_attachments.sum('file_file_size')
|
||||
.dashboard__counters__label= t 'admin.accounts.media_attachments'
|
||||
%div
|
||||
= link_to admin_account_relationships_path(account.id, location: account.local? ? nil : 'local', relationship: 'followed_by') do
|
||||
.dashboard__counters__num= number_with_delimiter account.local_followers_count
|
||||
.dashboard__counters__label= t 'admin.accounts.followers'
|
||||
%div
|
||||
= link_to admin_reports_path(account_id: account.id) do
|
||||
.dashboard__counters__num= number_with_delimiter account.reports.count
|
||||
.dashboard__counters__label= t 'admin.accounts.show.created_reports'
|
||||
%div
|
||||
= link_to admin_reports_path(target_account_id: account.id) do
|
||||
.dashboard__counters__num= number_with_delimiter account.targeted_reports.count
|
||||
.dashboard__counters__label= t 'admin.accounts.show.targeted_reports'
|
||||
%div
|
||||
= link_to admin_action_logs_path(target_account_id: account.id) do
|
||||
.dashboard__counters__text
|
||||
- if account.local? && account.user.nil?
|
||||
= t('admin.accounts.deleted')
|
||||
- elsif account.memorial?
|
||||
= t('admin.accounts.memorialized')
|
||||
- elsif account.suspended?
|
||||
= t('admin.accounts.suspended')
|
||||
- elsif account.silenced?
|
||||
= t('admin.accounts.silenced')
|
||||
- elsif account.local? && account.user&.disabled?
|
||||
= t('admin.accounts.disabled')
|
||||
- elsif account.local? && !account.user&.confirmed?
|
||||
= t('admin.accounts.confirming')
|
||||
- elsif account.local? && !account.user_approved?
|
||||
= t('admin.accounts.pending')
|
||||
- elsif account.sensitized?
|
||||
= t('admin.accounts.sensitive')
|
||||
- else
|
||||
= t('admin.accounts.no_limits_imposed')
|
||||
.dashboard__counters__label= t 'admin.accounts.login_status'
|
82
app/views/admin/accounts/_local_account.html.haml
Normal file
82
app/views/admin/accounts/_local_account.html.haml
Normal file
@ -0,0 +1,82 @@
|
||||
- if account.avatar?
|
||||
%tr
|
||||
%th= t('admin.accounts.avatar')
|
||||
%td= table_link_to 'trash', t('admin.accounts.remove_avatar'), remove_avatar_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_avatar, account)
|
||||
%td
|
||||
- if account.header?
|
||||
%tr
|
||||
%th= t('admin.accounts.header')
|
||||
%td= table_link_to 'trash', t('admin.accounts.remove_header'), remove_header_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_header, account)
|
||||
%td
|
||||
%tr
|
||||
%th= t('admin.accounts.role')
|
||||
%td
|
||||
- if account.user_role&.everyone?
|
||||
= t('admin.accounts.no_role_assigned')
|
||||
- else
|
||||
= account.user_role&.name
|
||||
%td
|
||||
= table_link_to 'vcard', t('admin.accounts.change_role.label'), admin_user_role_path(account.user) if can?(:change_role, account.user)
|
||||
%tr
|
||||
%th{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= t('admin.accounts.email')
|
||||
%td{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= account.user_email
|
||||
%td= table_link_to 'edit', t('admin.accounts.change_email.label'), admin_account_change_email_path(account.id) if can?(:change_email, account.user)
|
||||
%tr
|
||||
%td= table_link_to 'search', t('admin.accounts.search_same_email_domain'), admin_accounts_path(email: "%@#{account.user_email.split('@').last}")
|
||||
- if can?(:create, :email_domain_block)
|
||||
%tr
|
||||
%td= table_link_to 'ban', t('admin.accounts.add_email_domain_block'), new_admin_email_domain_block_path(_domain: account.user_email.split('@').last)
|
||||
- if account.user_unconfirmed_email.present?
|
||||
%tr
|
||||
%th= t('admin.accounts.unconfirmed_email')
|
||||
%td= account.user_unconfirmed_email
|
||||
%td
|
||||
%tr
|
||||
%th= t('admin.accounts.email_status')
|
||||
%td
|
||||
- if account.user&.confirmed?
|
||||
= t('admin.accounts.confirmed')
|
||||
- else
|
||||
= t('admin.accounts.confirming')
|
||||
%td= table_link_to 'refresh', t('admin.accounts.resend_confirmation.send'), resend_admin_account_confirmation_path(account.id), method: :post if can?(:confirm, account.user)
|
||||
%tr
|
||||
%th{ rowspan: can?(:reset_password, account.user) ? 2 : 1 }= t('admin.accounts.security')
|
||||
%td{ rowspan: can?(:reset_password, account.user) ? 2 : 1 }
|
||||
- if account.user&.two_factor_enabled?
|
||||
= t 'admin.accounts.security_measures.password_and_2fa'
|
||||
- else
|
||||
= t 'admin.accounts.security_measures.only_password'
|
||||
%td
|
||||
- if account.user&.two_factor_enabled?
|
||||
= table_link_to 'unlock', t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(account.user.id), method: :delete if can?(:disable_2fa, account.user)
|
||||
- if can?(:reset_password, account.user)
|
||||
%tr
|
||||
%td
|
||||
= table_link_to 'key', t('admin.accounts.reset_password'), admin_account_reset_path(account.id), method: :create, data: { confirm: t('admin.accounts.are_you_sure') }
|
||||
%tr
|
||||
%th= t('simple_form.labels.defaults.locale')
|
||||
%td= standard_locale_name(account.user_locale)
|
||||
%td
|
||||
%tr
|
||||
%th= t('admin.accounts.joined')
|
||||
%td
|
||||
%time.formatted{ datetime: account.created_at.iso8601, title: l(account.created_at) }= l account.created_at
|
||||
%td
|
||||
- recent_ips = account.user.ips.order(used_at: :desc).to_a
|
||||
- recent_ips.each_with_index do |recent_ip, i|
|
||||
%tr
|
||||
- if i.zero?
|
||||
%th{ rowspan: recent_ips.size }= t('admin.accounts.most_recent_ip')
|
||||
%td= recent_ip.ip
|
||||
%td= table_link_to 'search', t('admin.accounts.search_same_ip'), admin_accounts_path(ip: recent_ip.ip)
|
||||
%tr
|
||||
%th= t('admin.accounts.most_recent_activity')
|
||||
%td
|
||||
- if account.user_current_sign_in_at
|
||||
%time.formatted{ datetime: account.user_current_sign_in_at.iso8601, title: l(account.user_current_sign_in_at) }= l account.user_current_sign_in_at
|
||||
%td
|
||||
- if account.user&.invited?
|
||||
%tr
|
||||
%th= t('admin.accounts.invited_by')
|
||||
%td= admin_account_link_to account.user.invite.user.account
|
||||
%td
|
15
app/views/admin/accounts/_remote_account.html.haml
Normal file
15
app/views/admin/accounts/_remote_account.html.haml
Normal file
@ -0,0 +1,15 @@
|
||||
%tr
|
||||
%th= t('admin.accounts.inbox_url')
|
||||
%td
|
||||
= account.inbox_url
|
||||
= fa_icon DeliveryFailureTracker.available?(account.inbox_url) ? 'check' : 'times'
|
||||
%td
|
||||
= table_link_to 'search', domain_block.present? ? t('admin.domain_blocks.view') : t('admin.accounts.view_domain'), admin_instance_path(account.domain)
|
||||
%tr
|
||||
%th= t('admin.accounts.shared_inbox_url')
|
||||
%td
|
||||
= account.shared_inbox_url
|
||||
= fa_icon DeliveryFailureTracker.available?(account.shared_inbox_url) ? 'check' : 'times'
|
||||
%td
|
||||
- if domain_block.nil?
|
||||
= table_link_to 'ban', t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: account.domain)
|
@ -27,49 +27,7 @@
|
||||
%div
|
||||
.account__header__content.emojify= prerender_custom_emojis(account_bio_format(account), account.emojis)
|
||||
|
||||
.dashboard__counters.admin-account-counters
|
||||
%div
|
||||
= link_to admin_account_statuses_path(@account.id) do
|
||||
.dashboard__counters__num= number_with_delimiter @account.statuses_count
|
||||
.dashboard__counters__label= t 'admin.accounts.statuses'
|
||||
%div
|
||||
= link_to admin_account_statuses_path(@account.id, { media: true }) do
|
||||
.dashboard__counters__num= number_to_human_size @account.media_attachments.sum('file_file_size')
|
||||
.dashboard__counters__label= t 'admin.accounts.media_attachments'
|
||||
%div
|
||||
= link_to admin_account_relationships_path(@account.id, location: @account.local? ? nil : 'local', relationship: 'followed_by') do
|
||||
.dashboard__counters__num= number_with_delimiter @account.local_followers_count
|
||||
.dashboard__counters__label= t 'admin.accounts.followers'
|
||||
%div
|
||||
= link_to admin_reports_path(account_id: @account.id) do
|
||||
.dashboard__counters__num= number_with_delimiter @account.reports.count
|
||||
.dashboard__counters__label= t '.created_reports'
|
||||
%div
|
||||
= link_to admin_reports_path(target_account_id: @account.id) do
|
||||
.dashboard__counters__num= number_with_delimiter @account.targeted_reports.count
|
||||
.dashboard__counters__label= t '.targeted_reports'
|
||||
%div
|
||||
= link_to admin_action_logs_path(target_account_id: @account.id) do
|
||||
.dashboard__counters__text
|
||||
- if @account.local? && @account.user.nil?
|
||||
= t('admin.accounts.deleted')
|
||||
- elsif @account.memorial?
|
||||
= t('admin.accounts.memorialized')
|
||||
- elsif @account.suspended?
|
||||
= t('admin.accounts.suspended')
|
||||
- elsif @account.silenced?
|
||||
= t('admin.accounts.silenced')
|
||||
- elsif @account.local? && @account.user&.disabled?
|
||||
= t('admin.accounts.disabled')
|
||||
- elsif @account.local? && !@account.user&.confirmed?
|
||||
= t('admin.accounts.confirming')
|
||||
- elsif @account.local? && !@account.user_approved?
|
||||
= t('admin.accounts.pending')
|
||||
- elsif @account.sensitized?
|
||||
= t('admin.accounts.sensitive')
|
||||
- else
|
||||
= t('admin.accounts.no_limits_imposed')
|
||||
.dashboard__counters__label= t 'admin.accounts.login_status'
|
||||
= render 'admin/accounts/counters', account: @account
|
||||
|
||||
- if @account.local? && @account.user.nil?
|
||||
= link_to t('admin.accounts.unblock_email'), unblock_email_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unblock_email, @account) && CanonicalEmailBlock.exists?(reference_account_id: @account.id)
|
||||
@ -78,171 +36,11 @@
|
||||
%table.table.inline-table
|
||||
%tbody
|
||||
- if @account.local?
|
||||
- if @account.avatar?
|
||||
%tr
|
||||
%th= t('admin.accounts.avatar')
|
||||
%td= table_link_to 'trash', t('admin.accounts.remove_avatar'), remove_avatar_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_avatar, @account)
|
||||
%td
|
||||
|
||||
- if @account.header?
|
||||
%tr
|
||||
%th= t('admin.accounts.header')
|
||||
%td= table_link_to 'trash', t('admin.accounts.remove_header'), remove_header_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_header, @account)
|
||||
%td
|
||||
|
||||
%tr
|
||||
%th= t('admin.accounts.role')
|
||||
%td
|
||||
- if @account.user_role&.everyone?
|
||||
= t('admin.accounts.no_role_assigned')
|
||||
- else
|
||||
= @account.user_role&.name
|
||||
%td
|
||||
= table_link_to 'vcard', t('admin.accounts.change_role.label'), admin_user_role_path(@account.user) if can?(:change_role, @account.user)
|
||||
|
||||
%tr
|
||||
%th{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= t('admin.accounts.email')
|
||||
%td{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= @account.user_email
|
||||
%td= table_link_to 'edit', t('admin.accounts.change_email.label'), admin_account_change_email_path(@account.id) if can?(:change_email, @account.user)
|
||||
|
||||
%tr
|
||||
%td= table_link_to 'search', t('admin.accounts.search_same_email_domain'), admin_accounts_path(email: "%@#{@account.user_email.split('@').last}")
|
||||
|
||||
- if can?(:create, :email_domain_block)
|
||||
%tr
|
||||
%td= table_link_to 'ban', t('admin.accounts.add_email_domain_block'), new_admin_email_domain_block_path(_domain: @account.user_email.split('@').last)
|
||||
|
||||
- if @account.user_unconfirmed_email.present?
|
||||
%tr
|
||||
%th= t('admin.accounts.unconfirmed_email')
|
||||
%td= @account.user_unconfirmed_email
|
||||
%td
|
||||
|
||||
%tr
|
||||
%th= t('admin.accounts.email_status')
|
||||
%td
|
||||
- if @account.user&.confirmed?
|
||||
= t('admin.accounts.confirmed')
|
||||
- else
|
||||
= t('admin.accounts.confirming')
|
||||
%td= table_link_to 'refresh', t('admin.accounts.resend_confirmation.send'), resend_admin_account_confirmation_path(@account.id), method: :post if can?(:confirm, @account.user)
|
||||
%tr
|
||||
%th{ rowspan: can?(:reset_password, @account.user) ? 2 : 1 }= t('admin.accounts.security')
|
||||
%td{ rowspan: can?(:reset_password, @account.user) ? 2 : 1 }
|
||||
- if @account.user&.two_factor_enabled?
|
||||
= t 'admin.accounts.security_measures.password_and_2fa'
|
||||
- else
|
||||
= t 'admin.accounts.security_measures.only_password'
|
||||
%td
|
||||
- if @account.user&.two_factor_enabled?
|
||||
= table_link_to 'unlock', t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete if can?(:disable_2fa, @account.user)
|
||||
|
||||
- if can?(:reset_password, @account.user)
|
||||
%tr
|
||||
%td
|
||||
= table_link_to 'key', t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, data: { confirm: t('admin.accounts.are_you_sure') }
|
||||
|
||||
%tr
|
||||
%th= t('simple_form.labels.defaults.locale')
|
||||
%td= standard_locale_name(@account.user_locale)
|
||||
%td
|
||||
|
||||
%tr
|
||||
%th= t('admin.accounts.joined')
|
||||
%td
|
||||
%time.formatted{ datetime: @account.created_at.iso8601, title: l(@account.created_at) }= l @account.created_at
|
||||
%td
|
||||
|
||||
- recent_ips = @account.user.ips.order(used_at: :desc).to_a
|
||||
|
||||
- recent_ips.each_with_index do |recent_ip, i|
|
||||
%tr
|
||||
- if i.zero?
|
||||
%th{ rowspan: recent_ips.size }= t('admin.accounts.most_recent_ip')
|
||||
%td= recent_ip.ip
|
||||
%td= table_link_to 'search', t('admin.accounts.search_same_ip'), admin_accounts_path(ip: recent_ip.ip)
|
||||
|
||||
%tr
|
||||
%th= t('admin.accounts.most_recent_activity')
|
||||
%td
|
||||
- if @account.user_current_sign_in_at
|
||||
%time.formatted{ datetime: @account.user_current_sign_in_at.iso8601, title: l(@account.user_current_sign_in_at) }= l @account.user_current_sign_in_at
|
||||
%td
|
||||
|
||||
- if @account.user&.invited?
|
||||
%tr
|
||||
%th= t('admin.accounts.invited_by')
|
||||
%td= admin_account_link_to @account.user.invite.user.account
|
||||
%td
|
||||
|
||||
= render 'admin/accounts/local_account', account: @account
|
||||
- else
|
||||
%tr
|
||||
%th= t('admin.accounts.inbox_url')
|
||||
%td
|
||||
= @account.inbox_url
|
||||
= fa_icon DeliveryFailureTracker.available?(@account.inbox_url) ? 'check' : 'times'
|
||||
%td
|
||||
= table_link_to 'search', @domain_block.present? ? t('admin.domain_blocks.view') : t('admin.accounts.view_domain'), admin_instance_path(@account.domain)
|
||||
%tr
|
||||
%th= t('admin.accounts.shared_inbox_url')
|
||||
%td
|
||||
= @account.shared_inbox_url
|
||||
= fa_icon DeliveryFailureTracker.available?(@account.shared_inbox_url) ? 'check' : 'times'
|
||||
%td
|
||||
- if @domain_block.nil?
|
||||
= table_link_to 'ban', t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain)
|
||||
= render 'admin/accounts/remote_account', account: @account, domain_block: @domain_block
|
||||
|
||||
- if @account.suspended?
|
||||
%hr.spacer/
|
||||
|
||||
- if @account.suspension_origin_remote?
|
||||
%p.muted-hint= @deletion_request.present? ? t('admin.accounts.remote_suspension_reversible_hint_html', date: content_tag(:strong, l(@deletion_request.due_at.to_date))) : t('admin.accounts.remote_suspension_irreversible')
|
||||
- else
|
||||
%p.muted-hint= @deletion_request.present? ? t('admin.accounts.suspension_reversible_hint_html', date: content_tag(:strong, l(@deletion_request.due_at.to_date))) : t('admin.accounts.suspension_irreversible')
|
||||
|
||||
= link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsuspend, @account)
|
||||
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account) && @account.suspension_origin_remote?
|
||||
|
||||
- if @deletion_request.present?
|
||||
= link_to t('admin.accounts.delete'), admin_account_path(@account.id), method: :delete, class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, @account)
|
||||
- else
|
||||
.action-buttons
|
||||
%div
|
||||
- if @account.local? && @account.user_approved?
|
||||
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account)
|
||||
|
||||
- if @account.user_disabled?
|
||||
= link_to t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post, class: 'button' if can?(:enable, @account.user)
|
||||
- else
|
||||
= link_to t('admin.accounts.disable'), new_admin_account_action_path(@account.id, type: 'disable'), class: 'button' if can?(:disable, @account.user)
|
||||
|
||||
- if @account.sensitized?
|
||||
= link_to t('admin.accounts.undo_sensitized'), unsensitive_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsensitive, @account)
|
||||
- elsif !@account.local? || @account.user_approved?
|
||||
= link_to t('admin.accounts.sensitive'), new_admin_account_action_path(@account.id, type: 'sensitive'), class: 'button' if can?(:sensitive, @account)
|
||||
|
||||
- if @account.silenced?
|
||||
= link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsilence, @account)
|
||||
- elsif !@account.local? || @account.user_approved?
|
||||
= link_to t('admin.accounts.silence'), new_admin_account_action_path(@account.id, type: 'silence'), class: 'button' if can?(:silence, @account)
|
||||
|
||||
- if @account.local?
|
||||
- if @account.user_pending?
|
||||
= link_to t('admin.accounts.approve'), approve_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:approve, @account.user)
|
||||
= link_to t('admin.accounts.reject'), reject_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, @account.user)
|
||||
|
||||
- unless @account.user_confirmed?
|
||||
= link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' if can?(:confirm, @account.user)
|
||||
|
||||
- if !@account.local? || @account.user_approved?
|
||||
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@account.id, type: 'suspend'), class: 'button' if can?(:suspend, @account)
|
||||
|
||||
%div
|
||||
- if @account.local?
|
||||
- if !@account.memorial? && @account.user_approved?
|
||||
= link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account)
|
||||
- else
|
||||
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account)
|
||||
= render 'admin/accounts/buttons', account: @account, deletion_request: @deletion_request
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
|
24
app/views/admin/reports/_comment.html.haml
Normal file
24
app/views/admin/reports/_comment.html.haml
Normal file
@ -0,0 +1,24 @@
|
||||
- if report.account.instance_actor?
|
||||
%p= t('admin.reports.comment_description_html', name: content_tag(:strong, site_hostname, class: 'username'))
|
||||
- elsif report.account.local?
|
||||
%p= t('admin.reports.comment_description_html', name: content_tag(:strong, report.account.username, class: 'username'))
|
||||
- else
|
||||
%p= t('admin.reports.comment_description_html', name: t('admin.reports.remote_user_placeholder', instance: report.account.domain))
|
||||
.report-notes
|
||||
.report-notes__item
|
||||
- if report.account.local? && !report.account.instance_actor?
|
||||
= image_tag report.account.avatar.url, class: 'report-notes__item__avatar'
|
||||
- else
|
||||
= image_tag(full_asset_url('avatars/original/missing.png', skip_pipeline: true), class: 'report-notes__item__avatar')
|
||||
.report-notes__item__header
|
||||
%span.username
|
||||
- if report.account.instance_actor?
|
||||
= site_hostname
|
||||
- elsif report.account.local?
|
||||
= link_to report.account.username, admin_account_path(report.account_id)
|
||||
- else
|
||||
= link_to report.account.domain, admin_instance_path(report.account.domain)
|
||||
%time.relative-formatted{ datetime: report.created_at.iso8601 }
|
||||
= l report.created_at.to_date
|
||||
.report-notes__item__content
|
||||
= simple_format(h(report.comment))
|
46
app/views/admin/reports/_header_card.html.haml
Normal file
46
app/views/admin/reports/_header_card.html.haml
Normal file
@ -0,0 +1,46 @@
|
||||
.report-header__card
|
||||
.account-card
|
||||
.account-card__header
|
||||
= image_tag report.target_account.header.url, alt: ''
|
||||
.account-card__title
|
||||
.account-card__title__avatar
|
||||
= image_tag report.target_account.avatar.url, alt: ''
|
||||
.display-name
|
||||
%bdi
|
||||
%strong.emojify.p-name= display_name(report.target_account, custom_emojify: true)
|
||||
%span
|
||||
= acct(report.target_account)
|
||||
= fa_icon('lock') if report.target_account.locked?
|
||||
- if report.target_account.note.present?
|
||||
.account-card__bio.emojify
|
||||
= prerender_custom_emojis(account_bio_format(report.target_account), report.target_account.emojis)
|
||||
.account-card__actions
|
||||
.account-card__counters
|
||||
.account-card__counters__item
|
||||
= friendly_number_to_human report.target_account.statuses_count
|
||||
%small= t('accounts.posts', count: report.target_account.statuses_count).downcase
|
||||
.account-card__counters__item
|
||||
= friendly_number_to_human report.target_account.followers_count
|
||||
%small= t('accounts.followers', count: report.target_account.followers_count).downcase
|
||||
.account-card__counters__item
|
||||
= friendly_number_to_human report.target_account.following_count
|
||||
%small= t('accounts.following', count: report.target_account.following_count).downcase
|
||||
.account-card__actions__button
|
||||
= link_to t('admin.reports.view_profile'), admin_account_path(report.target_account_id), class: 'button'
|
||||
.report-header__details.report-header__details--horizontal
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('admin.accounts.joined')
|
||||
.report-header__details__item__content
|
||||
%time.time-ago{ datetime: report.target_account.created_at.iso8601, title: l(report.target_account.created_at) }= l report.target_account.created_at
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('accounts.last_active')
|
||||
.report-header__details__item__content
|
||||
- if report.target_account.last_status_at.present?
|
||||
%time.time-ago{ datetime: report.target_account.last_status_at.to_date.iso8601, title: l(report.target_account.last_status_at.to_date) }= l report.target_account.last_status_at
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('admin.accounts.strikes')
|
||||
.report-header__details__item__content
|
||||
= report.target_account.previous_strikes_count
|
53
app/views/admin/reports/_header_details.html.haml
Normal file
53
app/views/admin/reports/_header_details.html.haml
Normal file
@ -0,0 +1,53 @@
|
||||
.report-header__details
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('admin.reports.created_at')
|
||||
.report-header__details__item__content
|
||||
%time.formatted{ datetime: report.created_at.iso8601 }
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('admin.reports.reported_by')
|
||||
.report-header__details__item__content
|
||||
- if report.account.instance_actor?
|
||||
= site_hostname
|
||||
- elsif report.account.local?
|
||||
= admin_account_link_to report.account
|
||||
- else
|
||||
= report.account.domain
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('admin.reports.status')
|
||||
.report-header__details__item__content
|
||||
- if report.action_taken?
|
||||
= t('admin.reports.resolved')
|
||||
- else
|
||||
= t('admin.reports.unresolved')
|
||||
- unless report.target_account.local?
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('admin.reports.forwarded')
|
||||
.report-header__details__item__content
|
||||
- if report.forwarded?
|
||||
= t('simple_form.yes')
|
||||
- else
|
||||
= t('simple_form.no')
|
||||
- if report.action_taken_by_account.present?
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('admin.reports.action_taken_by')
|
||||
.report-header__details__item__content
|
||||
= admin_account_link_to report.action_taken_by_account
|
||||
- else
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('admin.reports.assigned')
|
||||
.report-header__details__item__content
|
||||
- if report.assigned_account.nil?
|
||||
= t 'admin.reports.no_one_assigned'
|
||||
- else
|
||||
= admin_account_link_to report.assigned_account
|
||||
—
|
||||
- if report.assigned_account != current_user.account
|
||||
= table_link_to 'user', t('admin.reports.assign_to_self'), assign_to_self_admin_report_path(report), method: :post
|
||||
- elsif !report.assigned_account.nil?
|
||||
= table_link_to 'trash', t('admin.reports.unassign'), unassign_admin_report_path(report), method: :post
|
@ -8,106 +8,8 @@
|
||||
= link_to t('admin.reports.mark_as_unresolved'), reopen_admin_report_path(@report), method: :post, class: 'button'
|
||||
|
||||
.report-header
|
||||
.report-header__card
|
||||
.account-card
|
||||
.account-card__header
|
||||
= image_tag @report.target_account.header.url, alt: ''
|
||||
.account-card__title
|
||||
.account-card__title__avatar
|
||||
= image_tag @report.target_account.avatar.url, alt: ''
|
||||
.display-name
|
||||
%bdi
|
||||
%strong.emojify.p-name= display_name(@report.target_account, custom_emojify: true)
|
||||
%span
|
||||
= acct(@report.target_account)
|
||||
= fa_icon('lock') if @report.target_account.locked?
|
||||
- if @report.target_account.note.present?
|
||||
.account-card__bio.emojify
|
||||
= prerender_custom_emojis(account_bio_format(@report.target_account), @report.target_account.emojis)
|
||||
.account-card__actions
|
||||
.account-card__counters
|
||||
.account-card__counters__item
|
||||
= friendly_number_to_human @report.target_account.statuses_count
|
||||
%small= t('accounts.posts', count: @report.target_account.statuses_count).downcase
|
||||
.account-card__counters__item
|
||||
= friendly_number_to_human @report.target_account.followers_count
|
||||
%small= t('accounts.followers', count: @report.target_account.followers_count).downcase
|
||||
.account-card__counters__item
|
||||
= friendly_number_to_human @report.target_account.following_count
|
||||
%small= t('accounts.following', count: @report.target_account.following_count).downcase
|
||||
.account-card__actions__button
|
||||
= link_to t('admin.reports.view_profile'), admin_account_path(@report.target_account_id), class: 'button'
|
||||
.report-header__details.report-header__details--horizontal
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('admin.accounts.joined')
|
||||
.report-header__details__item__content
|
||||
%time.time-ago{ datetime: @report.target_account.created_at.iso8601, title: l(@report.target_account.created_at) }= l @report.target_account.created_at
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('accounts.last_active')
|
||||
.report-header__details__item__content
|
||||
- if @report.target_account.last_status_at.present?
|
||||
%time.time-ago{ datetime: @report.target_account.last_status_at.to_date.iso8601, title: l(@report.target_account.last_status_at.to_date) }= l @report.target_account.last_status_at
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('admin.accounts.strikes')
|
||||
.report-header__details__item__content
|
||||
= @report.target_account.previous_strikes_count
|
||||
|
||||
.report-header__details
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('admin.reports.created_at')
|
||||
.report-header__details__item__content
|
||||
%time.formatted{ datetime: @report.created_at.iso8601 }
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('admin.reports.reported_by')
|
||||
.report-header__details__item__content
|
||||
- if @report.account.instance_actor?
|
||||
= site_hostname
|
||||
- elsif @report.account.local?
|
||||
= admin_account_link_to @report.account
|
||||
- else
|
||||
= @report.account.domain
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('admin.reports.status')
|
||||
.report-header__details__item__content
|
||||
- if @report.action_taken?
|
||||
= t('admin.reports.resolved')
|
||||
- else
|
||||
= t('admin.reports.unresolved')
|
||||
- unless @report.target_account.local?
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('admin.reports.forwarded')
|
||||
.report-header__details__item__content
|
||||
- if @report.forwarded?
|
||||
= t('simple_form.yes')
|
||||
- else
|
||||
= t('simple_form.no')
|
||||
- if @report.action_taken_by_account.present?
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('admin.reports.action_taken_by')
|
||||
.report-header__details__item__content
|
||||
= admin_account_link_to @report.action_taken_by_account
|
||||
- else
|
||||
.report-header__details__item
|
||||
.report-header__details__item__header
|
||||
%strong= t('admin.reports.assigned')
|
||||
.report-header__details__item__content
|
||||
- if @report.assigned_account.nil?
|
||||
= t 'admin.reports.no_one_assigned'
|
||||
- else
|
||||
= admin_account_link_to @report.assigned_account
|
||||
—
|
||||
- if @report.assigned_account != current_user.account
|
||||
= table_link_to 'user', t('admin.reports.assign_to_self'), assign_to_self_admin_report_path(@report), method: :post
|
||||
- elsif !@report.assigned_account.nil?
|
||||
= table_link_to 'trash', t('admin.reports.unassign'), unassign_admin_report_path(@report), method: :post
|
||||
= render 'admin/reports/header_card', report: @report
|
||||
= render 'admin/reports/header_details', report: @report
|
||||
|
||||
%hr.spacer
|
||||
|
||||
@ -118,33 +20,7 @@
|
||||
= react_admin_component :report_reason_selector, id: @report.id, category: @report.category, rule_ids: @report.rule_ids&.map(&:to_s), disabled: @report.action_taken?
|
||||
|
||||
- if @report.comment.present?
|
||||
- if @report.account.instance_actor?
|
||||
%p= t('admin.reports.comment_description_html', name: content_tag(:strong, site_hostname, class: 'username'))
|
||||
- elsif @report.account.local?
|
||||
%p= t('admin.reports.comment_description_html', name: content_tag(:strong, @report.account.username, class: 'username'))
|
||||
- else
|
||||
%p= t('admin.reports.comment_description_html', name: t('admin.reports.remote_user_placeholder', instance: @report.account.domain))
|
||||
|
||||
.report-notes
|
||||
.report-notes__item
|
||||
- if @report.account.local? && !@report.account.instance_actor?
|
||||
= image_tag @report.account.avatar.url, class: 'report-notes__item__avatar'
|
||||
- else
|
||||
= image_tag(full_asset_url('avatars/original/missing.png', skip_pipeline: true), class: 'report-notes__item__avatar')
|
||||
|
||||
.report-notes__item__header
|
||||
%span.username
|
||||
- if @report.account.instance_actor?
|
||||
= site_hostname
|
||||
- elsif @report.account.local?
|
||||
= link_to @report.account.username, admin_account_path(@report.account_id)
|
||||
- else
|
||||
= link_to @report.account.domain, admin_instance_path(@report.account.domain)
|
||||
%time.relative-formatted{ datetime: @report.created_at.iso8601 }
|
||||
= l @report.created_at.to_date
|
||||
|
||||
.report-notes__item__content
|
||||
= simple_format(h(@report.comment))
|
||||
= render 'admin/reports/comment', report: @report
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
|
@ -7,468 +7,319 @@ RSpec.describe AccountsController do
|
||||
|
||||
let(:account) { Fabricate(:account) }
|
||||
|
||||
shared_examples 'cacheable response' do
|
||||
it 'does not set cookies' do
|
||||
expect(response.cookies).to be_empty
|
||||
expect(response.headers['Set-Cookies']).to be_nil
|
||||
shared_examples 'unapproved account check' do
|
||||
before { account.user.update(approved: false) }
|
||||
|
||||
it 'returns http not found' do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'permanently suspended account check' do
|
||||
before do
|
||||
account.suspend!
|
||||
account.deletion_request.destroy
|
||||
end
|
||||
|
||||
it 'does not set sessions' do
|
||||
expect(session).to be_empty
|
||||
end
|
||||
it 'returns http gone' do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
|
||||
it 'returns Vary header' do
|
||||
expect(response.headers['Vary']).to include 'Accept'
|
||||
expect(response).to have_http_status(410)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns public Cache-Control header' do
|
||||
expect(response.headers['Cache-Control']).to include 'public'
|
||||
shared_examples 'temporarily suspended account check' do |code: 403|
|
||||
before { account.suspend! }
|
||||
|
||||
it 'returns appropriate http response code' do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
|
||||
expect(response).to have_http_status(code)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
let(:format) { 'html' }
|
||||
context 'with basic account status checks' do
|
||||
context 'with HTML' do
|
||||
let(:format) { 'html' }
|
||||
|
||||
let!(:status) { Fabricate(:status, account: account) }
|
||||
let!(:status_reply) { Fabricate(:status, account: account, thread: Fabricate(:status)) }
|
||||
let!(:status_self_reply) { Fabricate(:status, account: account, thread: status) }
|
||||
let!(:status_media) { Fabricate(:status, account: account) }
|
||||
let!(:status_pinned) { Fabricate(:status, account: account) }
|
||||
let!(:status_private) { Fabricate(:status, account: account, visibility: :private) }
|
||||
let!(:status_direct) { Fabricate(:status, account: account, visibility: :direct) }
|
||||
let!(:status_reblog) { Fabricate(:status, account: account, reblog: Fabricate(:status)) }
|
||||
it_behaves_like 'unapproved account check'
|
||||
it_behaves_like 'permanently suspended account check'
|
||||
it_behaves_like 'temporarily suspended account check'
|
||||
end
|
||||
|
||||
before do
|
||||
status_media.media_attachments << Fabricate(:media_attachment, account: account, type: :image)
|
||||
account.pinned_statuses << status_pinned
|
||||
account.pinned_statuses << status_private
|
||||
end
|
||||
context 'with JSON' do
|
||||
let(:format) { 'json' }
|
||||
|
||||
shared_examples 'preliminary checks' do
|
||||
context 'when account is not approved' do
|
||||
before do
|
||||
account.user.update(approved: false)
|
||||
end
|
||||
it_behaves_like 'unapproved account check'
|
||||
it_behaves_like 'permanently suspended account check'
|
||||
it_behaves_like 'temporarily suspended account check', code: 200
|
||||
end
|
||||
|
||||
it 'returns http not found' do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
context 'with RSS' do
|
||||
let(:format) { 'rss' }
|
||||
|
||||
it_behaves_like 'unapproved account check'
|
||||
it_behaves_like 'permanently suspended account check'
|
||||
it_behaves_like 'temporarily suspended account check'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with HTML' do
|
||||
let(:format) { 'html' }
|
||||
|
||||
it_behaves_like 'preliminary checks'
|
||||
|
||||
context 'when account is permanently suspended' do
|
||||
before do
|
||||
account.suspend!
|
||||
account.deletion_request.destroy
|
||||
end
|
||||
|
||||
it 'returns http gone' do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
expect(response).to have_http_status(410)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when account is temporarily suspended' do
|
||||
before do
|
||||
account.suspend!
|
||||
end
|
||||
|
||||
it 'returns http forbidden' do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
expect(response).to have_http_status(403)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'common response characteristics' do
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns Link header' do
|
||||
expect(response.headers['Link'].to_s).to include ActivityPub::TagManager.instance.uri_for(account)
|
||||
end
|
||||
|
||||
it 'renders show template' do
|
||||
expect(response).to render_template(:show)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a normal account in an HTML request' do
|
||||
before do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it_behaves_like 'common response characteristics'
|
||||
end
|
||||
|
||||
context 'with replies' do
|
||||
before do
|
||||
allow(controller).to receive(:replies_requested?).and_return(true)
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it_behaves_like 'common response characteristics'
|
||||
end
|
||||
|
||||
context 'with media' do
|
||||
before do
|
||||
allow(controller).to receive(:media_requested?).and_return(true)
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it_behaves_like 'common response characteristics'
|
||||
end
|
||||
|
||||
context 'with tag' do
|
||||
let(:tag) { Fabricate(:tag) }
|
||||
|
||||
let!(:status_tag) { Fabricate(:status, account: account) }
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:tag_requested?).and_return(true)
|
||||
status_tag.tags << tag
|
||||
get :show, params: { username: account.username, format: format, tag: tag.to_param }
|
||||
end
|
||||
|
||||
it_behaves_like 'common response characteristics'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with JSON' do
|
||||
let(:authorized_fetch_mode) { false }
|
||||
let(:format) { 'json' }
|
||||
context 'with existing statuses' do
|
||||
let!(:status) { Fabricate(:status, account: account) }
|
||||
let!(:status_reply) { Fabricate(:status, account: account, thread: Fabricate(:status)) }
|
||||
let!(:status_self_reply) { Fabricate(:status, account: account, thread: status) }
|
||||
let!(:status_media) { Fabricate(:status, account: account) }
|
||||
let!(:status_pinned) { Fabricate(:status, account: account) }
|
||||
let!(:status_private) { Fabricate(:status, account: account, visibility: :private) }
|
||||
let!(:status_direct) { Fabricate(:status, account: account, visibility: :direct) }
|
||||
let!(:status_reblog) { Fabricate(:status, account: account, reblog: Fabricate(:status)) }
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:authorized_fetch_mode?).and_return(authorized_fetch_mode)
|
||||
status_media.media_attachments << Fabricate(:media_attachment, account: account, type: :image)
|
||||
account.pinned_statuses << status_pinned
|
||||
account.pinned_statuses << status_private
|
||||
end
|
||||
|
||||
it_behaves_like 'preliminary checks'
|
||||
context 'with HTML' do
|
||||
let(:format) { 'html' }
|
||||
|
||||
context 'when account is suspended permanently' do
|
||||
before do
|
||||
account.suspend!
|
||||
account.deletion_request.destroy
|
||||
shared_examples 'common HTML response' do
|
||||
it 'returns a standard HTML response', :aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
expect(response.headers['Link'].to_s).to include ActivityPub::TagManager.instance.uri_for(account)
|
||||
|
||||
expect(response).to render_template(:show)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns http gone' do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
expect(response).to have_http_status(410)
|
||||
context 'with a normal account in an HTML request' do
|
||||
before do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it_behaves_like 'common HTML response'
|
||||
end
|
||||
|
||||
context 'with replies' do
|
||||
before do
|
||||
allow(controller).to receive(:replies_requested?).and_return(true)
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it_behaves_like 'common HTML response'
|
||||
end
|
||||
|
||||
context 'with media' do
|
||||
before do
|
||||
allow(controller).to receive(:media_requested?).and_return(true)
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it_behaves_like 'common HTML response'
|
||||
end
|
||||
|
||||
context 'with tag' do
|
||||
let(:tag) { Fabricate(:tag) }
|
||||
|
||||
let!(:status_tag) { Fabricate(:status, account: account) }
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:tag_requested?).and_return(true)
|
||||
status_tag.tags << tag
|
||||
get :show, params: { username: account.username, format: format, tag: tag.to_param }
|
||||
end
|
||||
|
||||
it_behaves_like 'common HTML response'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when account is suspended temporarily' do
|
||||
context 'with JSON' do
|
||||
let(:authorized_fetch_mode) { false }
|
||||
let(:format) { 'json' }
|
||||
|
||||
before do
|
||||
account.suspend!
|
||||
allow(controller).to receive(:authorized_fetch_mode?).and_return(authorized_fetch_mode)
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
context 'with a normal account in a JSON request' do
|
||||
before do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
context 'with a normal account in a JSON request' do
|
||||
before do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
it 'returns a JSON version of the account', :aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
|
||||
expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||
end
|
||||
|
||||
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
|
||||
|
||||
context 'with authorized fetch mode' do
|
||||
let(:authorized_fetch_mode) { true }
|
||||
|
||||
it 'returns http unauthorized' do
|
||||
expect(response).to have_http_status(401)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
context 'when signed in' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it 'returns a private JSON version of the account', :aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
|
||||
expect(response.headers['Cache-Control']).to include 'private'
|
||||
|
||||
expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns application/activity+json' do
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
end
|
||||
context 'with signature' do
|
||||
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
|
||||
|
||||
it_behaves_like 'cacheable response'
|
||||
before do
|
||||
allow(controller).to receive(:signed_request_actor).and_return(remote_account)
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it 'renders account' do
|
||||
json = body_as_json
|
||||
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||
end
|
||||
it 'returns a JSON version of the account', :aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
context 'with authorized fetch mode' do
|
||||
let(:authorized_fetch_mode) { true }
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
|
||||
it 'returns http unauthorized' do
|
||||
expect(response).to have_http_status(401)
|
||||
expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||
end
|
||||
|
||||
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
|
||||
|
||||
context 'with authorized fetch mode' do
|
||||
let(:authorized_fetch_mode) { true }
|
||||
|
||||
it 'returns a private signature JSON version of the account', :aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
|
||||
expect(response.headers['Cache-Control']).to include 'private'
|
||||
|
||||
expect(response.headers['Vary']).to include 'Signature'
|
||||
|
||||
expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signed in' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns application/activity+json' do
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
end
|
||||
|
||||
it 'returns private Cache-Control header' do
|
||||
expect(response.headers['Cache-Control']).to include 'private'
|
||||
end
|
||||
|
||||
it 'renders account' do
|
||||
json = body_as_json
|
||||
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with signature' do
|
||||
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:signed_request_actor).and_return(remote_account)
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns application/activity+json' do
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
end
|
||||
|
||||
it_behaves_like 'cacheable response'
|
||||
|
||||
it 'renders account' do
|
||||
json = body_as_json
|
||||
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||
end
|
||||
|
||||
context 'with authorized fetch mode' do
|
||||
let(:authorized_fetch_mode) { true }
|
||||
context 'with RSS' do
|
||||
let(:format) { 'rss' }
|
||||
|
||||
shared_examples 'common RSS response' do
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns application/activity+json' do
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
|
||||
end
|
||||
|
||||
context 'with a normal account in an RSS request' do
|
||||
before do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it 'returns private Cache-Control header' do
|
||||
expect(response.headers['Cache-Control']).to include 'private'
|
||||
end
|
||||
it_behaves_like 'common RSS response'
|
||||
|
||||
it 'returns Vary header with Signature' do
|
||||
expect(response.headers['Vary']).to include 'Signature'
|
||||
end
|
||||
|
||||
it 'renders account' do
|
||||
json = body_as_json
|
||||
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||
it 'responds with correct statuses', :aggregate_failures do
|
||||
expect(response.body).to include_status_tag(status_media)
|
||||
expect(response.body).to include_status_tag(status_self_reply)
|
||||
expect(response.body).to include_status_tag(status)
|
||||
expect(response.body).to_not include_status_tag(status_direct)
|
||||
expect(response.body).to_not include_status_tag(status_private)
|
||||
expect(response.body).to_not include_status_tag(status_reblog.reblog)
|
||||
expect(response.body).to_not include_status_tag(status_reply)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with RSS' do
|
||||
let(:format) { 'rss' }
|
||||
context 'with replies' do
|
||||
before do
|
||||
allow(controller).to receive(:replies_requested?).and_return(true)
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it_behaves_like 'preliminary checks'
|
||||
it_behaves_like 'common RSS response'
|
||||
|
||||
context 'when account is permanently suspended' do
|
||||
before do
|
||||
account.suspend!
|
||||
account.deletion_request.destroy
|
||||
it 'responds with correct statuses with replies', :aggregate_failures do
|
||||
expect(response.body).to include_status_tag(status_media)
|
||||
expect(response.body).to include_status_tag(status_reply)
|
||||
expect(response.body).to include_status_tag(status_self_reply)
|
||||
expect(response.body).to include_status_tag(status)
|
||||
expect(response.body).to_not include_status_tag(status_direct)
|
||||
expect(response.body).to_not include_status_tag(status_private)
|
||||
expect(response.body).to_not include_status_tag(status_reblog.reblog)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns http gone' do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
expect(response).to have_http_status(410)
|
||||
end
|
||||
end
|
||||
context 'with media' do
|
||||
before do
|
||||
allow(controller).to receive(:media_requested?).and_return(true)
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
context 'when account is temporarily suspended' do
|
||||
before do
|
||||
account.suspend!
|
||||
it_behaves_like 'common RSS response'
|
||||
|
||||
it 'responds with correct statuses with media', :aggregate_failures do
|
||||
expect(response.body).to include_status_tag(status_media)
|
||||
expect(response.body).to_not include_status_tag(status_direct)
|
||||
expect(response.body).to_not include_status_tag(status_private)
|
||||
expect(response.body).to_not include_status_tag(status_reblog.reblog)
|
||||
expect(response.body).to_not include_status_tag(status_reply)
|
||||
expect(response.body).to_not include_status_tag(status_self_reply)
|
||||
expect(response.body).to_not include_status_tag(status)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns http forbidden' do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
expect(response).to have_http_status(403)
|
||||
end
|
||||
end
|
||||
context 'with tag' do
|
||||
let(:tag) { Fabricate(:tag) }
|
||||
|
||||
shared_examples 'common response characteristics' do
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
let!(:status_tag) { Fabricate(:status, account: account) }
|
||||
|
||||
it_behaves_like 'cacheable response'
|
||||
end
|
||||
before do
|
||||
allow(controller).to receive(:tag_requested?).and_return(true)
|
||||
status_tag.tags << tag
|
||||
get :show, params: { username: account.username, format: format, tag: tag.to_param }
|
||||
end
|
||||
|
||||
context 'with a normal account in an RSS request' do
|
||||
before do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
it_behaves_like 'common RSS response'
|
||||
|
||||
it_behaves_like 'common response characteristics'
|
||||
|
||||
it 'renders public status' do
|
||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
|
||||
end
|
||||
|
||||
it 'renders self-reply' do
|
||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||
end
|
||||
|
||||
it 'renders status with media' do
|
||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||
end
|
||||
|
||||
it 'does not render reblog' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||
end
|
||||
|
||||
it 'does not render private status' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||
end
|
||||
|
||||
it 'does not render direct status' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||
end
|
||||
|
||||
it 'does not render reply to someone else' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||
end
|
||||
end
|
||||
|
||||
context 'with replies' do
|
||||
before do
|
||||
allow(controller).to receive(:replies_requested?).and_return(true)
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it_behaves_like 'common response characteristics'
|
||||
|
||||
it 'renders public status' do
|
||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
|
||||
end
|
||||
|
||||
it 'renders self-reply' do
|
||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||
end
|
||||
|
||||
it 'renders status with media' do
|
||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||
end
|
||||
|
||||
it 'does not render reblog' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||
end
|
||||
|
||||
it 'does not render private status' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||
end
|
||||
|
||||
it 'does not render direct status' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||
end
|
||||
|
||||
it 'renders reply to someone else' do
|
||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||
end
|
||||
end
|
||||
|
||||
context 'with media' do
|
||||
before do
|
||||
allow(controller).to receive(:media_requested?).and_return(true)
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it_behaves_like 'common response characteristics'
|
||||
|
||||
it 'does not render public status' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
|
||||
end
|
||||
|
||||
it 'does not render self-reply' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||
end
|
||||
|
||||
it 'renders status with media' do
|
||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||
end
|
||||
|
||||
it 'does not render reblog' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||
end
|
||||
|
||||
it 'does not render private status' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||
end
|
||||
|
||||
it 'does not render direct status' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||
end
|
||||
|
||||
it 'does not render reply to someone else' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||
end
|
||||
end
|
||||
|
||||
context 'with tag' do
|
||||
let(:tag) { Fabricate(:tag) }
|
||||
|
||||
let!(:status_tag) { Fabricate(:status, account: account) }
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:tag_requested?).and_return(true)
|
||||
status_tag.tags << tag
|
||||
get :show, params: { username: account.username, format: format, tag: tag.to_param }
|
||||
end
|
||||
|
||||
it_behaves_like 'common response characteristics'
|
||||
|
||||
it 'does not render public status' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
|
||||
end
|
||||
|
||||
it 'does not render self-reply' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
||||
end
|
||||
|
||||
it 'does not render status with media' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media))
|
||||
end
|
||||
|
||||
it 'does not render reblog' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
||||
end
|
||||
|
||||
it 'does not render private status' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
||||
end
|
||||
|
||||
it 'does not render direct status' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
||||
end
|
||||
|
||||
it 'does not render reply to someone else' do
|
||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
||||
end
|
||||
|
||||
it 'renders status with tag' do
|
||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_tag))
|
||||
it 'responds with correct statuses with a tag', :aggregate_failures do
|
||||
expect(response.body).to include_status_tag(status_tag)
|
||||
expect(response.body).to_not include_status_tag(status_direct)
|
||||
expect(response.body).to_not include_status_tag(status_media)
|
||||
expect(response.body).to_not include_status_tag(status_private)
|
||||
expect(response.body).to_not include_status_tag(status_reblog.reblog)
|
||||
expect(response.body).to_not include_status_tag(status_reply)
|
||||
expect(response.body).to_not include_status_tag(status_self_reply)
|
||||
expect(response.body).to_not include_status_tag(status)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def include_status_tag(status)
|
||||
include ActivityPub::TagManager.instance.url_for(status)
|
||||
end
|
||||
end
|
||||
|
@ -7,22 +7,6 @@ RSpec.describe ActivityPub::CollectionsController do
|
||||
let!(:private_pinned) { Fabricate(:status, account: account, text: 'secret private stuff', visibility: :private) }
|
||||
let(:remote_account) { nil }
|
||||
|
||||
shared_examples 'cacheable response' do
|
||||
it 'does not set cookies' do
|
||||
expect(response.cookies).to be_empty
|
||||
expect(response.headers['Set-Cookies']).to be_nil
|
||||
end
|
||||
|
||||
it 'does not set sessions' do
|
||||
response
|
||||
expect(session).to be_empty
|
||||
end
|
||||
|
||||
it 'returns public Cache-Control header' do
|
||||
expect(response.headers['Cache-Control']).to include 'public'
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:signed_request_actor).and_return(remote_account)
|
||||
|
||||
|
@ -5,22 +5,6 @@ require 'rails_helper'
|
||||
RSpec.describe ActivityPub::OutboxesController do
|
||||
let!(:account) { Fabricate(:account) }
|
||||
|
||||
shared_examples 'cacheable response' do
|
||||
it 'does not set cookies' do
|
||||
expect(response.cookies).to be_empty
|
||||
expect(response.headers['Set-Cookies']).to be_nil
|
||||
end
|
||||
|
||||
it 'does not set sessions' do
|
||||
response
|
||||
expect(session).to be_empty
|
||||
end
|
||||
|
||||
it 'returns public Cache-Control header' do
|
||||
expect(response.headers['Cache-Control']).to include 'public'
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
Fabricate(:status, account: account, visibility: :public)
|
||||
Fabricate(:status, account: account, visibility: :unlisted)
|
||||
|
@ -8,22 +8,6 @@ RSpec.describe ActivityPub::RepliesController do
|
||||
let(:remote_reply_id) { 'https://foobar.com/statuses/1234' }
|
||||
let(:remote_querier) { nil }
|
||||
|
||||
shared_examples 'cacheable response' do
|
||||
it 'does not set cookies' do
|
||||
expect(response.cookies).to be_empty
|
||||
expect(response.headers['Set-Cookies']).to be_nil
|
||||
end
|
||||
|
||||
it 'does not set sessions' do
|
||||
response
|
||||
expect(session).to be_empty
|
||||
end
|
||||
|
||||
it 'returns public Cache-Control header' do
|
||||
expect(response.headers['Cache-Control']).to include 'public'
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'common behavior' do
|
||||
context 'when status is private' do
|
||||
let(:parent_visibility) { :private }
|
||||
|
@ -15,6 +15,16 @@ RSpec.describe Admin::Disputes::AppealsController do
|
||||
let(:strike) { Fabricate(:account_warning, target_account: target_account, action: :suspend) }
|
||||
let(:appeal) { Fabricate(:appeal, strike: strike, account: target_account) }
|
||||
|
||||
describe 'GET #index' do
|
||||
let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
|
||||
|
||||
it 'lists appeals' do
|
||||
get :index
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #approve' do
|
||||
let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
|
||||
|
||||
|
@ -165,6 +165,17 @@ RSpec.describe Admin::DomainBlocksController do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #edit' do
|
||||
let(:domain_block) { Fabricate(:domain_block) }
|
||||
|
||||
it 'returns http success' do
|
||||
get :edit, params: { id: domain_block.id }
|
||||
|
||||
expect(assigns(:domain_block)).to be_instance_of(DomainBlock)
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT #update' do
|
||||
subject do
|
||||
post :update, params: { :id => domain_block.id, :domain_block => { domain: 'example.com', severity: new_severity }, 'confirm' => '' }
|
||||
|
@ -9,6 +9,14 @@ RSpec.describe Admin::ExportDomainAllowsController do
|
||||
sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
|
||||
end
|
||||
|
||||
describe 'GET #new' do
|
||||
it 'returns http success' do
|
||||
get :new
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #export' do
|
||||
it 'renders instances' do
|
||||
Fabricate(:domain_allow, domain: 'good.domain')
|
||||
|
@ -9,6 +9,14 @@ RSpec.describe Admin::ExportDomainBlocksController do
|
||||
sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
|
||||
end
|
||||
|
||||
describe 'GET #new' do
|
||||
it 'returns http success' do
|
||||
get :new
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #export' do
|
||||
it 'renders instances' do
|
||||
Fabricate(:domain_block, domain: 'bad.domain', severity: 'silence', public_comment: 'bad server')
|
||||
|
@ -34,6 +34,63 @@ RSpec.describe Admin::InstancesController do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
it 'shows an instance page' do
|
||||
get :show, params: { id: account_popular_main.domain }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #clear_delivery_errors' do
|
||||
let(:tracker) { instance_double(DeliveryFailureTracker, clear_failures!: true) }
|
||||
|
||||
before { allow(DeliveryFailureTracker).to receive(:new).and_return(tracker) }
|
||||
|
||||
it 'clears instance delivery errors' do
|
||||
post :clear_delivery_errors, params: { id: account_popular_main.domain }
|
||||
|
||||
expect(response).to redirect_to(admin_instance_path(account_popular_main.domain))
|
||||
expect(tracker).to have_received(:clear_failures!)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #restart_delivery' do
|
||||
let(:tracker) { instance_double(DeliveryFailureTracker, track_success!: true) }
|
||||
|
||||
before { allow(DeliveryFailureTracker).to receive(:new).and_return(tracker) }
|
||||
|
||||
context 'with an unavailable instance' do
|
||||
before { Fabricate(:unavailable_domain, domain: account_popular_main.domain) }
|
||||
|
||||
it 'tracks success on the instance' do
|
||||
post :restart_delivery, params: { id: account_popular_main.domain }
|
||||
|
||||
expect(response).to redirect_to(admin_instance_path(account_popular_main.domain))
|
||||
expect(tracker).to have_received(:track_success!)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an available instance' do
|
||||
it 'does not track success on the instance' do
|
||||
post :restart_delivery, params: { id: account_popular_main.domain }
|
||||
|
||||
expect(response).to redirect_to(admin_instance_path(account_popular_main.domain))
|
||||
expect(tracker).to_not have_received(:track_success!)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #stop_delivery' do
|
||||
it 'clears instance delivery errors' do
|
||||
expect do
|
||||
post :stop_delivery, params: { id: account_popular_main.domain }
|
||||
end.to change(UnavailableDomain, :count).by(1)
|
||||
|
||||
expect(response).to redirect_to(admin_instance_path(account_popular_main.domain))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE #destroy' do
|
||||
subject { delete :destroy, params: { id: Instance.first.id } }
|
||||
|
||||
|
@ -18,4 +18,12 @@ describe Admin::Settings::AboutController do
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT #update' do
|
||||
it 'updates the settings' do
|
||||
put :update, params: { form_admin_settings: { site_extended_description: 'new site description' } }
|
||||
|
||||
expect(response).to redirect_to(admin_settings_about_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -18,4 +18,12 @@ describe Admin::Settings::AppearanceController do
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT #update' do
|
||||
it 'updates the settings' do
|
||||
put :update, params: { form_admin_settings: { custom_css: 'html { display: inline; }' } }
|
||||
|
||||
expect(response).to redirect_to(admin_settings_appearance_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -18,4 +18,12 @@ describe Admin::Settings::ContentRetentionController do
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT #update' do
|
||||
it 'updates the settings' do
|
||||
put :update, params: { form_admin_settings: { media_cache_retention_period: '2' } }
|
||||
|
||||
expect(response).to redirect_to(admin_settings_content_retention_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -18,4 +18,12 @@ describe Admin::Settings::DiscoveryController do
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT #update' do
|
||||
it 'updates the settings' do
|
||||
put :update, params: { form_admin_settings: { trends: '1' } }
|
||||
|
||||
expect(response).to redirect_to(admin_settings_discovery_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -18,4 +18,12 @@ describe Admin::Settings::RegistrationsController do
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT #update' do
|
||||
it 'updates the settings' do
|
||||
put :update, params: { form_admin_settings: { registrations_mode: 'open' } }
|
||||
|
||||
expect(response).to redirect_to(admin_settings_registrations_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -20,4 +20,26 @@ RSpec.describe Admin::TagsController do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT #update' do
|
||||
let!(:tag) { Fabricate(:tag, listable: false) }
|
||||
|
||||
context 'with valid params' do
|
||||
it 'updates the tag' do
|
||||
put :update, params: { id: tag.id, tag: { listable: '1' } }
|
||||
|
||||
expect(response).to redirect_to(admin_tag_path(tag.id))
|
||||
expect(tag.reload).to be_listable
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid params' do
|
||||
it 'does not update the tag' do
|
||||
put :update, params: { id: tag.id, tag: { name: 'cant-change-name' } }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response).to render_template(:show)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -86,6 +86,24 @@ describe Admin::WebhooksController do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #enable' do
|
||||
it 'enables the webhook' do
|
||||
post :enable, params: { id: webhook.id }
|
||||
|
||||
expect(webhook.reload).to be_enabled
|
||||
expect(response).to redirect_to(admin_webhook_path(webhook))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #disable' do
|
||||
it 'disables the webhook' do
|
||||
post :disable, params: { id: webhook.id }
|
||||
|
||||
expect(webhook.reload).to_not be_enabled
|
||||
expect(response).to redirect_to(admin_webhook_path(webhook))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE #destroy' do
|
||||
it 'destroys the record' do
|
||||
expect do
|
||||
|
@ -1,137 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Api::V1::NotificationsController do
|
||||
render_views
|
||||
|
||||
let(:user) { Fabricate(:user, account_attributes: { username: 'alice' }) }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
|
||||
let(:other) { Fabricate(:user) }
|
||||
let(:third) { Fabricate(:user) }
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:doorkeeper_token) { token }
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
let(:scopes) { 'read:notifications' }
|
||||
|
||||
it 'returns http success' do
|
||||
notification = Fabricate(:notification, account: user.account)
|
||||
get :show, params: { id: notification.id }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #dismiss' do
|
||||
let(:scopes) { 'write:notifications' }
|
||||
|
||||
it 'destroys the notification' do
|
||||
notification = Fabricate(:notification, account: user.account)
|
||||
post :dismiss, params: { id: notification.id }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect { notification.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #clear' do
|
||||
let(:scopes) { 'write:notifications' }
|
||||
|
||||
it 'clears notifications for the account' do
|
||||
notification = Fabricate(:notification, account: user.account)
|
||||
post :clear
|
||||
|
||||
expect(notification.account.reload.notifications).to be_empty
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #index' do
|
||||
let(:scopes) { 'read:notifications' }
|
||||
|
||||
before do
|
||||
first_status = PostStatusService.new.call(user.account, text: 'Test')
|
||||
@reblog_of_first_status = ReblogService.new.call(other.account, first_status)
|
||||
mentioning_status = PostStatusService.new.call(other.account, text: 'Hello @alice')
|
||||
@mention_from_status = mentioning_status.mentions.first
|
||||
@favourite = FavouriteService.new.call(other.account, first_status)
|
||||
@second_favourite = FavouriteService.new.call(third.account, first_status)
|
||||
@follow = FollowService.new.call(other.account, user.account)
|
||||
end
|
||||
|
||||
describe 'with no options' do
|
||||
before do
|
||||
get :index
|
||||
end
|
||||
|
||||
it 'returns expected notification types', :aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
expect(body_json_types).to include 'reblog'
|
||||
expect(body_json_types).to include 'mention'
|
||||
expect(body_json_types).to include 'favourite'
|
||||
expect(body_json_types).to include 'follow'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with account_id param' do
|
||||
before do
|
||||
get :index, params: { account_id: third.account.id }
|
||||
end
|
||||
|
||||
it 'returns only notifications from specified user', :aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
expect(body_json_account_ids.uniq).to eq [third.account.id.to_s]
|
||||
end
|
||||
|
||||
def body_json_account_ids
|
||||
body_as_json.map { |x| x[:account][:id] }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with invalid account_id param' do
|
||||
before do
|
||||
get :index, params: { account_id: 'foo' }
|
||||
end
|
||||
|
||||
it 'returns nothing', :aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
expect(body_as_json.size).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with exclude_types param' do
|
||||
before do
|
||||
get :index, params: { exclude_types: %w(mention) }
|
||||
end
|
||||
|
||||
it 'returns everything but excluded type', :aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
expect(body_as_json.size).to_not eq 0
|
||||
expect(body_json_types.uniq).to_not include 'mention'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with types param' do
|
||||
before do
|
||||
get :index, params: { types: %w(mention) }
|
||||
end
|
||||
|
||||
it 'returns only requested type', :aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
expect(body_json_types.uniq).to eq ['mention']
|
||||
end
|
||||
end
|
||||
|
||||
def body_json_types
|
||||
body_as_json.pluck(:type)
|
||||
end
|
||||
end
|
||||
end
|
@ -252,6 +252,19 @@ RSpec.describe Settings::ImportsController do
|
||||
|
||||
include_examples 'export failed rows', "https://foo.com/1\nhttps://foo.com/2\n"
|
||||
end
|
||||
|
||||
context 'with lists' do
|
||||
let(:import_type) { 'lists' }
|
||||
|
||||
let!(:rows) do
|
||||
[
|
||||
{ 'list_name' => 'Amigos', 'acct' => 'user@example.com' },
|
||||
{ 'list_name' => 'Frenemies', 'acct' => 'user@org.org' },
|
||||
].map { |data| Fabricate(:bulk_import_row, bulk_import: bulk_import, data: data) }
|
||||
end
|
||||
|
||||
include_examples 'export failed rows', "Amigos,user@example.com\nFrenemies,user@org.org\n"
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #create' do
|
||||
|
@ -5,25 +5,6 @@ require 'rails_helper'
|
||||
describe StatusesController do
|
||||
render_views
|
||||
|
||||
shared_examples 'cacheable response' do
|
||||
it 'does not set cookies' do
|
||||
expect(response.cookies).to be_empty
|
||||
expect(response.headers['Set-Cookies']).to be_nil
|
||||
end
|
||||
|
||||
it 'does not set sessions' do
|
||||
expect(session).to be_empty
|
||||
end
|
||||
|
||||
it 'returns Vary header' do
|
||||
expect(response.headers['Vary']).to include 'Accept, Accept-Language, Cookie'
|
||||
end
|
||||
|
||||
it 'returns public Cache-Control header' do
|
||||
expect(response.headers['Cache-Control']).to include 'public'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
let(:account) { Fabricate(:account) }
|
||||
let(:status) { Fabricate(:status, account: account) }
|
||||
@ -88,7 +69,7 @@ describe StatusesController do
|
||||
context 'with JSON' do
|
||||
let(:format) { 'json' }
|
||||
|
||||
it_behaves_like 'cacheable response'
|
||||
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
|
||||
|
||||
it 'renders ActivityPub Note object successfully', :aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
@ -371,7 +352,7 @@ describe StatusesController do
|
||||
context 'with JSON' do
|
||||
let(:format) { 'json' }
|
||||
|
||||
it_behaves_like 'cacheable response'
|
||||
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
|
||||
|
||||
it 'renders ActivityPub Note object successfully', :aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
|
@ -4,9 +4,78 @@ require 'rails_helper'
|
||||
require 'mastodon/cli/media'
|
||||
|
||||
describe Mastodon::CLI::Media do
|
||||
let(:cli) { described_class.new }
|
||||
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe '#remove' do
|
||||
context 'with --prune-profiles and --remove-headers' do
|
||||
let(:options) { { prune_profiles: true, remove_headers: true } }
|
||||
|
||||
it 'warns about usage and exits' do
|
||||
expect { cli.invoke(:remove, [], options) }.to output(
|
||||
a_string_including('--prune-profiles and --remove-headers should not be specified simultaneously')
|
||||
).to_stdout.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --include-follows but not including --prune-profiles and --remove-headers' do
|
||||
let(:options) { { include_follows: true } }
|
||||
|
||||
it 'warns about usage and exits' do
|
||||
expect { cli.invoke(:remove, [], options) }.to output(
|
||||
a_string_including('--include-follows can only be used with --prune-profiles or --remove-headers')
|
||||
).to_stdout.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a relevant account' do
|
||||
let!(:account) do
|
||||
Fabricate(:account, domain: 'example.com', updated_at: 1.month.ago, last_webfingered_at: 1.month.ago, avatar: attachment_fixture('attachment.jpg'), header: attachment_fixture('attachment.jpg'))
|
||||
end
|
||||
|
||||
context 'with --prune-profiles' do
|
||||
let(:options) { { prune_profiles: true } }
|
||||
|
||||
it 'removes account avatars' do
|
||||
expect { cli.invoke(:remove, [], options) }.to output(
|
||||
a_string_including('Visited 1')
|
||||
).to_stdout
|
||||
|
||||
expect(account.reload.avatar).to be_blank
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --remove-headers' do
|
||||
let(:options) { { remove_headers: true } }
|
||||
|
||||
it 'removes account header' do
|
||||
expect { cli.invoke(:remove, [], options) }.to output(
|
||||
a_string_including('Visited 1')
|
||||
).to_stdout
|
||||
|
||||
expect(account.reload.header).to be_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a relevant media attachment' do
|
||||
let!(:media_attachment) { Fabricate(:media_attachment, remote_url: 'https://example.com/image.jpg', created_at: 1.month.ago) }
|
||||
|
||||
context 'without options' do
|
||||
it 'removes account avatars' do
|
||||
expect { cli.invoke(:remove) }.to output(
|
||||
a_string_including('Removed 1')
|
||||
).to_stdout
|
||||
|
||||
expect(media_attachment.reload.file).to be_blank
|
||||
expect(media_attachment.reload.thumbnail).to be_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -4,9 +4,52 @@ require 'rails_helper'
|
||||
require 'mastodon/cli/preview_cards'
|
||||
|
||||
describe Mastodon::CLI::PreviewCards do
|
||||
let(:cli) { described_class.new }
|
||||
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe '#remove' do
|
||||
context 'with relevant preview cards' do
|
||||
before do
|
||||
Fabricate(:preview_card, updated_at: 10.years.ago, type: :link)
|
||||
Fabricate(:preview_card, updated_at: 10.months.ago, type: :photo)
|
||||
Fabricate(:preview_card, updated_at: 10.days.ago, type: :photo)
|
||||
end
|
||||
|
||||
context 'with no arguments' do
|
||||
it 'deletes thumbnails for local preview cards' do
|
||||
expect { cli.invoke(:remove) }.to output(
|
||||
a_string_including('Removed 2 preview cards')
|
||||
.and(a_string_including('approx. 119 KB'))
|
||||
).to_stdout
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the --link option' do
|
||||
let(:options) { { link: true } }
|
||||
|
||||
it 'deletes thumbnails for local preview cards' do
|
||||
expect { cli.invoke(:remove, [], options) }.to output(
|
||||
a_string_including('Removed 1 link-type preview cards')
|
||||
.and(a_string_including('approx. 59.6 KB'))
|
||||
).to_stdout
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the --days option' do
|
||||
let(:options) { { days: 365 } }
|
||||
|
||||
it 'deletes thumbnails for local preview cards' do
|
||||
expect { cli.invoke(:remove, [], options) }.to output(
|
||||
a_string_including('Removed 1 preview cards')
|
||||
.and(a_string_including('approx. 59.6 KB'))
|
||||
).to_stdout
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -29,4 +29,23 @@ describe Poll do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
context 'when valid' do
|
||||
let(:poll) { Fabricate.build(:poll) }
|
||||
|
||||
it 'is valid with valid attributes' do
|
||||
expect(poll).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not valid' do
|
||||
let(:poll) { Fabricate.build(:poll, expires_at: nil) }
|
||||
|
||||
it 'is invalid without an expire date' do
|
||||
poll.valid?
|
||||
expect(poll).to model_have_error_on_field(:expires_at)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
183
spec/requests/api/v1/notifications_spec.rb
Normal file
183
spec/requests/api/v1/notifications_spec.rb
Normal file
@ -0,0 +1,183 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Notifications' do
|
||||
let(:user) { Fabricate(:user, account_attributes: { username: 'alice' }) }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
|
||||
let(:scopes) { 'read:notifications write:notifications' }
|
||||
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
|
||||
|
||||
describe 'GET /api/v1/notifications' do
|
||||
subject do
|
||||
get '/api/v1/notifications', headers: headers, params: params
|
||||
end
|
||||
|
||||
let(:bob) { Fabricate(:user) }
|
||||
let(:tom) { Fabricate(:user) }
|
||||
let(:params) { {} }
|
||||
|
||||
before do
|
||||
first_status = PostStatusService.new.call(user.account, text: 'Test')
|
||||
ReblogService.new.call(bob.account, first_status)
|
||||
mentioning_status = PostStatusService.new.call(bob.account, text: 'Hello @alice')
|
||||
mentioning_status.mentions.first
|
||||
FavouriteService.new.call(bob.account, first_status)
|
||||
FavouriteService.new.call(tom.account, first_status)
|
||||
FollowService.new.call(bob.account, user.account)
|
||||
end
|
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'write write:notifications'
|
||||
|
||||
context 'with no options' do
|
||||
it 'returns expected notification types', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(body_json_types).to include 'reblog'
|
||||
expect(body_json_types).to include 'mention'
|
||||
expect(body_json_types).to include 'favourite'
|
||||
expect(body_json_types).to include 'follow'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with account_id param' do
|
||||
let(:params) { { account_id: tom.account.id } }
|
||||
|
||||
it 'returns only notifications from specified user', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(body_json_account_ids.uniq).to eq [tom.account.id.to_s]
|
||||
end
|
||||
|
||||
def body_json_account_ids
|
||||
body_as_json.map { |x| x[:account][:id] }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid account_id param' do
|
||||
let(:params) { { account_id: 'foo' } }
|
||||
|
||||
it 'returns nothing', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(body_as_json.size).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'with exclude_types param' do
|
||||
let(:params) { { exclude_types: %w(mention) } }
|
||||
|
||||
it 'returns everything but excluded type', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(body_as_json.size).to_not eq 0
|
||||
expect(body_json_types.uniq).to_not include 'mention'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with types param' do
|
||||
let(:params) { { types: %w(mention) } }
|
||||
|
||||
it 'returns only requested type', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(body_json_types.uniq).to eq ['mention']
|
||||
end
|
||||
end
|
||||
|
||||
context 'with limit param' do
|
||||
let(:params) { { limit: 3 } }
|
||||
|
||||
it 'returns the requested number of notifications paginated', :aggregate_failures do
|
||||
subject
|
||||
|
||||
notifications = user.account.notifications
|
||||
|
||||
expect(body_as_json.size).to eq(params[:limit])
|
||||
expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_notifications_url(limit: params[:limit], min_id: notifications.last.id.to_s))
|
||||
expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_notifications_url(limit: params[:limit], max_id: notifications[2].id.to_s))
|
||||
end
|
||||
end
|
||||
|
||||
def body_json_types
|
||||
body_as_json.pluck(:type)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v1/notifications/:id' do
|
||||
subject do
|
||||
get "/api/v1/notifications/#{notification.id}", headers: headers
|
||||
end
|
||||
|
||||
let(:notification) { Fabricate(:notification, account: user.account) }
|
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'write write:notifications'
|
||||
|
||||
it 'returns http success' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
context 'when notification belongs to someone else' do
|
||||
let(:notification) { Fabricate(:notification) }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v1/notifications/:id/dismiss' do
|
||||
subject do
|
||||
post "/api/v1/notifications/#{notification.id}/dismiss", headers: headers
|
||||
end
|
||||
|
||||
let!(:notification) { Fabricate(:notification, account: user.account) }
|
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'read read:notifications'
|
||||
|
||||
it 'destroys the notification' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect { notification.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
context 'when notification belongs to someone else' do
|
||||
let(:notification) { Fabricate(:notification) }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v1/notifications/clear' do
|
||||
subject do
|
||||
post '/api/v1/notifications/clear', headers: headers
|
||||
end
|
||||
|
||||
before do
|
||||
Fabricate.times(3, :notification, account: user.account)
|
||||
end
|
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'read read:notifications'
|
||||
|
||||
it 'clears notifications for the account' do
|
||||
subject
|
||||
|
||||
expect(user.account.reload.notifications).to be_empty
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
22
spec/support/examples/cache.rb
Normal file
22
spec/support/examples/cache.rb
Normal file
@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
shared_examples 'cacheable response' do |expects_vary: false|
|
||||
it 'does not set cookies' do
|
||||
expect(response.cookies).to be_empty
|
||||
expect(response.headers['Set-Cookies']).to be_nil
|
||||
end
|
||||
|
||||
it 'does not set sessions' do
|
||||
expect(session).to be_empty
|
||||
end
|
||||
|
||||
if expects_vary
|
||||
it 'returns Vary header' do
|
||||
expect(response.headers['Vary']).to include(expects_vary)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns public Cache-Control header' do
|
||||
expect(response.headers['Cache-Control']).to include('public')
|
||||
end
|
||||
end
|
@ -2,41 +2,118 @@
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe UnreservedUsernameValidator, type: :validator do
|
||||
describe '#validate' do
|
||||
before do
|
||||
allow(validator).to receive(:reserved_username?) { reserved_username }
|
||||
validator.validate(account)
|
||||
describe UnreservedUsernameValidator do
|
||||
let(:record_class) do
|
||||
Class.new do
|
||||
include ActiveModel::Validations
|
||||
attr_accessor :username
|
||||
|
||||
validates_with UnreservedUsernameValidator
|
||||
end
|
||||
end
|
||||
let(:record) { record_class.new }
|
||||
|
||||
let(:validator) { described_class.new }
|
||||
let(:account) { instance_double(Account, username: username, errors: errors) }
|
||||
let(:errors) { instance_double(ActiveModel::Errors, add: nil) }
|
||||
describe '#validate' do
|
||||
context 'when username is nil' do
|
||||
it 'does not add errors' do
|
||||
record.username = nil
|
||||
|
||||
context 'when @username is blank?' do
|
||||
let(:username) { nil }
|
||||
|
||||
it 'not calls errors.add' do
|
||||
expect(errors).to_not have_received(:add).with(:username, any_args)
|
||||
expect(record).to be_valid
|
||||
expect(record.errors).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when @username is not blank?' do
|
||||
let(:username) { 'f' }
|
||||
context 'when PAM is enabled' do
|
||||
before do
|
||||
allow(Devise).to receive(:pam_authentication).and_return(true)
|
||||
end
|
||||
|
||||
context 'with reserved_username?' do
|
||||
let(:reserved_username) { true }
|
||||
context 'with a pam service available' do
|
||||
let(:service) { double }
|
||||
let(:pam_class) do
|
||||
Class.new do
|
||||
def self.account(service, username); end
|
||||
end
|
||||
end
|
||||
|
||||
it 'calls errors.add' do
|
||||
expect(errors).to have_received(:add).with(:username, :reserved)
|
||||
before do
|
||||
stub_const('Rpam2', pam_class)
|
||||
allow(Devise).to receive(:pam_controlled_service).and_return(service)
|
||||
end
|
||||
|
||||
context 'when the account exists' do
|
||||
before do
|
||||
allow(Rpam2).to receive(:account).with(service, 'username').and_return(true)
|
||||
end
|
||||
|
||||
it 'adds errors to the record' do
|
||||
record.username = 'username'
|
||||
|
||||
expect(record).to_not be_valid
|
||||
expect(record.errors.first.attribute).to eq(:username)
|
||||
expect(record.errors.first.type).to eq(:reserved)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the account does not exist' do
|
||||
before do
|
||||
allow(Rpam2).to receive(:account).with(service, 'username').and_return(false)
|
||||
end
|
||||
|
||||
it 'does not add errors to the record' do
|
||||
record.username = 'username'
|
||||
|
||||
expect(record).to be_valid
|
||||
expect(record.errors).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when username is not reserved' do
|
||||
let(:reserved_username) { false }
|
||||
context 'without a pam service' do
|
||||
before do
|
||||
allow(Devise).to receive(:pam_controlled_service).and_return(false)
|
||||
end
|
||||
|
||||
it 'not calls errors.add' do
|
||||
expect(errors).to_not have_received(:add).with(:username, any_args)
|
||||
context 'when there are not any reserved usernames' do
|
||||
before do
|
||||
stub_reserved_usernames(nil)
|
||||
end
|
||||
|
||||
it 'does not add errors to the record' do
|
||||
record.username = 'username'
|
||||
|
||||
expect(record).to be_valid
|
||||
expect(record.errors).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are reserved usernames' do
|
||||
before do
|
||||
stub_reserved_usernames(%w(alice bob))
|
||||
end
|
||||
|
||||
context 'when the username is reserved' do
|
||||
it 'adds errors to the record' do
|
||||
record.username = 'alice'
|
||||
|
||||
expect(record).to_not be_valid
|
||||
expect(record.errors.first.attribute).to eq(:username)
|
||||
expect(record.errors.first.type).to eq(:reserved)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the username is not reserved' do
|
||||
it 'does not add errors to the record' do
|
||||
record.username = 'chris'
|
||||
|
||||
expect(record).to be_valid
|
||||
expect(record.errors).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def stub_reserved_usernames(value)
|
||||
allow(Setting).to receive(:[]).with('reserved_usernames').and_return(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user