Merge commit '25c66fa640' into kb_migration

This commit is contained in:
KMY 2023-06-13 18:34:49 +09:00
commit 1f64358afb
107 changed files with 2159 additions and 1540 deletions

View file

@ -394,7 +394,7 @@ RSpec/MessageSpies:
- 'spec/validators/status_length_validator_spec.rb' - 'spec/validators/status_length_validator_spec.rb'
RSpec/MultipleExpectations: RSpec/MultipleExpectations:
Max: 19 Max: 8
# Configuration parameters: AllowSubject. # Configuration parameters: AllowSubject.
RSpec/MultipleMemoizedHelpers: RSpec/MultipleMemoizedHelpers:
@ -1242,11 +1242,6 @@ Style/GlobalStdStream:
- 'config/environments/development.rb' - 'config/environments/development.rb'
- 'config/environments/production.rb' - 'config/environments/production.rb'
# Configuration parameters: AllowedVariables.
Style/GlobalVars:
Exclude:
- 'config/initializers/statsd.rb'
# This cop supports safe autocorrection (--autocorrect). # This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals.
Style/GuardClause: Style/GuardClause:
@ -1380,7 +1375,6 @@ Style/RedundantConstantBase:
Exclude: Exclude:
- 'config/environments/production.rb' - 'config/environments/production.rb'
- 'config/initializers/sidekiq.rb' - 'config/initializers/sidekiq.rb'
- 'config/initializers/statsd.rb'
- 'config/locales/sr-Latn.rb' - 'config/locales/sr-Latn.rb'
- 'config/locales/sr.rb' - 'config/locales/sr.rb'

View file

@ -887,4 +887,4 @@ RUBY VERSION
ruby 3.2.2p53 ruby 3.2.2p53
BUNDLED WITH BUNDLED WITH
2.4.10 2.4.13

View file

@ -14,15 +14,5 @@ module Admin
@pending_tags_count = Tag.pending_review.count @pending_tags_count = Tag.pending_review.count
@pending_appeals_count = Appeal.pending.count @pending_appeals_count = Appeal.pending.count
end end
private
def redis_info
@redis_info ||= if redis.is_a?(Redis::Namespace)
redis.redis.info
else
redis.info
end
end
end end
end end

View file

@ -90,7 +90,7 @@ class Api::V1::AccountsController < Api::BaseController
end end
def account_params def account_params
params.permit(:username, :email, :password, :agreement, :locale, :reason) params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone)
end end
def check_enabled_registrations def check_enabled_registrations

View file

@ -0,0 +1,41 @@
# frozen_string_literal: true
class MailSubscriptionsController < ApplicationController
layout 'auth'
skip_before_action :require_functional!
before_action :set_body_classes
before_action :set_user
before_action :set_type
def show; end
def create
@user.settings[email_type_from_param] = false
@user.save!
end
private
def set_user
@user = GlobalID::Locator.locate_signed(params[:token], for: 'unsubscribe')
end
def set_body_classes
@body_classes = 'lighter'
end
def set_type
@type = email_type_from_param
end
def email_type_from_param
case params[:type]
when 'follow', 'reblog', 'favourite', 'mention', 'follow_request'
"notification_emails.#{params[:type]}"
else
raise ArgumentError
end
end
end

View file

@ -19,6 +19,6 @@ class Settings::Preferences::BaseController < Settings::BaseController
end end
def user_params def user_params
params.require(:user).permit(:locale, chosen_languages: [], settings_attributes: UserSettings.keys) params.require(:user).permit(:locale, :time_zone, chosen_languages: [], settings_attributes: UserSettings.keys)
end end
end end

View file

@ -1,23 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { FormattedMessage } from 'react-intl';
export default class LoadPending extends PureComponent {
static propTypes = {
onClick: PropTypes.func,
count: PropTypes.number,
};
render() {
const { count } = this.props;
return (
<button className='load-more load-gap' onClick={this.props.onClick}>
<FormattedMessage id='load_pending' defaultMessage='{count, plural, one {# new item} other {# new items}}' values={{ count }} />
</button>
);
}
}

View file

@ -0,0 +1,18 @@
import { FormattedMessage } from 'react-intl';
interface Props {
onClick: (event: React.MouseEvent) => void;
count: number;
}
export const LoadPending: React.FC<Props> = ({ onClick, count }) => {
return (
<button className='load-more load-gap' onClick={onClick}>
<FormattedMessage
id='load_pending'
defaultMessage='{count, plural, one {# new item} other {# new items}}'
values={{ count }}
/>
</button>
);
};

View file

@ -16,7 +16,7 @@ import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper'; import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
import { LoadMore } from './load_more'; import { LoadMore } from './load_more';
import LoadPending from './load_pending'; import { LoadPending } from './load_pending';
import LoadingIndicator from './loading_indicator'; import LoadingIndicator from './loading_indicator';
const MOUSE_IDLE_DELAY = 300; const MOUSE_IDLE_DELAY = 300;

View file

@ -377,7 +377,7 @@ class Header extends ImmutablePureComponent {
let badge; let badge;
if (account.get('bot')) { if (account.get('bot')) {
badge = (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div>); badge = (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Automated' /></div>);
} else if (account.get('group')) { } else if (account.get('group')) {
badge = (<div className='account-role group'><FormattedMessage id='account.badges.group' defaultMessage='Group' /></div>); badge = (<div className='account-role group'><FormattedMessage id='account.badges.group' defaultMessage='Group' /></div>);
} else { } else {

View file

@ -15,7 +15,7 @@
"about.rules": "Server rules", "about.rules": "Server rules",
"account.account_note_header": "Note", "account.account_note_header": "Note",
"account.add_or_remove_from_list": "Add or Remove from lists", "account.add_or_remove_from_list": "Add or Remove from lists",
"account.badges.bot": "Bot", "account.badges.bot": "Automated",
"account.badges.group": "Group", "account.badges.group": "Group",
"account.block": "Block @{name}", "account.block": "Block @{name}",
"account.block_domain": "Block domain {domain}", "account.block_domain": "Block domain {domain}",

View file

@ -8,61 +8,71 @@ class NotificationMailer < ApplicationMailer
def mention(recipient, notification) def mention(recipient, notification)
@me = recipient @me = recipient
@user = recipient.user
@type = 'mention'
@status = notification.target_status @status = notification.target_status
return unless @me.user.functional? && @status.present? return unless @user.functional? && @status.present?
locale_for_account(@me) do locale_for_account(@me) do
thread_by_conversation(@status.conversation) thread_by_conversation(@status.conversation)
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct) mail to: email_address_with_name(@user.email, @me.username), subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct)
end end
end end
def follow(recipient, notification) def follow(recipient, notification)
@me = recipient @me = recipient
@user = recipient.user
@type = 'follow'
@account = notification.from_account @account = notification.from_account
return unless @me.user.functional? return unless @user.functional?
locale_for_account(@me) do locale_for_account(@me) do
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.follow.subject', name: @account.acct) mail to: email_address_with_name(@user.email, @me.username), subject: I18n.t('notification_mailer.follow.subject', name: @account.acct)
end end
end end
def favourite(recipient, notification) def favourite(recipient, notification)
@me = recipient @me = recipient
@user = recipient.user
@type = 'favourite'
@account = notification.from_account @account = notification.from_account
@status = notification.target_status @status = notification.target_status
return unless @me.user.functional? && @status.present? return unless @user.functional? && @status.present?
locale_for_account(@me) do locale_for_account(@me) do
thread_by_conversation(@status.conversation) thread_by_conversation(@status.conversation)
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct) mail to: email_address_with_name(@user.email, @me.username), subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct)
end end
end end
def reblog(recipient, notification) def reblog(recipient, notification)
@me = recipient @me = recipient
@user = recipient.user
@type = 'reblog'
@account = notification.from_account @account = notification.from_account
@status = notification.target_status @status = notification.target_status
return unless @me.user.functional? && @status.present? return unless @user.functional? && @status.present?
locale_for_account(@me) do locale_for_account(@me) do
thread_by_conversation(@status.conversation) thread_by_conversation(@status.conversation)
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct) mail to: email_address_with_name(@user.email, @me.username), subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct)
end end
end end
def follow_request(recipient, notification) def follow_request(recipient, notification)
@me = recipient @me = recipient
@user = recipient.user
@type = 'follow_request'
@account = notification.from_account @account = notification.from_account
return unless @me.user.functional? return unless @user.functional?
locale_for_account(@me) do locale_for_account(@me) do
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct) mail to: email_address_with_name(@user.email, @me.username), subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct)
end end
end end

View file

@ -145,6 +145,7 @@ class Account < ApplicationRecord
:locale, :locale,
:shows_application?, :shows_application?,
:prefers_noindex?, :prefers_noindex?,
:time_zone,
to: :user, to: :user,
prefix: true, prefix: true,
allow_nil: true allow_nil: true

View file

@ -40,6 +40,7 @@
# sign_up_ip :inet # sign_up_ip :inet
# role_id :bigint(8) # role_id :bigint(8)
# settings :text # settings :text
# time_zone :string
# #
class User < ApplicationRecord class User < ApplicationRecord
@ -101,6 +102,7 @@ class User < ApplicationRecord
validates_with BlacklistedEmailValidator, if: -> { ENV['EMAIL_DOMAIN_LISTS_APPLY_AFTER_CONFIRMATION'] == 'true' || !confirmed? } validates_with BlacklistedEmailValidator, if: -> { ENV['EMAIL_DOMAIN_LISTS_APPLY_AFTER_CONFIRMATION'] == 'true' || !confirmed? }
validates_with EmailMxValidator, if: :validate_email_dns? validates_with EmailMxValidator, if: :validate_email_dns?
validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create
validates :time_zone, inclusion: { in: ActiveSupport::TimeZone.all.map { |tz| tz.tzinfo.name } }, allow_blank: true
# Honeypot/anti-spam fields # Honeypot/anti-spam fields
attr_accessor :registration_form_time, :website, :confirm_password attr_accessor :registration_form_time, :website, :confirm_password

View file

@ -35,7 +35,7 @@ class AppSignUpService < BaseService
end end
def user_params def user_params
@params.slice(:email, :password, :agreement, :locale) @params.slice(:email, :password, :agreement, :locale, :time_zone)
end end
def account_params def account_params

View file

@ -44,7 +44,11 @@
%tbody %tbody
%td.column-cell %td.column-cell
%p= t 'about.hosted_on', domain: site_hostname %p= t 'about.hosted_on', domain: site_hostname
%p= link_to t('application_mailer.notification_preferences'), settings_preferences_notifications_url %p
= link_to t('application_mailer.notification_preferences'), settings_preferences_notifications_url
- if defined?(@type)
·
= link_to t('application_mailer.unsubscribe'), unsubscribe_url(token: @user.to_sgid(for: 'unsubscribe').to_s, type: @type)
%td.column-cell.text-right %td.column-cell.text-right
= link_to root_url do = link_to root_url do
= image_tag full_pack_url('media/images/mailer/logo.png'), alt: 'Mastodon', height: 24 = image_tag full_pack_url('media/images/mailer/logo.png'), alt: 'Mastodon', height: 24

View file

@ -0,0 +1,9 @@
- content_for :page_title do
= t('mail_subscriptions.unsubscribe.title')
.simple_form
%h1.title= t('mail_subscriptions.unsubscribe.complete')
%p.lead
= t('mail_subscriptions.unsubscribe.success_html', domain: content_tag(:strong, site_hostname), type: content_tag(:strong, I18n.t(@type, scope: 'mail_subscriptions.unsubscribe.emails')), email: content_tag(:strong, @user.email))
%p.lead
= t('mail_subscriptions.unsubscribe.resubscribe_html', settings_path: settings_preferences_notifications_path)

View file

@ -0,0 +1,12 @@
- content_for :page_title do
= t('mail_subscriptions.unsubscribe.title')
.simple_form
%h1.title= t('mail_subscriptions.unsubscribe.title')
%p.lead
= t('mail_subscriptions.unsubscribe.confirmation_html', domain: content_tag(:strong, site_hostname), type: content_tag(:strong, I18n.t(@type, scope: 'mail_subscriptions.unsubscribe.emails')), email: content_tag(:strong, @user.email), settings_path: settings_preferences_notifications_path)
= form_tag unsubscribe_path, method: :post do
= hidden_field_tag :token, params[:token]
= hidden_field_tag :type, params[:type]
= button_tag t('mail_subscriptions.unsubscribe.action'), type: :submit

View file

@ -42,4 +42,4 @@
= link_to a.remote_url, a.remote_url = link_to a.remote_url, a.remote_url
%p.status-footer %p.status-footer
= link_to l(status.created_at), web_url("@#{status.account.pretty_acct}/#{status.id}") = link_to l(status.created_at.in_time_zone(time_zone)), web_url("@#{status.account.pretty_acct}/#{status.id}")

View file

@ -22,7 +22,7 @@
%h1= t 'notification_mailer.favourite.title' %h1= t 'notification_mailer.favourite.title'
%p.lead= t('notification_mailer.favourite.body', name: @account.pretty_acct) %p.lead= t('notification_mailer.favourite.body', name: @account.pretty_acct)
= render 'status', status: @status = render 'status', status: @status, time_zone: @me.user_time_zone
%table.email-table{ cellspacing: 0, cellpadding: 0 } %table.email-table{ cellspacing: 0, cellpadding: 0 }
%tbody %tbody

View file

@ -22,7 +22,7 @@
%h1= t 'notification_mailer.mention.title' %h1= t 'notification_mailer.mention.title'
%p.lead= t('notification_mailer.mention.body', name: @status.account.pretty_acct) %p.lead= t('notification_mailer.mention.body', name: @status.account.pretty_acct)
= render 'status', status: @status = render 'status', status: @status, time_zone: @me.user_time_zone
%table.email-table{ cellspacing: 0, cellpadding: 0 } %table.email-table{ cellspacing: 0, cellpadding: 0 }
%tbody %tbody

View file

@ -22,7 +22,7 @@
%h1= t 'notification_mailer.reblog.title' %h1= t 'notification_mailer.reblog.title'
%p.lead= t('notification_mailer.reblog.body', name: @account.pretty_acct) %p.lead= t('notification_mailer.reblog.body', name: @account.pretty_acct)
= render 'status', status: @status = render 'status', status: @status, time_zone: @me.user_time_zone
%table.email-table{ cellspacing: 0, cellpadding: 0 } %table.email-table{ cellspacing: 0, cellpadding: 0 }
%tbody %tbody

View file

@ -9,8 +9,11 @@
.fields-group.fields-row__column.fields-row__column-6 .fields-group.fields-row__column.fields-row__column-6
= f.input :locale, collection: I18n.available_locales, wrapper: :with_label, include_blank: false, label_method: lambda { |locale| native_locale_name(locale) }, selected: I18n.locale, hint: false = f.input :locale, collection: I18n.available_locales, wrapper: :with_label, include_blank: false, label_method: lambda { |locale| native_locale_name(locale) }, selected: I18n.locale, hint: false
.fields-group.fields-row__column.fields-row__column-6 .fields-group.fields-row__column.fields-row__column-6
= f.simple_fields_for :settings, current_user.settings do |ff| = f.input :time_zone, wrapper: :with_label, collection: ActiveSupport::TimeZone.all.map { |tz| ["(GMT#{tz.formatted_offset}) #{tz.name}", tz.tzinfo.name] }, hint: false
= ff.input :theme, collection: Themes.instance.names, label_method: lambda { |theme| I18n.t("themes.#{theme}", default: theme) }, wrapper: :with_label, include_blank: false, hint: false
.fields-group
= f.simple_fields_for :settings, current_user.settings do |ff|
= ff.input :theme, collection: Themes.instance.names, label_method: lambda { |theme| I18n.t("themes.#{theme}", default: theme) }, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_theme'), include_blank: false, hint: false
- unless I18n.locale == :en - unless I18n.locale == :en
.flash-message.translation-prompt .flash-message.translation-prompt

View file

@ -36,7 +36,7 @@
%tbody %tbody
%tr %tr
%td.column-cell.text-center %td.column-cell.text-center
%p= t 'user_mailer.appeal_approved.explanation', appeal_date: l(@appeal.created_at), strike_date: l(@appeal.strike.created_at) %p= t 'user_mailer.appeal_approved.explanation', appeal_date: l(@appeal.created_at.in_time_zone(@resource.time_zone)), strike_date: l(@appeal.strike.created_at.in_time_zone(@resource.time_zone))
%table.email-table{ cellspacing: 0, cellpadding: 0 } %table.email-table{ cellspacing: 0, cellpadding: 0 }
%tbody %tbody

View file

@ -2,6 +2,6 @@
=== ===
<%= t 'user_mailer.appeal_approved.explanation', appeal_date: l(@appeal.created_at), strike_date: l(@appeal.strike.created_at) %> <%= t 'user_mailer.appeal_approved.explanation', appeal_date: l(@appeal.created_at.in_time_zone(@resource.time_zone)), strike_date: l(@appeal.strike.created_at.in_time_zone(@resource.time_zone)) %>
=> <%= root_url %> => <%= root_url %>

View file

@ -36,7 +36,7 @@
%tbody %tbody
%tr %tr
%td.column-cell.text-center %td.column-cell.text-center
%p= t 'user_mailer.appeal_rejected.explanation', appeal_date: l(@appeal.created_at), strike_date: l(@appeal.strike.created_at) %p= t 'user_mailer.appeal_rejected.explanation', appeal_date: l(@appeal.created_at.in_time_zone(@resource.time_zone)), strike_date: l(@appeal.strike.created_at.in_time_zone(@resource.time_zone))
%table.email-table{ cellspacing: 0, cellpadding: 0 } %table.email-table{ cellspacing: 0, cellpadding: 0 }
%tbody %tbody

View file

@ -2,6 +2,6 @@
=== ===
<%= t 'user_mailer.appeal_rejected.explanation', appeal_date: l(@appeal.created_at), strike_date: l(@appeal.strike.created_at) %> <%= t 'user_mailer.appeal_rejected.explanation', appeal_date: l(@appeal.created_at.in_time_zone(@resource.time_zone)), strike_date: l(@appeal.strike.created_at.in_time_zone(@resource.time_zone)) %>
=> <%= root_url %> => <%= root_url %>

View file

@ -47,7 +47,7 @@
%strong= "#{t('sessions.browser')}:" %strong= "#{t('sessions.browser')}:"
%span{ title: @user_agent }= t 'sessions.description', browser: t("sessions.browsers.#{@detection.id}", default: @detection.id.to_s), platform: t("sessions.platforms.#{@detection.platform.id}", default: @detection.platform.id.to_s) %span{ title: @user_agent }= t 'sessions.description', browser: t("sessions.browsers.#{@detection.id}", default: @detection.id.to_s), platform: t("sessions.platforms.#{@detection.platform.id}", default: @detection.platform.id.to_s)
%br/ %br/
= l(@timestamp) = l(@timestamp.in_time_zone(@resource.time_zone))
%table.email-table{ cellspacing: 0, cellpadding: 0 } %table.email-table{ cellspacing: 0, cellpadding: 0 }
%tbody %tbody

View file

@ -8,7 +8,7 @@
<%= t('sessions.ip') %>: <%= @remote_ip %> <%= t('sessions.ip') %>: <%= @remote_ip %>
<%= t('sessions.browser') %>: <%= t('sessions.description', browser: t("sessions.browsers.#{@detection.id}", default: "#{@detection.id}"), platform: t("sessions.platforms.#{@detection.platform.id}", default: "#{@detection.platform.id}")) %> <%= t('sessions.browser') %>: <%= t('sessions.description', browser: t("sessions.browsers.#{@detection.id}", default: "#{@detection.id}"), platform: t("sessions.platforms.#{@detection.platform.id}", default: "#{@detection.platform.id}")) %>
<%= l(@timestamp) %> <%= l(@timestamp.in_time_zone(@resource.time_zone)) %>
<%= t 'user_mailer.suspicious_sign_in.further_actions_html', action: t('user_mailer.suspicious_sign_in.change_password') %> <%= t 'user_mailer.suspicious_sign_in.further_actions_html', action: t('user_mailer.suspicious_sign_in.change_password') %>

View file

@ -58,7 +58,7 @@
- unless @statuses.empty? - unless @statuses.empty?
- @statuses.each_with_index do |status, i| - @statuses.each_with_index do |status, i|
= render 'notification_mailer/status', status: status, i: i + 1, highlighted: true = render 'notification_mailer/status', status: status, i: i + 1, highlighted: true, time_zone: @resource.time_zone
%table.email-table{ cellspacing: 0, cellpadding: 0 } %table.email-table{ cellspacing: 0, cellpadding: 0 }
%tbody %tbody

View file

@ -66,6 +66,7 @@ ignore_unused:
- 'notification_mailer.*' - 'notification_mailer.*'
- 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks}_html' - 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks}_html'
- 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks}_html' - 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks}_html'
- 'mail_subscriptions.unsubscribe.emails.*'
ignore_inconsistent_interpolations: ignore_inconsistent_interpolations:
- '*.one' - '*.one'

View file

@ -1072,6 +1072,7 @@ en:
notification_preferences: Change e-mail preferences notification_preferences: Change e-mail preferences
salutation: "%{name}," salutation: "%{name},"
settings: 'Change e-mail preferences: %{link}' settings: 'Change e-mail preferences: %{link}'
unsubscribe: Unsubscribe
view: 'View:' view: 'View:'
view_profile: View profile view_profile: View profile
view_status: View post view_status: View post
@ -1437,6 +1438,21 @@ en:
failed_sign_in_html: Failed sign-in attempt with %{method} from %{ip} (%{browser}) failed_sign_in_html: Failed sign-in attempt with %{method} from %{ip} (%{browser})
successful_sign_in_html: Successful sign-in with %{method} from %{ip} (%{browser}) successful_sign_in_html: Successful sign-in with %{method} from %{ip} (%{browser})
title: Authentication history title: Authentication history
mail_subscriptions:
unsubscribe:
action: Yes, unsubscribe
complete: Unsubscribed
confirmation_html: Are you sure you want to unsubscribe from receiving %{type} for Mastodon on %{domain} to your e-mail at %{email}? You can always re-subscribe from your <a href="%{settings_path}">e-mail notification settings</a>.
emails:
notification_emails:
favourite: favorite notification e-mails
follow: follow notification e-mails
follow_request: follow request e-mails
mention: mention notification e-mails
reblog: boost notification e-mails
resubscribe_html: If you've unsubscribed by mistake, you can re-subscribe from your <a href="%{settings_path}">e-mail notification settings</a>.
success_html: You'll no longer receive %{type} for Mastodon on %{domain} to your e-mail at %{email}.
title: Unsubscribe
media_attachments: media_attachments:
validations: validations:
images_and_video: Cannot attach a video to a post that already contains images images_and_video: Cannot attach a video to a post that already contains images

View file

@ -173,7 +173,7 @@ en:
defaults: defaults:
autofollow: Invite to follow your account autofollow: Invite to follow your account
avatar: Avatar avatar: Avatar
bot: This is a bot account bot: This is an automated account
chosen_languages: Filter languages chosen_languages: Filter languages
confirm_new_password: Confirm new password confirm_new_password: Confirm new password
confirm_password: Confirm password confirm_password: Confirm password
@ -322,6 +322,7 @@ en:
usable: Allow posts to use this hashtag usable: Allow posts to use this hashtag
user: user:
role: Role role: Role
time_zone: Time zone
user_role: user_role:
color: Badge color color: Badge color
highlighted: Display role as badge on user profiles highlighted: Display role as badge on user profiles

View file

@ -69,6 +69,8 @@ Rails.application.routes.draw do
devise_scope :user do devise_scope :user do
get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite
resource :unsubscribe, only: [:show, :create], controller: :mail_subscriptions
namespace :auth do namespace :auth do
resource :setup, only: [:show, :update], controller: :setup resource :setup, only: [:show, :update], controller: :setup
resource :challenge, only: [:create], controller: :challenges resource :challenge, only: [:create], controller: :challenges

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddTimeZoneToUsers < ActiveRecord::Migration[6.1]
def change
add_column :users, :time_zone, :string
end
end

View file

@ -12,7 +12,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2023_06_05_085710) do ActiveRecord::Schema.define(version: 2023_06_05_085711) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -1211,6 +1211,7 @@ ActiveRecord::Schema.define(version: 2023_06_05_085710) do
t.boolean "skip_sign_in_token" t.boolean "skip_sign_in_token"
t.bigint "role_id" t.bigint "role_id"
t.text "settings" t.text "settings"
t.string "time_zone"
t.index ["account_id"], name: "index_users_on_account_id" t.index ["account_id"], name: "index_users_on_account_id"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["created_by_application_id"], name: "index_users_on_created_by_application_id", where: "(created_by_application_id IS NOT NULL)" t.index ["created_by_application_id"], name: "index_users_on_created_by_application_id", where: "(created_by_application_id IS NOT NULL)"

View file

@ -23,23 +23,12 @@ module Mastodon::CLI
def recount(type) def recount(type)
case type case type
when 'accounts' when 'accounts'
processed, = parallelize_with_progress(Account.local.includes(:account_stat)) do |account| processed, = parallelize_with_progress(accounts_with_stats) do |account|
account_stat = account.account_stat recount_account_stats(account)
account_stat.following_count = account.active_relationships.count
account_stat.followers_count = account.passive_relationships.count
account_stat.statuses_count = account.statuses.where.not(visibility: :direct).count
account_stat.save if account_stat.changed?
end end
when 'statuses' when 'statuses'
processed, = parallelize_with_progress(Status.includes(:status_stat)) do |status| processed, = parallelize_with_progress(statuses_with_stats) do |status|
status_stat = status.status_stat recount_status_stats(status)
status_stat.replies_count = status.replies.where.not(visibility: :direct).count
status_stat.reblogs_count = status.reblogs.count
status_stat.favourites_count = status.favourites.count
status_stat.emoji_reactions = status.generate_emoji_reactions_grouped_by_name
status_stat.save if status_stat.changed?
end end
else else
say("Unknown type: #{type}", :red) say("Unknown type: #{type}", :red)
@ -49,5 +38,36 @@ module Mastodon::CLI
say say
say("OK, recounted #{processed} records", :green) say("OK, recounted #{processed} records", :green)
end end
private
def accounts_with_stats
Account.local.includes(:account_stat)
end
def statuses_with_stats
Status.includes(:status_stat)
end
def recount_account_stats(account)
account.account_stat.tap do |account_stat|
account_stat.following_count = account.active_relationships.count
account_stat.followers_count = account.passive_relationships.count
account_stat.statuses_count = account.statuses.where.not(visibility: :direct).count
account_stat.save if account_stat.changed?
end
end
def recount_status_stats(status)
status.status_stat.tap do |status_stat|
status_stat.replies_count = status.replies.where.not(visibility: :direct).count
status_stat.reblogs_count = status.reblogs.count
status_stat.favourites_count = status.favourites.count
status_stat.emoji_reactions = status.generate_emoji_reactions_grouped_by_name
status_stat.save if status_stat.changed?
end
end
end end
end end

View file

@ -19,7 +19,7 @@ module Mastodon::CLI
LONG_DESC LONG_DESC
def build(username = nil) def build(username = nil)
if options[:all] || username.nil? if options[:all] || username.nil?
processed, = parallelize_with_progress(Account.joins(:user).merge(User.active)) do |account| processed, = parallelize_with_progress(active_user_accounts) do |account|
PrecomputeFeedService.new.call(account) unless dry_run? PrecomputeFeedService.new.call(account) unless dry_run?
end end
@ -47,5 +47,11 @@ module Mastodon::CLI
redis.del(keys) redis.del(keys)
say('OK', :green) say('OK', :green)
end end
private
def active_user_accounts
Account.joins(:user).merge(User.active)
end
end end
end end

View file

@ -48,25 +48,32 @@ describe Api::V1::Accounts::RelationshipsController do
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
it 'returns JSON with correct data' do context 'when there is returned JSON data' do
json = body_as_json let(:json) { body_as_json }
expect(json).to be_a Enumerable it 'returns an enumerable json' do
expect(json.first[:id]).to eq simon.id.to_s expect(json).to be_a Enumerable
expect(json.first[:following]).to be true end
expect(json.first[:showing_reblogs]).to be true
expect(json.first[:followed_by]).to be false
expect(json.first[:muting]).to be false
expect(json.first[:requested]).to be false
expect(json.first[:domain_blocking]).to be false
expect(json.second[:id]).to eq lewis.id.to_s it 'returns a correct first element' do
expect(json.second[:following]).to be false expect(json.first[:id]).to eq simon.id.to_s
expect(json.second[:showing_reblogs]).to be false expect(json.first[:following]).to be true
expect(json.second[:followed_by]).to be true expect(json.first[:showing_reblogs]).to be true
expect(json.second[:muting]).to be false expect(json.first[:followed_by]).to be false
expect(json.second[:requested]).to be false expect(json.first[:muting]).to be false
expect(json.second[:domain_blocking]).to be false expect(json.first[:requested]).to be false
expect(json.first[:domain_blocking]).to be false
end
it 'returns a correct second element' do
expect(json.second[:id]).to eq lewis.id.to_s
expect(json.second[:following]).to be false
expect(json.second[:showing_reblogs]).to be false
expect(json.second[:followed_by]).to be true
expect(json.second[:muting]).to be false
expect(json.second[:requested]).to be false
expect(json.second[:domain_blocking]).to be false
end
end end
it 'returns JSON with correct data on cached requests too' do it 'returns JSON with correct data on cached requests too' do

View file

@ -55,20 +55,6 @@ RSpec.describe Api::V1::AccountsController do
end end
end end
describe 'GET #show' do
let(:scopes) { 'read:accounts' }
before do
get :show, params: { id: user.account.id }
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
end
describe 'POST #follow' do describe 'POST #follow' do
let(:scopes) { 'write:follows' } let(:scopes) { 'write:follows' }
let(:other_account) { Fabricate(:account, username: 'bob', locked: locked) } let(:other_account) { Fabricate(:account, username: 'bob', locked: locked) }

View file

@ -1,358 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::V1::Admin::CanonicalEmailBlocksController do
render_views
let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'admin:read:canonical_email_blocks admin:write:canonical_email_blocks' }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
shared_examples 'forbidden for wrong scope' do |wrong_scope|
let(:scopes) { wrong_scope }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
shared_examples 'forbidden for wrong role' do |wrong_role|
let(:role) { UserRole.find_by(name: wrong_role) }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
describe 'GET #index' do
context 'with wrong scope' do
before do
get :index
end
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
end
context 'with wrong role' do
before do
get :index
end
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
end
it 'returns http success' do
get :index
expect(response).to have_http_status(200)
end
context 'when there is no canonical email block' do
it 'returns an empty list' do
get :index
body = body_as_json
expect(body).to be_empty
end
end
context 'when there are canonical email blocks' do
let!(:canonical_email_blocks) { Fabricate.times(5, :canonical_email_block) }
let(:expected_email_hashes) { canonical_email_blocks.pluck(:canonical_email_hash) }
it 'returns the correct canonical email hashes' do
get :index
json = body_as_json
expect(json.pluck(:canonical_email_hash)).to match_array(expected_email_hashes)
end
context 'with limit param' do
let(:params) { { limit: 2 } }
it 'returns only the requested number of canonical email blocks' do
get :index, params: params
json = body_as_json
expect(json.size).to eq(params[:limit])
end
end
context 'with since_id param' do
let(:params) { { since_id: canonical_email_blocks[1].id } }
it 'returns only the canonical email blocks after since_id' do
get :index, params: params
canonical_email_blocks_ids = canonical_email_blocks.pluck(:id).map(&:to_s)
json = body_as_json
expect(json.pluck(:id)).to match_array(canonical_email_blocks_ids[2..])
end
end
context 'with max_id param' do
let(:params) { { max_id: canonical_email_blocks[3].id } }
it 'returns only the canonical email blocks before max_id' do
get :index, params: params
canonical_email_blocks_ids = canonical_email_blocks.pluck(:id).map(&:to_s)
json = body_as_json
expect(json.pluck(:id)).to match_array(canonical_email_blocks_ids[..2])
end
end
end
end
describe 'GET #show' do
let!(:canonical_email_block) { Fabricate(:canonical_email_block) }
let(:params) { { id: canonical_email_block.id } }
context 'with wrong scope' do
before do
get :show, params: params
end
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
end
context 'with wrong role' do
before do
get :show, params: params
end
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
end
context 'when canonical email block exists' do
it 'returns http success' do
get :show, params: params
expect(response).to have_http_status(200)
end
it 'returns canonical email block data correctly' do
get :show, params: params
json = body_as_json
expect(json[:id]).to eq(canonical_email_block.id.to_s)
expect(json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash)
end
end
context 'when canonical block does not exist' do
it 'returns http not found' do
get :show, params: { id: 0 }
expect(response).to have_http_status(404)
end
end
end
describe 'POST #test' do
context 'with wrong scope' do
before do
post :test
end
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
end
context 'with wrong role' do
before do
post :test, params: { email: 'whatever@email.com' }
end
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
end
context 'when required email is not provided' do
it 'returns http bad request' do
post :test
expect(response).to have_http_status(400)
end
end
context 'when required email is provided' do
let(:params) { { email: 'example@email.com' } }
context 'when there is a matching canonical email block' do
let!(:canonical_email_block) { CanonicalEmailBlock.create(params) }
it 'returns http success' do
post :test, params: params
expect(response).to have_http_status(200)
end
it 'returns expected canonical email hash' do
post :test, params: params
json = body_as_json
expect(json[0][:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash)
end
end
context 'when there is no matching canonical email block' do
it 'returns http success' do
post :test, params: params
expect(response).to have_http_status(200)
end
it 'returns an empty list' do
post :test, params: params
json = body_as_json
expect(json).to be_empty
end
end
end
end
describe 'POST #create' do
let(:params) { { email: 'example@email.com' } }
let(:canonical_email_block) { CanonicalEmailBlock.new(email: params[:email]) }
context 'with wrong scope' do
before do
post :create, params: params
end
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
end
context 'with wrong role' do
before do
post :create, params: params
end
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
end
it 'returns http success' do
post :create, params: params
expect(response).to have_http_status(200)
end
it 'returns canonical_email_hash correctly' do
post :create, params: params
json = body_as_json
expect(json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash)
end
context 'when required email param is not provided' do
it 'returns http unprocessable entity' do
post :create
expect(response).to have_http_status(422)
end
end
context 'when canonical_email_hash param is provided instead of email' do
let(:params) { { canonical_email_hash: 'dd501ce4e6b08698f19df96f2f15737e48a75660b1fa79b6ff58ea25ee4851a4' } }
it 'returns http success' do
post :create, params: params
expect(response).to have_http_status(200)
end
it 'returns correct canonical_email_hash' do
post :create, params: params
json = body_as_json
expect(json[:canonical_email_hash]).to eq(params[:canonical_email_hash])
end
end
context 'when both email and canonical_email_hash params are provided' do
let(:params) { { email: 'example@email.com', canonical_email_hash: 'dd501ce4e6b08698f19df96f2f15737e48a75660b1fa79b6ff58ea25ee4851a4' } }
it 'returns http success' do
post :create, params: params
expect(response).to have_http_status(200)
end
it 'ignores canonical_email_hash param' do
post :create, params: params
json = body_as_json
expect(json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash)
end
end
context 'when canonical email was already blocked' do
before do
canonical_email_block.save
end
it 'returns http unprocessable entity' do
post :create, params: params
expect(response).to have_http_status(422)
end
end
end
describe 'DELETE #destroy' do
let!(:canonical_email_block) { Fabricate(:canonical_email_block) }
let(:params) { { id: canonical_email_block.id } }
context 'with wrong scope' do
before do
delete :destroy, params: params
end
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
end
context 'with wrong role' do
before do
delete :destroy, params: params
end
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
end
it 'returns http success' do
delete :destroy, params: params
expect(response).to have_http_status(200)
end
context 'when canonical email block is not found' do
it 'returns http not found' do
delete :destroy, params: { id: 0 }
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -1,140 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Api::V1::Admin::DomainAllowsController do
render_views
let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:scopes) { 'admin:read admin:write' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
shared_examples 'forbidden for wrong scope' do |wrong_scope|
let(:scopes) { wrong_scope }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
shared_examples 'forbidden for wrong role' do |wrong_role|
let(:role) { UserRole.find_by(name: wrong_role) }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
describe 'GET #index' do
let!(:domain_allow) { Fabricate(:domain_allow) }
before do
get :index
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns the expected domain allows' do
json = body_as_json
expect(json.length).to eq 1
expect(json[0][:id].to_i).to eq domain_allow.id
end
end
describe 'GET #show' do
let!(:domain_allow) { Fabricate(:domain_allow) }
before do
get :show, params: { id: domain_allow.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns expected domain name' do
json = body_as_json
expect(json[:domain]).to eq domain_allow.domain
end
end
describe 'DELETE #destroy' do
let!(:domain_allow) { Fabricate(:domain_allow) }
before do
delete :destroy, params: { id: domain_allow.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'deletes the block' do
expect(DomainAllow.find_by(id: domain_allow.id)).to be_nil
end
end
describe 'POST #create' do
let!(:domain_allow) { Fabricate(:domain_allow, domain: 'example.com') }
context 'with a valid domain' do
before do
post :create, params: { domain: 'foo.bar.com' }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns expected domain name' do
json = body_as_json
expect(json[:domain]).to eq 'foo.bar.com'
end
it 'creates a domain block' do
expect(DomainAllow.find_by(domain: 'foo.bar.com')).to_not be_nil
end
end
context 'with invalid domain name' do
before do
post :create, params: { domain: 'foo bar' }
end
it 'returns http unprocessable entity' do
expect(response).to have_http_status(422)
end
end
context 'when domain name is not specified' do
it 'returns http unprocessable entity' do
post :create
expect(response).to have_http_status(422)
end
end
end
end

View file

@ -1,180 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Api::V1::Admin::DomainBlocksController do
render_views
let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:scopes) { 'admin:read admin:write' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
shared_examples 'forbidden for wrong scope' do |wrong_scope|
let(:scopes) { wrong_scope }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
shared_examples 'forbidden for wrong role' do |wrong_role|
let(:role) { UserRole.find_by(name: wrong_role) }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
describe 'GET #index' do
let!(:block) { Fabricate(:domain_block) }
before do
get :index
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns the expected domain blocks' do
json = body_as_json
expect(json.length).to eq 1
expect(json[0][:id].to_i).to eq block.id
end
end
describe 'GET #show' do
let!(:block) { Fabricate(:domain_block) }
before do
get :show, params: { id: block.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns expected domain name' do
json = body_as_json
expect(json[:domain]).to eq block.domain
end
end
describe 'PUT #update' do
let!(:remote_account) { Fabricate(:account, domain: 'example.com') }
let(:subject) do
post :update, params: { id: domain_block.id, domain: 'example.com', severity: new_severity }
end
let(:domain_block) { Fabricate(:domain_block, domain: 'example.com', severity: original_severity) }
before do
BlockDomainService.new.call(domain_block)
end
context 'when downgrading a domain suspension to silence' do
let(:original_severity) { 'suspend' }
let(:new_severity) { 'silence' }
it 'changes the block severity' do
expect { subject }.to change { domain_block.reload.severity }.from('suspend').to('silence')
end
it 'undoes individual suspensions' do
expect { subject }.to change { remote_account.reload.suspended? }.from(true).to(false)
end
it 'performs individual silences' do
expect { subject }.to change { remote_account.reload.silenced? }.from(false).to(true)
end
end
context 'when upgrading a domain silence to suspend' do
let(:original_severity) { 'silence' }
let(:new_severity) { 'suspend' }
it 'changes the block severity' do
expect { subject }.to change { domain_block.reload.severity }.from('silence').to('suspend')
end
it 'undoes individual silences' do
expect { subject }.to change { remote_account.reload.silenced? }.from(true).to(false)
end
it 'performs individual suspends' do
expect { subject }.to change { remote_account.reload.suspended? }.from(false).to(true)
end
end
end
describe 'DELETE #destroy' do
let!(:block) { Fabricate(:domain_block) }
before do
delete :destroy, params: { id: block.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'deletes the block' do
expect(DomainBlock.find_by(id: block.id)).to be_nil
end
end
describe 'POST #create' do
let(:existing_block_domain) { 'example.com' }
let!(:block) { Fabricate(:domain_block, domain: existing_block_domain, severity: :suspend) }
before do
post :create, params: { domain: 'foo.bar.com', severity: :silence }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns expected domain name' do
json = body_as_json
expect(json[:domain]).to eq 'foo.bar.com'
end
it 'creates a domain block' do
expect(DomainBlock.find_by(domain: 'foo.bar.com')).to_not be_nil
end
context 'when a stricter domain block already exists' do
let(:existing_block_domain) { 'bar.com' }
it 'returns http unprocessable entity' do
expect(response).to have_http_status(422)
end
it 'renders existing domain block in error' do
json = body_as_json
expect(json[:existing_domain_block][:domain]).to eq existing_block_domain
end
end
end
end

View file

@ -1,309 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::V1::Admin::IpBlocksController do
render_views
let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'admin:read:ip_blocks admin:write:ip_blocks' }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
shared_examples 'forbidden for wrong scope' do |wrong_scope|
let(:scopes) { wrong_scope }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
shared_examples 'forbidden for wrong role' do |wrong_role|
let(:role) { UserRole.find_by(name: wrong_role) }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
describe 'GET #index' do
context 'with wrong scope' do
before do
get :index
end
it_behaves_like 'forbidden for wrong scope', 'admin:write:ip_blocks'
end
context 'with wrong role' do
before do
get :index
end
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
end
it 'returns http success' do
get :index
expect(response).to have_http_status(200)
end
context 'when there is no ip block' do
it 'returns an empty body' do
get :index
json = body_as_json
expect(json).to be_empty
end
end
context 'when there are ip blocks' do
let!(:ip_blocks) do
[
IpBlock.create(ip: '192.0.2.0/24', severity: :no_access),
IpBlock.create(ip: '172.16.0.1', severity: :sign_up_requires_approval, comment: 'Spam'),
IpBlock.create(ip: '2001:0db8::/32', severity: :sign_up_block, expires_in: 10.days),
]
end
let(:expected_response) do
ip_blocks.map do |ip_block|
{
id: ip_block.id.to_s,
ip: ip_block.ip,
severity: ip_block.severity.to_s,
comment: ip_block.comment,
created_at: ip_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
expires_at: ip_block.expires_at&.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
}
end
end
it 'returns the correct blocked ips' do
get :index
json = body_as_json
expect(json).to match_array(expected_response)
end
context 'with limit param' do
let(:params) { { limit: 2 } }
it 'returns only the requested number of ip blocks' do
get :index, params: params
json = body_as_json
expect(json.size).to eq(params[:limit])
end
end
end
end
describe 'GET #show' do
let!(:ip_block) { IpBlock.create(ip: '192.0.2.0/24', severity: :no_access) }
let(:params) { { id: ip_block.id } }
context 'with wrong scope' do
before do
get :show, params: params
end
it_behaves_like 'forbidden for wrong scope', 'admin:write:ip_blocks'
end
context 'with wrong role' do
before do
get :show, params: params
end
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
end
it 'returns http success' do
get :show, params: params
expect(response).to have_http_status(200)
end
it 'returns the correct ip block' do
get :show, params: params
json = body_as_json
expect(json[:ip]).to eq("#{ip_block.ip}/#{ip_block.ip.prefix}")
expect(json[:severity]).to eq(ip_block.severity.to_s)
end
context 'when ip block does not exist' do
it 'returns http not found' do
get :show, params: { id: 0 }
expect(response).to have_http_status(404)
end
end
end
describe 'POST #create' do
let(:params) { { ip: '151.0.32.55', severity: 'no_access', comment: 'Spam' } }
context 'with wrong scope' do
before do
post :create, params: params
end
it_behaves_like 'forbidden for wrong scope', 'admin:read:ip_blocks'
end
context 'with wrong role' do
before do
post :create, params: params
end
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
end
it 'returns http success' do
post :create, params: params
expect(response).to have_http_status(200)
end
it 'returns the correct ip block' do
post :create, params: params
json = body_as_json
expect(json[:ip]).to eq("#{params[:ip]}/32")
expect(json[:severity]).to eq(params[:severity])
expect(json[:comment]).to eq(params[:comment])
end
context 'when ip is not provided' do
let(:params) { { ip: '', severity: 'no_access' } }
it 'returns http unprocessable entity' do
post :create, params: params
expect(response).to have_http_status(422)
end
end
context 'when severity is not provided' do
let(:params) { { ip: '173.65.23.1', severity: '' } }
it 'returns http unprocessable entity' do
post :create, params: params
expect(response).to have_http_status(422)
end
end
context 'when provided ip is already blocked' do
before do
IpBlock.create(params)
end
it 'returns http unprocessable entity' do
post :create, params: params
expect(response).to have_http_status(422)
end
end
context 'when provided ip address is invalid' do
let(:params) { { ip: '520.13.54.120', severity: 'no_access' } }
it 'returns http unprocessable entity' do
post :create, params: params
expect(response).to have_http_status(422)
end
end
end
describe 'PUT #update' do
context 'when ip block exists' do
let!(:ip_block) { IpBlock.create(ip: '185.200.13.3', severity: 'no_access', comment: 'Spam', expires_in: 48.hours) }
let(:params) { { id: ip_block.id, severity: 'sign_up_requires_approval', comment: 'Decreasing severity' } }
it 'returns http success' do
put :update, params: params
expect(response).to have_http_status(200)
end
it 'returns the correct ip block' do
put :update, params: params
json = body_as_json
expect(json).to match(hash_including({
ip: "#{ip_block.ip}/#{ip_block.ip.prefix}",
severity: 'sign_up_requires_approval',
comment: 'Decreasing severity',
}))
end
it 'updates the severity correctly' do
expect { put :update, params: params }.to change { ip_block.reload.severity }.from('no_access').to('sign_up_requires_approval')
end
it 'updates the comment correctly' do
expect { put :update, params: params }.to change { ip_block.reload.comment }.from('Spam').to('Decreasing severity')
end
end
context 'when ip block does not exist' do
it 'returns http not found' do
put :update, params: { id: 0 }
expect(response).to have_http_status(404)
end
end
end
describe 'DELETE #destroy' do
context 'when ip block exists' do
let!(:ip_block) { IpBlock.create(ip: '185.200.13.3', severity: 'no_access') }
let(:params) { { id: ip_block.id } }
it 'returns http success' do
delete :destroy, params: params
expect(response).to have_http_status(200)
end
it 'returns an empty body' do
delete :destroy, params: params
json = body_as_json
expect(json).to be_empty
end
it 'deletes the ip block' do
delete :destroy, params: params
expect(IpBlock.find_by(id: ip_block.id)).to be_nil
end
end
context 'when ip block does not exist' do
it 'returns http not found' do
delete :destroy, params: { id: 0 }
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -1,111 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Api::V1::Admin::ReportsController do
render_views
let(:role) { UserRole.find_by(name: 'Moderator') }
let(:user) { Fabricate(:user, role: role) }
let(:scopes) { 'admin:read admin:write' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:report) { Fabricate(:report) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
shared_examples 'forbidden for wrong scope' do |wrong_scope|
let(:scopes) { wrong_scope }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
shared_examples 'forbidden for wrong role' do |wrong_role|
let(:role) { UserRole.find_by(name: wrong_role) }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
describe 'GET #index' do
before do
get :index
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
describe 'GET #show' do
before do
get :show, params: { id: report.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
describe 'POST #resolve' do
before do
post :resolve, params: { id: report.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
describe 'POST #reopen' do
before do
post :reopen, params: { id: report.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
describe 'POST #assign_to_self' do
before do
post :assign_to_self, params: { id: report.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
describe 'POST #unassign' do
before do
post :unassign, params: { id: report.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
end

View file

@ -56,18 +56,11 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
end end
describe 'when creation succeeds' do describe 'when creation succeeds' do
let!(:otp_backup_codes) { user.generate_otp_backup_codes! }
it 'renders page with success' do it 'renders page with success' do
otp_backup_codes = user.generate_otp_backup_codes! prepare_user_otp_generation
expect_any_instance_of(User).to receive(:generate_otp_backup_codes!) do |value| prepare_user_otp_consumption
expect(value).to eq user
otp_backup_codes
end
expect_any_instance_of(User).to receive(:validate_and_consume_otp!) do |value, code, options|
expect(value).to eq user
expect(code).to eq '123456'
expect(options).to eq({ otp_secret: 'thisisasecretforthespecofnewview' })
true
end
expect do expect do
post :create, post :create,
@ -80,6 +73,22 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(response).to render_template('settings/two_factor_authentication/recovery_codes/index') expect(response).to render_template('settings/two_factor_authentication/recovery_codes/index')
end end
def prepare_user_otp_generation
expect_any_instance_of(User).to receive(:generate_otp_backup_codes!) do |value|
expect(value).to eq user
otp_backup_codes
end
end
def prepare_user_otp_consumption
expect_any_instance_of(User).to receive(:validate_and_consume_otp!) do |value, code, options|
expect(value).to eq user
expect(code).to eq '123456'
expect(options).to eq({ otp_secret: 'thisisasecretforthespecofnewview' })
true
end
end
end end
describe 'when creation fails' do describe 'when creation fails' do

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:account_domain_block) do Fabricator(:account_domain_block) do
account account { Fabricate.build(:account) }
domain 'example.com' domain 'example.com'
end end

View file

@ -2,6 +2,6 @@
Fabricator(:account_moderation_note) do Fabricator(:account_moderation_note) do
content 'MyText' content 'MyText'
account account { Fabricate.build(:account) }
target_account { Fabricate(:account) } target_account { Fabricate.build(:account) }
end end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:account_note) do Fabricator(:account_note) do
account account { Fabricate.build(:account) }
target_account { Fabricate(:account) } target_account { Fabricate.build(:account) }
comment 'User note text' comment 'User note text'
end end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:account_stat) do Fabricator(:account_stat) do
account account { Fabricate.build(:account) }
statuses_count '123' statuses_count '123'
following_count '456' following_count '456'
followers_count '789' followers_count '789'

View file

@ -1,5 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:account_statuses_cleanup_policy) do Fabricator(:account_statuses_cleanup_policy) do
account account { Fabricate.build(:account) }
end end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:account_warning) do Fabricator(:account_warning) do
account account { Fabricate.build(:account) }
target_account(fabricator: :account) target_account(fabricator: :account)
text { Faker::Lorem.paragraph } text { Faker::Lorem.paragraph }
action 'suspend' action 'suspend'

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator('Admin::ActionLog') do Fabricator('Admin::ActionLog') do
account account { Fabricate.build(:account) }
action 'MyString' action 'MyString'
target nil target nil
end end

View file

@ -1,5 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:backup) do Fabricator(:backup) do
user user { Fabricate.build(:user) }
end end

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:block) do Fabricator(:block) do
account account { Fabricate.build(:account) }
target_account { Fabricate(:account) } target_account { Fabricate.build(:account) }
end end

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:bookmark) do Fabricator(:bookmark) do
account account { Fabricate.build(:account) }
status status { Fabricate.build(:status) }
end end

View file

@ -8,5 +8,5 @@ Fabricator(:bulk_import) do
imported_items 1 imported_items 1
finished_at '2022-11-18 14:55:07' finished_at '2022-11-18 14:55:07'
overwrite false overwrite false
account account { Fabricate.build(:account) }
end end

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:bulk_import_row) do Fabricator(:bulk_import_row) do
bulk_import bulk_import { Fabricate.build(:bulk_import) }
data '' data ''
end end

View file

@ -2,5 +2,5 @@
Fabricator(:canonical_email_block) do Fabricator(:canonical_email_block) do
email { sequence(:email) { |i| "#{i}#{Faker::Internet.email}" } } email { sequence(:email) { |i| "#{i}#{Faker::Internet.email}" } }
reference_account { Fabricate(:account) } reference_account { Fabricate.build(:account) }
end end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:custom_filter) do Fabricator(:custom_filter) do
account account { Fabricate.build(:account) }
expires_at nil expires_at nil
phrase 'discourse' phrase 'discourse'
context %w(home notifications) context %w(home notifications)

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:custom_filter_keyword) do Fabricator(:custom_filter_keyword) do
custom_filter custom_filter { Fabricate.build(:custom_filter) }
keyword 'discourse' keyword 'discourse'
end end

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:custom_filter_status) do Fabricator(:custom_filter_status) do
custom_filter custom_filter { Fabricate.build(:custom_filter) }
status status { Fabricate.build(:status) }
end end

View file

@ -1,8 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:device) do Fabricator(:device) do
access_token access_token { Fabricate.build(:access_token) }
account account { Fabricate.build(:account) }
device_id { Faker::Number.number(digits: 5) } device_id { Faker::Number.number(digits: 5) }
name { Faker::App.name } name { Faker::App.name }
fingerprint_key { Base64.strict_encode64(Ed25519::SigningKey.generate.verify_key.to_bytes) } fingerprint_key { Base64.strict_encode64(Ed25519::SigningKey.generate.verify_key.to_bytes) }

View file

@ -1,5 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:domain_allow) do Fabricator(:domain_allow) do
domain 'MyString' domain { sequence(:domain) { |i| "example#{i}.com" } }
end end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:encrypted_message) do Fabricator(:encrypted_message) do
device device { Fabricate.build(:device) }
from_account { Fabricate(:account) } from_account { Fabricate.build(:account) }
from_device_id { Faker::Number.number(digits: 5) } from_device_id { Faker::Number.number(digits: 5) }
end end

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:favourite) do Fabricator(:favourite) do
account account { Fabricate.build(:account) }
status status { Fabricate.build(:status) }
end end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:featured_tag) do Fabricator(:featured_tag) do
account account { Fabricate.build(:account) }
tag tag { Fabricate.build(:tag) }
name { sequence(:name) { |i| "Tag#{i}" } } name { sequence(:name) { |i| "Tag#{i}" } }
end end

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:follow) do Fabricator(:follow) do
account account { Fabricate.build(:account) }
target_account { Fabricate(:account) } target_account { Fabricate.build(:account) }
end end

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:follow_request) do Fabricator(:follow_request) do
account account { Fabricate.build(:account) }
target_account { Fabricate(:account, locked: true) } target_account { Fabricate.build(:account, locked: true) }
end end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:identity) do Fabricator(:identity) do
user user { Fabricate.build(:user) }
provider 'MyString' provider 'MyString'
uid 'MyString' uid 'MyString'
end end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:invite) do Fabricator(:invite) do
user user { Fabricate.build(:user) }
expires_at nil expires_at nil
max_uses nil max_uses nil
uses 0 uses 0

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:list) do Fabricator(:list) do
account account { Fabricate.build(:account) }
title 'MyString' title 'MyString'
end end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:login_activity) do Fabricator(:login_activity) do
user user { Fabricate.build(:user) }
authentication_method 'password' authentication_method 'password'
success true success true
failure_reason nil failure_reason nil

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:marker) do Fabricator(:marker) do
user user { Fabricate.build(:user) }
timeline 'home' timeline 'home'
last_read_id 0 last_read_id 0
lock_version 0 lock_version 0

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:media_attachment) do Fabricator(:media_attachment) do
account account { Fabricate.build(:account) }
file do |attrs| file do |attrs|
case attrs[:type] case attrs[:type]

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:mention) do Fabricator(:mention) do
account account { Fabricate.build(:account) }
status status { Fabricate.build(:status) }
end end

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:mute) do Fabricator(:mute) do
account account { Fabricate.build(:account) }
target_account { Fabricate(:account) } target_account { Fabricate.build(:account) }
end end

View file

@ -2,5 +2,5 @@
Fabricator(:notification) do Fabricator(:notification) do
activity fabricator: :status activity fabricator: :status
account account { Fabricate.build(:account) }
end end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:one_time_key) do Fabricator(:one_time_key) do
device device { Fabricate.build(:device) }
key_id { Faker::Alphanumeric.alphanumeric(number: 10) } key_id { Faker::Alphanumeric.alphanumeric(number: 10) }
key { Base64.strict_encode64(Ed25519::SigningKey.generate.verify_key.to_bytes) } key { Base64.strict_encode64(Ed25519::SigningKey.generate.verify_key.to_bytes) }

View file

@ -1,8 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:poll) do Fabricator(:poll) do
account account { Fabricate.build(:account) }
status status { Fabricate.build(:status) }
expires_at { 7.days.from_now } expires_at { 7.days.from_now }
options %w(Foo Bar) options %w(Foo Bar)
multiple false multiple false

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:poll_vote) do Fabricator(:poll_vote) do
account account { Fabricate.build(:account) }
poll poll
choice 0 choice 0
end end

View file

@ -1,8 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:report) do Fabricator(:report) do
account account { Fabricate.build(:account) }
target_account { Fabricate(:account) } target_account { Fabricate.build(:account) }
comment 'You nasty' comment 'You nasty'
action_taken_at nil action_taken_at nil
end end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:report_note) do Fabricator(:report_note) do
report report { Fabricate.build(:report) }
account { Fabricate(:account) } account { Fabricate.build(:account) }
content 'Test Content' content 'Test Content'
end end

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:scheduled_status) do Fabricator(:scheduled_status) do
account account { Fabricate.build(:account) }
scheduled_at { 20.hours.from_now } scheduled_at { 20.hours.from_now }
end end

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:session_activation) do Fabricator(:session_activation) do
user user { Fabricate.build(:user) }
session_id 'MyString' session_id 'MyString'
end end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:status) do Fabricator(:status) do
account account { Fabricate.build(:account) }
text 'Lorem ipsum dolor sit amet' text 'Lorem ipsum dolor sit amet'
after_build do |status| after_build do |status|

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
Fabricator(:status_pin) do Fabricator(:status_pin) do
account account { Fabricate.build(:account) }
status { |attrs| Fabricate(:status, account: attrs[:account], visibility: :public) } status { |attrs| Fabricate.build(:status, account: attrs[:account], visibility: :public) }
end end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
Fabricator(:status_stat) do
status
replies_count '123'
reblogs_count '456'
favourites_count '789'
end

View file

@ -2,5 +2,5 @@
Fabricator(:tag_follow) do Fabricator(:tag_follow) do
tag tag
account account { Fabricate.build(:account) }
end end

View file

@ -4,9 +4,68 @@ require 'rails_helper'
require 'mastodon/cli/cache' require 'mastodon/cli/cache'
describe Mastodon::CLI::Cache do describe Mastodon::CLI::Cache do
let(:cli) { described_class.new }
describe '.exit_on_failure?' do describe '.exit_on_failure?' do
it 'returns true' do it 'returns true' do
expect(described_class.exit_on_failure?).to be true expect(described_class.exit_on_failure?).to be true
end end
end end
describe '#clear' do
before { allow(Rails.cache).to receive(:clear) }
it 'clears the Rails cache' do
expect { cli.invoke(:clear) }.to output(
a_string_including('OK')
).to_stdout
expect(Rails.cache).to have_received(:clear)
end
end
describe '#recount' do
context 'with the `accounts` argument' do
let(:arguments) { ['accounts'] }
let(:account_stat) { Fabricate(:account_stat) }
before do
account_stat.update(statuses_count: 123)
end
it 're-calculates account records in the cache' do
expect { cli.invoke(:recount, arguments) }.to output(
a_string_including('OK')
).to_stdout
expect(account_stat.reload.statuses_count).to be_zero
end
end
context 'with the `statuses` argument' do
let(:arguments) { ['statuses'] }
let(:status_stat) { Fabricate(:status_stat) }
before do
status_stat.update(replies_count: 123)
end
it 're-calculates account records in the cache' do
expect { cli.invoke(:recount, arguments) }.to output(
a_string_including('OK')
).to_stdout
expect(status_stat.reload.replies_count).to be_zero
end
end
context 'with an unknown type' do
let(:arguments) { ['other-type'] }
it 'Exits with an error message' do
expect { cli.invoke(:recount, arguments) }.to output(
a_string_including('Unknown')
).to_stdout.and raise_error(SystemExit)
end
end
end
end end

View file

@ -4,9 +4,65 @@ require 'rails_helper'
require 'mastodon/cli/feeds' require 'mastodon/cli/feeds'
describe Mastodon::CLI::Feeds do describe Mastodon::CLI::Feeds do
let(:cli) { described_class.new }
describe '.exit_on_failure?' do describe '.exit_on_failure?' do
it 'returns true' do it 'returns true' do
expect(described_class.exit_on_failure?).to be true expect(described_class.exit_on_failure?).to be true
end end
end end
describe '#build' do
before { Fabricate(:account) }
context 'with --all option' do
let(:options) { { all: true } }
it 'regenerates feeds for all accounts' do
expect { cli.invoke(:build, [], options) }.to output(
a_string_including('Regenerated feeds')
).to_stdout
end
end
context 'with a username' do
before { Fabricate(:account, username: 'alice') }
let(:arguments) { ['alice'] }
it 'regenerates feeds for the account' do
expect { cli.invoke(:build, arguments) }.to output(
a_string_including('OK')
).to_stdout
end
end
context 'with invalid username' do
let(:arguments) { ['invalid-username'] }
it 'displays an error and exits' do
expect { cli.invoke(:build, arguments) }.to output(
a_string_including('No such account')
).to_stdout.and raise_error(SystemExit)
end
end
end
describe '#clear' do
before do
allow(redis).to receive(:del).with(key_namespace)
end
it 'clears the redis `feed:*` namespace' do
expect { cli.invoke(:clear) }.to output(
a_string_including('OK')
).to_stdout
expect(redis).to have_received(:del).with(key_namespace).once
end
def key_namespace
redis.keys('feed:*')
end
end
end end

View file

@ -245,17 +245,44 @@ RSpec.describe Form::Import do
expect(account.bulk_imports.first.rows.pluck(:data)).to match_array(expected_rows) expect(account.bulk_imports.first.rows.pluck(:data)).to match_array(expected_rows)
end end
it 'creates a BulkImport with expected attributes' do context 'with a BulkImport' do
bulk_import = account.bulk_imports.first let(:bulk_import) { account.bulk_imports.first }
expect(bulk_import).to_not be_nil
expect(bulk_import.type.to_sym).to eq subject.type.to_sym it 'creates a non-nil bulk import' do
expect(bulk_import.original_filename).to eq subject.data.original_filename expect(bulk_import).to_not be_nil
expect(bulk_import.likely_mismatched?).to eq subject.likely_mismatched? end
expect(bulk_import.overwrite?).to eq !!subject.overwrite # rubocop:disable Style/DoubleNegation
expect(bulk_import.processed_items).to eq 0 it 'matches the subjects type' do
expect(bulk_import.imported_items).to eq 0 expect(bulk_import.type.to_sym).to eq subject.type.to_sym
expect(bulk_import.total_items).to eq bulk_import.rows.count end
expect(bulk_import.unconfirmed?).to be true
it 'matches the subjects original filename' do
expect(bulk_import.original_filename).to eq subject.data.original_filename
end
it 'matches the subjects likely_mismatched? value' do
expect(bulk_import.likely_mismatched?).to eq subject.likely_mismatched?
end
it 'matches the subject overwrite value' do
expect(bulk_import.overwrite?).to eq !!subject.overwrite # rubocop:disable Style/DoubleNegation
end
it 'has zero processed items' do
expect(bulk_import.processed_items).to eq 0
end
it 'has zero imported items' do
expect(bulk_import.imported_items).to eq 0
end
it 'has a correct total_items value' do
expect(bulk_import.total_items).to eq bulk_import.rows.count
end
it 'defaults to unconfirmed true' do
expect(bulk_import.unconfirmed?).to be true
end
end end
end end

View file

@ -2,7 +2,7 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe MediaAttachment do RSpec.describe MediaAttachment, paperclip_processing: true do
describe 'local?' do describe 'local?' do
subject { media_attachment.local? } subject { media_attachment.local? }

View file

@ -99,73 +99,87 @@ RSpec.describe Notification do
] ]
end end
it 'preloads target status' do context 'with a preloaded target status' do
# mention it 'preloads mention' do
expect(subject[0].type).to eq :mention expect(subject[0].type).to eq :mention
expect(subject[0].association(:mention)).to be_loaded expect(subject[0].association(:mention)).to be_loaded
expect(subject[0].mention.association(:status)).to be_loaded expect(subject[0].mention.association(:status)).to be_loaded
end
# status it 'preloads status' do
expect(subject[1].type).to eq :status expect(subject[1].type).to eq :status
expect(subject[1].association(:status)).to be_loaded expect(subject[1].association(:status)).to be_loaded
end
# reblog it 'preloads reblog' do
expect(subject[2].type).to eq :reblog expect(subject[2].type).to eq :reblog
expect(subject[2].association(:status)).to be_loaded expect(subject[2].association(:status)).to be_loaded
expect(subject[2].status.association(:reblog)).to be_loaded expect(subject[2].status.association(:reblog)).to be_loaded
end
# follow: nothing it 'preloads follow as nil' do
expect(subject[3].type).to eq :follow expect(subject[3].type).to eq :follow
expect(subject[3].target_status).to be_nil expect(subject[3].target_status).to be_nil
end
# follow_request: nothing it 'preloads follow_request as nill' do
expect(subject[4].type).to eq :follow_request expect(subject[4].type).to eq :follow_request
expect(subject[4].target_status).to be_nil expect(subject[4].target_status).to be_nil
end
# favourite it 'preloads favourite' do
expect(subject[5].type).to eq :favourite expect(subject[5].type).to eq :favourite
expect(subject[5].association(:favourite)).to be_loaded expect(subject[5].association(:favourite)).to be_loaded
expect(subject[5].favourite.association(:status)).to be_loaded expect(subject[5].favourite.association(:status)).to be_loaded
end
# poll it 'preloads poll' do
expect(subject[6].type).to eq :poll expect(subject[6].type).to eq :poll
expect(subject[6].association(:poll)).to be_loaded expect(subject[6].association(:poll)).to be_loaded
expect(subject[6].poll.association(:status)).to be_loaded expect(subject[6].poll.association(:status)).to be_loaded
end
end end
it 'replaces to cached status' do context 'with a cached status' do
# mention it 'replaces mention' do
expect(subject[0].type).to eq :mention expect(subject[0].type).to eq :mention
expect(subject[0].target_status.association(:account)).to be_loaded expect(subject[0].target_status.association(:account)).to be_loaded
expect(subject[0].target_status).to eq mention.status expect(subject[0].target_status).to eq mention.status
end
# status it 'replaces status' do
expect(subject[1].type).to eq :status expect(subject[1].type).to eq :status
expect(subject[1].target_status.association(:account)).to be_loaded expect(subject[1].target_status.association(:account)).to be_loaded
expect(subject[1].target_status).to eq status expect(subject[1].target_status).to eq status
end
# reblog it 'replaces reblog' do
expect(subject[2].type).to eq :reblog expect(subject[2].type).to eq :reblog
expect(subject[2].target_status.association(:account)).to be_loaded expect(subject[2].target_status.association(:account)).to be_loaded
expect(subject[2].target_status).to eq reblog.reblog expect(subject[2].target_status).to eq reblog.reblog
end
# follow: nothing it 'replaces follow' do
expect(subject[3].type).to eq :follow expect(subject[3].type).to eq :follow
expect(subject[3].target_status).to be_nil expect(subject[3].target_status).to be_nil
end
# follow_request: nothing it 'replaces follow_request' do
expect(subject[4].type).to eq :follow_request expect(subject[4].type).to eq :follow_request
expect(subject[4].target_status).to be_nil expect(subject[4].target_status).to be_nil
end
# favourite it 'replaces favourite' do
expect(subject[5].type).to eq :favourite expect(subject[5].type).to eq :favourite
expect(subject[5].target_status.association(:account)).to be_loaded expect(subject[5].target_status.association(:account)).to be_loaded
expect(subject[5].target_status).to eq favourite.status expect(subject[5].target_status).to eq favourite.status
end
# poll it 'replaces poll' do
expect(subject[6].type).to eq :poll expect(subject[6].type).to eq :poll
expect(subject[6].target_status.association(:account)).to be_loaded expect(subject[6].target_status.association(:account)).to be_loaded
expect(subject[6].target_status).to eq poll.status expect(subject[6].target_status).to eq poll.status
end
end end
end end
end end

View file

@ -79,6 +79,7 @@ RSpec.configure do |config|
config.before :each, type: :cli do config.before :each, type: :cli do
stub_stdout stub_stdout
stub_reset_connection_pools
end end
config.before :each, type: :feature do config.before :each, type: :feature do
@ -94,6 +95,12 @@ RSpec.configure do |config|
stub_jsonld_contexts! stub_jsonld_contexts!
end end
config.before(:each) do |example|
unless example.metadata[:paperclip_processing]
allow_any_instance_of(Paperclip::Attachment).to receive(:post_process).and_return(true) # rubocop:disable RSpec/AnyInstance
end
end
config.after :each do config.after :each do
Rails.cache.clear Rails.cache.clear
redis.del(redis.keys) redis.del(redis.keys)
@ -115,9 +122,20 @@ def attachment_fixture(name)
end end
def stub_stdout def stub_stdout
# TODO: Is there a bettery way to:
# - Avoid CLI command output being printed out
# - Allow rspec to assert things against STDOUT
# - Avoid disabling stdout for other desirable output (deprecation warnings, for example)
allow($stdout).to receive(:write) allow($stdout).to receive(:write)
end end
def stub_reset_connection_pools
# TODO: Is there a better way to correctly run specs without stubbing this?
# (Avoids reset_connection_pools! in test env)
allow(ActiveRecord::Base).to receive(:establish_connection)
allow(RedisConfiguration).to receive(:establish_pool)
end
def stub_jsonld_contexts! def stub_jsonld_contexts!
stub_request(:get, 'https://www.w3.org/ns/activitystreams').to_return(request_fixture('json-ld.activitystreams.txt')) stub_request(:get, 'https://www.w3.org/ns/activitystreams').to_return(request_fixture('json-ld.activitystreams.txt'))
stub_request(:get, 'https://w3id.org/identity/v1').to_return(request_fixture('json-ld.identity.txt')) stub_request(:get, 'https://w3id.org/identity/v1').to_return(request_fixture('json-ld.identity.txt'))

View file

@ -0,0 +1,53 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'GET /api/v1/accounts/{account_id}' do
it 'returns account entity as 200 OK' do
account = Fabricate(:account)
get "/api/v1/accounts/#{account.id}"
aggregate_failures do
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(account.id.to_s)
end
end
it 'returns 404 if account not found' do
get '/api/v1/accounts/1'
aggregate_failures do
expect(response).to have_http_status(404)
expect(body_as_json[:error]).to eq('Record not found')
end
end
context 'when with token' do
it 'returns account entity as 200 OK if token is valid' do
account = Fabricate(:account)
user = Fabricate(:user, account: account)
token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts').token
get "/api/v1/accounts/#{account.id}", headers: { Authorization: "Bearer #{token}" }
aggregate_failures do
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(account.id.to_s)
end
end
it 'returns 403 if scope of token is invalid' do
account = Fabricate(:account)
user = Fabricate(:user, account: account)
token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:statuses').token
get "/api/v1/accounts/#{account.id}", headers: { Authorization: "Bearer #{token}" }
aggregate_failures do
expect(response).to have_http_status(403)
expect(body_as_json[:error]).to eq('This action is outside the authorized scopes')
end
end
end
end

View file

@ -0,0 +1,305 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Canonical Email Blocks' do
let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'admin:read:canonical_email_blocks admin:write:canonical_email_blocks' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
shared_examples 'forbidden for wrong scope' do |wrong_scope|
let(:scopes) { wrong_scope }
it 'returns http forbidden' do
subject
expect(response).to have_http_status(403)
end
end
shared_examples 'forbidden for wrong role' do |wrong_role|
let(:role) { UserRole.find_by(name: wrong_role) }
it 'returns http forbidden' do
subject
expect(response).to have_http_status(403)
end
end
describe 'GET /api/v1/admin/canonical_email_blocks' do
subject do
get '/api/v1/admin/canonical_email_blocks', headers: headers, params: params
end
let(:params) { {} }
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
context 'when there is no canonical email block' do
it 'returns an empty list' do
subject
expect(body_as_json).to be_empty
end
end
context 'when there are canonical email blocks' do
let!(:canonical_email_blocks) { Fabricate.times(5, :canonical_email_block) }
let(:expected_email_hashes) { canonical_email_blocks.pluck(:canonical_email_hash) }
it 'returns the correct canonical email hashes' do
subject
expect(body_as_json.pluck(:canonical_email_hash)).to match_array(expected_email_hashes)
end
context 'with limit param' do
let(:params) { { limit: 2 } }
it 'returns only the requested number of canonical email blocks' do
subject
expect(body_as_json.size).to eq(params[:limit])
end
end
context 'with since_id param' do
let(:params) { { since_id: canonical_email_blocks[1].id } }
it 'returns only the canonical email blocks after since_id' do
subject
canonical_email_blocks_ids = canonical_email_blocks.pluck(:id).map(&:to_s)
expect(body_as_json.pluck(:id)).to match_array(canonical_email_blocks_ids[2..])
end
end
context 'with max_id param' do
let(:params) { { max_id: canonical_email_blocks[3].id } }
it 'returns only the canonical email blocks before max_id' do
subject
canonical_email_blocks_ids = canonical_email_blocks.pluck(:id).map(&:to_s)
expect(body_as_json.pluck(:id)).to match_array(canonical_email_blocks_ids[..2])
end
end
end
end
describe 'GET /api/v1/admin/canonical_email_blocks/:id' do
subject do
get "/api/v1/admin/canonical_email_blocks/#{canonical_email_block.id}", headers: headers
end
let!(:canonical_email_block) { Fabricate(:canonical_email_block) }
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
context 'when the requested canonical email block exists' do
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the requested canonical email block data correctly' do
subject
json = body_as_json
expect(json[:id]).to eq(canonical_email_block.id.to_s)
expect(json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash)
end
end
context 'when the requested canonical block does not exist' do
it 'returns http not found' do
get '/api/v1/admin/canonical_email_blocks/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
describe 'POST /api/v1/admin/canonical_email_blocks/test' do
subject do
post '/api/v1/admin/canonical_email_blocks/test', headers: headers, params: params
end
let(:params) { { email: 'email@example.com' } }
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
context 'when the required email param is not provided' do
let(:params) { {} }
it 'returns http bad request' do
subject
expect(response).to have_http_status(400)
end
end
context 'when the required email param is provided' do
context 'when there is a matching canonical email block' do
let!(:canonical_email_block) { CanonicalEmailBlock.create(params) }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the expected canonical email hash' do
subject
expect(body_as_json[0][:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash)
end
end
context 'when there is no matching canonical email block' do
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns an empty list' do
subject
expect(body_as_json).to be_empty
end
end
end
end
describe 'POST /api/v1/admin/canonical_email_blocks' do
subject do
post '/api/v1/admin/canonical_email_blocks', headers: headers, params: params
end
let(:params) { { email: 'example@email.com' } }
let(:canonical_email_block) { CanonicalEmailBlock.new(email: params[:email]) }
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the canonical_email_hash correctly' do
subject
expect(body_as_json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash)
end
context 'when the required email param is not provided' do
let(:params) { {} }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'when the canonical_email_hash param is provided instead of email' do
let(:params) { { canonical_email_hash: 'dd501ce4e6b08698f19df96f2f15737e48a75660b1fa79b6ff58ea25ee4851a4' } }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the correct canonical_email_hash' do
subject
expect(body_as_json[:canonical_email_hash]).to eq(params[:canonical_email_hash])
end
end
context 'when both email and canonical_email_hash params are provided' do
let(:params) { { email: 'example@email.com', canonical_email_hash: 'dd501ce4e6b08698f19df96f2f15737e48a75660b1fa79b6ff58ea25ee4851a4' } }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'ignores the canonical_email_hash param' do
subject
expect(body_as_json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash)
end
end
context 'when the given canonical email was already blocked' do
before do
canonical_email_block.save
end
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
end
describe 'DELETE /api/v1/admin/canonical_email_blocks/:id' do
subject do
delete "/api/v1/admin/canonical_email_blocks/#{canonical_email_block.id}", headers: headers
end
let!(:canonical_email_block) { Fabricate(:canonical_email_block) }
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'deletes the canonical email block' do
subject
expect(CanonicalEmailBlock.find_by(id: canonical_email_block.id)).to be_nil
end
context 'when the canonical email block is not found' do
it 'returns http not found' do
delete '/api/v1/admin/canonical_email_blocks/0', headers: headers
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -0,0 +1,214 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Domain Allows' do
let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:scopes) { 'admin:read admin:write' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
shared_examples 'forbidden for wrong scope' do |wrong_scope|
let(:scopes) { wrong_scope }
it 'returns http forbidden' do
subject
expect(response).to have_http_status(403)
end
end
shared_examples 'forbidden for wrong role' do |wrong_role|
let(:role) { UserRole.find_by(name: wrong_role) }
it 'returns http forbidden' do
subject
expect(response).to have_http_status(403)
end
end
describe 'GET /api/v1/admin/domain_allows' do
subject do
get '/api/v1/admin/domain_allows', headers: headers, params: params
end
let(:params) { {} }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
context 'when there is no allowed domains' do
it 'returns an empty body' do
subject
expect(body_as_json).to be_empty
end
end
context 'when there are allowed domains' do
let!(:domain_allows) { Fabricate.times(5, :domain_allow) }
let(:expected_response) do
domain_allows.map do |domain_allow|
{
id: domain_allow.id.to_s,
domain: domain_allow.domain,
created_at: domain_allow.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
}
end
end
it 'returns the correct allowed domains' do
subject
expect(body_as_json).to match_array(expected_response)
end
context 'with limit param' do
let(:params) { { limit: 2 } }
it 'returns only the requested number of allowed domains' do
subject
expect(body_as_json.size).to eq(params[:limit])
end
end
end
end
describe 'GET /api/v1/admin/domain_allows/:id' do
subject do
get "/api/v1/admin/domain_allows/#{domain_allow.id}", headers: headers
end
let!(:domain_allow) { Fabricate(:domain_allow) }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the expected allowed domain name' do
subject
expect(body_as_json[:domain]).to eq domain_allow.domain
end
context 'when the requested allowed domain does not exist' do
it 'returns http not found' do
get '/api/v1/admin/domain_allows/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
describe 'POST /api/v1/admin/domain_allows' do
subject do
post '/api/v1/admin/domain_allows', headers: headers, params: params
end
let(:params) { { domain: 'foo.bar.com' } }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
context 'with a valid domain name' do
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the expected domain name' do
subject
expect(body_as_json[:domain]).to eq 'foo.bar.com'
end
it 'creates a domain allow' do
subject
expect(DomainAllow.find_by(domain: 'foo.bar.com')).to be_present
end
end
context 'with invalid domain name' do
let(:params) { 'foo bar' }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'when domain name is not specified' do
let(:params) { {} }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'when the domain is already allowed' do
before do
DomainAllow.create(params)
end
it 'returns the existing allowed domain name' do
subject
expect(body_as_json[:domain]).to eq(params[:domain])
end
end
end
describe 'DELETE /api/v1/admin/domain_allows/:id' do
subject do
delete "/api/v1/admin/domain_allows/#{domain_allow.id}", headers: headers
end
let!(:domain_allow) { Fabricate(:domain_allow) }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'deletes the allowed domain' do
subject
expect(DomainAllow.find_by(id: domain_allow.id)).to be_nil
end
context 'when the allowed domain does not exist' do
it 'returns http not found' do
delete '/api/v1/admin/domain_allows/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -0,0 +1,284 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Domain Blocks' do
let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:scopes) { 'admin:read:domain_blocks admin:write:domain_blocks' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
shared_examples 'forbidden for wrong scope' do |wrong_scope|
let(:scopes) { wrong_scope }
it 'returns http forbidden' do
subject
expect(response).to have_http_status(403)
end
end
shared_examples 'forbidden for wrong role' do |wrong_role|
let(:role) { UserRole.find_by(name: wrong_role) }
it 'returns http forbidden' do
subject
expect(response).to have_http_status(403)
end
end
describe 'GET /api/v1/admin/domain_blocks' do
subject do
get '/api/v1/admin/domain_blocks', headers: headers, params: params
end
let(:params) { {} }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
context 'when there are no domain blocks' do
it 'returns an empty list' do
subject
expect(body_as_json).to be_empty
end
end
context 'when there are domain blocks' do
let!(:domain_blocks) do
[
Fabricate(:domain_block, severity: :silence, reject_media: true),
Fabricate(:domain_block, severity: :suspend, obfuscate: true),
Fabricate(:domain_block, severity: :noop, reject_reports: true),
Fabricate(:domain_block, public_comment: 'Spam'),
Fabricate(:domain_block, private_comment: 'Spam'),
]
end
let(:expected_responde) do
domain_blocks.map do |domain_block|
{
id: domain_block.id.to_s,
domain: domain_block.domain,
created_at: domain_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
severity: domain_block.severity.to_s,
reject_media: domain_block.reject_media,
reject_reports: domain_block.reject_reports,
private_comment: domain_block.private_comment,
public_comment: domain_block.public_comment,
obfuscate: domain_block.obfuscate,
}
end
end
it 'returns the expected domain blocks' do
subject
expect(body_as_json).to match_array(expected_responde)
end
context 'with limit param' do
let(:params) { { limit: 2 } }
it 'returns only the requested number of domain blocks' do
subject
expect(body_as_json.size).to eq(params[:limit])
end
end
end
end
describe 'GET /api/v1/admin/domain_blocks/:id' do
subject do
get "/api/v1/admin/domain_blocks/#{domain_block.id}", headers: headers
end
let!(:domain_block) { Fabricate(:domain_block) }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the expected domain block content' do
subject
expect(body_as_json).to eq(
{
id: domain_block.id.to_s,
domain: domain_block.domain,
created_at: domain_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
severity: domain_block.severity.to_s,
reject_media: domain_block.reject_media,
reject_reports: domain_block.reject_reports,
private_comment: domain_block.private_comment,
public_comment: domain_block.public_comment,
obfuscate: domain_block.obfuscate,
}
)
end
context 'when the requested domain block does not exist' do
it 'returns http not found' do
get '/api/v1/admin/domain_blocks/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
describe 'POST /api/v1/admin/domain_blocks' do
subject do
post '/api/v1/admin/domain_blocks', headers: headers, params: params
end
let(:params) { { domain: 'foo.bar.com', severity: :silence } }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns expected domain name and severity' do
subject
body = body_as_json
expect(body).to match a_hash_including(
{
domain: 'foo.bar.com',
severity: 'silence',
}
)
end
it 'creates a domain block' do
subject
expect(DomainBlock.find_by(domain: 'foo.bar.com')).to be_present
end
context 'when a stricter domain block already exists' do
before do
Fabricate(:domain_block, domain: 'bar.com', severity: :suspend)
end
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
it 'returns existing domain block in error' do
subject
expect(body_as_json[:existing_domain_block][:domain]).to eq('bar.com')
end
end
context 'when given domain name is invalid' do
let(:params) { { domain: 'foo bar', severity: :silence } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
end
describe 'PUT /api/v1/admin/domain_blocks/:id' do
subject do
put "/api/v1/admin/domain_blocks/#{domain_block.id}", headers: headers, params: params
end
let!(:domain_block) { Fabricate(:domain_block, domain: 'example.com', severity: :silence) }
let(:params) { { domain: 'example.com', severity: 'suspend' } }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the updated domain block' do
subject
expect(body_as_json).to match a_hash_including(
{
id: domain_block.id.to_s,
domain: domain_block.domain,
severity: 'suspend',
}
)
end
it 'updates the block severity' do
expect { subject }.to change { domain_block.reload.severity }.from('silence').to('suspend')
end
context 'when domain block does not exist' do
it 'returns http not found' do
put '/api/v1/admin/domain_blocks/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
describe 'DELETE /api/v1/admin/domain_blocks/:id' do
subject do
delete "/api/v1/admin/domain_blocks/#{domain_block.id}", headers: headers
end
let!(:domain_block) { Fabricate(:domain_block) }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'deletes the domain block' do
subject
expect(DomainBlock.find_by(id: domain_block.id)).to be_nil
end
context 'when domain block does not exist' do
it 'returns http not found' do
delete '/api/v1/admin/domain_blocks/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -2,23 +2,20 @@
require 'rails_helper' require 'rails_helper'
describe Api::V1::Admin::EmailDomainBlocksController do RSpec.describe 'Email Domain Blocks' do
render_views
let(:role) { UserRole.find_by(name: 'Admin') } let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) } let(:user) { Fabricate(:user, role: role) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:account) { Fabricate(:account) } let(:account) { Fabricate(:account) }
let(:scopes) { 'admin:read:email_domain_blocks admin:write:email_domain_blocks' } let(:scopes) { 'admin:read:email_domain_blocks admin:write:email_domain_blocks' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
shared_examples 'forbidden for wrong scope' do |wrong_scope| shared_examples 'forbidden for wrong scope' do |wrong_scope|
let(:scopes) { wrong_scope } let(:scopes) { wrong_scope }
it 'returns http forbidden' do it 'returns http forbidden' do
subject
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
end end
@ -27,65 +24,54 @@ describe Api::V1::Admin::EmailDomainBlocksController do
let(:role) { UserRole.find_by(name: wrong_role) } let(:role) { UserRole.find_by(name: wrong_role) }
it 'returns http forbidden' do it 'returns http forbidden' do
subject
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
end end
describe 'GET #index' do describe 'GET /api/v1/admin/email_domain_blocks' do
context 'with wrong scope' do subject do
before do get '/api/v1/admin/email_domain_blocks', headers: headers, params: params
get :index
end
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
end end
context 'with wrong role' do let(:params) { {} }
before do
get :index
end
it_behaves_like 'forbidden for wrong role', '' it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', 'Moderator' it_behaves_like 'forbidden for wrong role', ''
end it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do it 'returns http success' do
get :index subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
context 'when there is no email domain block' do context 'when there is no email domain block' do
it 'returns an empty list' do it 'returns an empty list' do
get :index subject
json = body_as_json expect(body_as_json).to be_empty
expect(json).to be_empty
end end
end end
context 'when there are email domain blocks' do context 'when there are email domain blocks' do
let!(:email_domain_blocks) { Fabricate.times(5, :email_domain_block) } let!(:email_domain_blocks) { Fabricate.times(5, :email_domain_block) }
let(:blocked_email_domains) { email_domain_blocks.pluck(:domain) } let(:blocked_email_domains) { email_domain_blocks.pluck(:domain) }
it 'return the correct blocked email domains' do it 'return the correct blocked email domains' do
get :index subject
json = body_as_json expect(body_as_json.pluck(:domain)).to match_array(blocked_email_domains)
expect(json.pluck(:domain)).to match_array(blocked_email_domains)
end end
context 'with limit param' do context 'with limit param' do
let(:params) { { limit: 2 } } let(:params) { { limit: 2 } }
it 'returns only the requested number of email domain blocks' do it 'returns only the requested number of email domain blocks' do
get :index, params: params subject
json = body_as_json expect(body_as_json.size).to eq(params[:limit])
expect(json.size).to eq(params[:limit])
end end
end end
@ -93,12 +79,11 @@ describe Api::V1::Admin::EmailDomainBlocksController do
let(:params) { { since_id: email_domain_blocks[1].id } } let(:params) { { since_id: email_domain_blocks[1].id } }
it 'returns only the email domain blocks after since_id' do it 'returns only the email domain blocks after since_id' do
get :index, params: params subject
email_domain_blocks_ids = email_domain_blocks.pluck(:id).map(&:to_s) email_domain_blocks_ids = email_domain_blocks.pluck(:id).map(&:to_s)
json = body_as_json
expect(json.pluck(:id)).to match_array(email_domain_blocks_ids[2..]) expect(body_as_json.pluck(:id)).to match_array(email_domain_blocks_ids[2..])
end end
end end
@ -106,102 +91,78 @@ describe Api::V1::Admin::EmailDomainBlocksController do
let(:params) { { max_id: email_domain_blocks[3].id } } let(:params) { { max_id: email_domain_blocks[3].id } }
it 'returns only the email domain blocks before max_id' do it 'returns only the email domain blocks before max_id' do
get :index, params: params subject
email_domain_blocks_ids = email_domain_blocks.pluck(:id).map(&:to_s) email_domain_blocks_ids = email_domain_blocks.pluck(:id).map(&:to_s)
json = body_as_json
expect(json.pluck(:id)).to match_array(email_domain_blocks_ids[..2]) expect(body_as_json.pluck(:id)).to match_array(email_domain_blocks_ids[..2])
end end
end end
end end
end end
describe 'GET #show' do describe 'GET /api/v1/admin/email_domain_blocks/:id' do
subject do
get "/api/v1/admin/email_domain_blocks/#{email_domain_block.id}", headers: headers
end
let!(:email_domain_block) { Fabricate(:email_domain_block) } let!(:email_domain_block) { Fabricate(:email_domain_block) }
let(:params) { { id: email_domain_block.id } }
context 'with wrong scope' do it_behaves_like 'forbidden for wrong scope', 'read:statuses'
before do it_behaves_like 'forbidden for wrong role', ''
get :show, params: params it_behaves_like 'forbidden for wrong role', 'Moderator'
end
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
end
context 'with wrong role' do
before do
get :show, params: params
end
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
end
context 'when email domain block exists' do context 'when email domain block exists' do
it 'returns http success' do it 'returns http success' do
get :show, params: params subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
it 'returns the correct blocked domain' do it 'returns the correct blocked domain' do
get :show, params: params subject
json = body_as_json expect(body_as_json[:domain]).to eq(email_domain_block.domain)
expect(json[:domain]).to eq(email_domain_block.domain)
end end
end end
context 'when email domain block does not exist' do context 'when email domain block does not exist' do
it 'returns http not found' do it 'returns http not found' do
get :show, params: { id: 0 } get '/api/v1/admin/email_domain_blocks/-1', headers: headers
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
end end
end end
describe 'POST #create' do describe 'POST /api/v1/admin/email_domain_blocks' do
subject do
post '/api/v1/admin/email_domain_blocks', headers: headers, params: params
end
let(:params) { { domain: 'example.com' } } let(:params) { { domain: 'example.com' } }
context 'with wrong scope' do it_behaves_like 'forbidden for wrong scope', 'read:statuses'
before do it_behaves_like 'forbidden for wrong role', ''
post :create, params: params it_behaves_like 'forbidden for wrong role', 'Moderator'
end
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
end
context 'with wrong role' do
before do
post :create, params: params
end
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
end
it 'returns http success' do it 'returns http success' do
post :create, params: params subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
it 'returns the correct blocked email domain' do it 'returns the correct blocked email domain' do
post :create, params: params subject
json = body_as_json expect(body_as_json[:domain]).to eq(params[:domain])
expect(json[:domain]).to eq(params[:domain])
end end
context 'when domain param is not provided' do context 'when domain param is not provided' do
let(:params) { { domain: '' } } let(:params) { { domain: '' } }
it 'returns http unprocessable entity' do it 'returns http unprocessable entity' do
post :create, params: params subject
expect(response).to have_http_status(422) expect(response).to have_http_status(422)
end end
@ -211,7 +172,7 @@ describe Api::V1::Admin::EmailDomainBlocksController do
let(:params) { { domain: 'do\uD800.com' } } let(:params) { { domain: 'do\uD800.com' } }
it 'returns http unprocessable entity' do it 'returns http unprocessable entity' do
post :create, params: params subject
expect(response).to have_http_status(422) expect(response).to have_http_status(422)
end end
@ -223,59 +184,45 @@ describe Api::V1::Admin::EmailDomainBlocksController do
end end
it 'returns http unprocessable entity' do it 'returns http unprocessable entity' do
post :create, params: params subject
expect(response).to have_http_status(422) expect(response).to have_http_status(422)
end end
end end
end end
describe 'DELETE #destroy' do describe 'DELETE /api/v1/admin/email_domain_blocks' do
subject do
delete "/api/v1/admin/email_domain_blocks/#{email_domain_block.id}", headers: headers
end
let!(:email_domain_block) { Fabricate(:email_domain_block) } let!(:email_domain_block) { Fabricate(:email_domain_block) }
let(:params) { { id: email_domain_block.id } }
context 'with wrong scope' do it_behaves_like 'forbidden for wrong scope', 'read:statuses'
before do it_behaves_like 'forbidden for wrong role', ''
delete :destroy, params: params it_behaves_like 'forbidden for wrong role', 'Moderator'
end
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
end
context 'with wrong role' do
before do
delete :destroy, params: params
end
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
end
it 'returns http success' do it 'returns http success' do
delete :destroy, params: params subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
it 'returns an empty body' do it 'returns an empty body' do
delete :destroy, params: params subject
json = body_as_json expect(body_as_json).to be_empty
expect(json).to be_empty
end end
it 'deletes email domain block' do it 'deletes email domain block' do
delete :destroy, params: params subject
email_domain_block = EmailDomainBlock.find_by(id: params[:id]) expect(EmailDomainBlock.find_by(id: email_domain_block.id)).to be_nil
expect(email_domain_block).to be_nil
end end
context 'when email domain block does not exist' do context 'when email domain block does not exist' do
it 'returns http not found' do it 'returns http not found' do
delete :destroy, params: { id: 0 } delete '/api/v1/admin/email_domain_blocks/-1', headers: headers
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end

Some files were not shown because too many files have changed in this diff Show more