Merge commit 'b87bfb8c96c8491f1228e0258d05119f3420db05' into glitch-soc/merge-upstream

master
Claire 2023-12-18 18:34:25 +01:00
commit 46ddaffd40
66 changed files with 1115 additions and 886 deletions

View File

@ -15,6 +15,6 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
RUN gem install foreman
# [Optional] Uncomment this line to install global node packages.
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g yarn" 2>&1
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && corepack enable" 2>&1
COPY welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt

View File

@ -11,6 +11,7 @@ bundle install
git checkout -- Gemfile.lock
# Fetch Javascript dependencies
corepack prepare
yarn install --immutable
# [re]create, migrate, and seed the test database

View File

@ -12,6 +12,7 @@
// If we do not want a package to be grouped with others, we need to set its groupName
// to `null` after any other rule set it to something.
dependencyDashboardHeader: 'This issue lists Renovate updates and detected dependencies. Read the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) docs to learn more. Before approving any upgrade: read the description and comments in the [`renovate.json5` file](https://github.com/mastodon/mastodon/blob/main/.github/renovate.json5).',
postUpdateOptions: ['yarnDedupeHighest'],
packageRules: [
{
// Require Dependency Dashboard Approval for major version bumps of these node packages

View File

@ -31,4 +31,3 @@ linters:
- 'app/views/admin/accounts/_buttons.html.haml'
- 'app/views/admin/accounts/_local_account.html.haml'
- 'app/views/admin/roles/_form.html.haml'
- 'app/views/layouts/application.html.haml'

View File

@ -24,15 +24,6 @@ Lint/NonLocalExitFromIterator:
Exclude:
- 'app/helpers/jsonld_helper.rb'
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
Lint/UnusedBlockArgument:
Exclude:
- 'config/initializers/content_security_policy.rb'
- 'config/initializers/doorkeeper.rb'
- 'config/initializers/paperclip.rb'
- 'config/initializers/simple_form.rb'
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
Max: 144
@ -73,26 +64,6 @@ RSpec/AnyInstance:
RSpec/ExampleLength:
Max: 22
# Configuration parameters: AssignmentOnly.
RSpec/InstanceVariable:
Exclude:
- 'spec/controllers/api/v1/streaming_controller_spec.rb'
- 'spec/controllers/auth/confirmations_controller_spec.rb'
- 'spec/controllers/auth/passwords_controller_spec.rb'
- 'spec/controllers/auth/sessions_controller_spec.rb'
- 'spec/controllers/concerns/export_controller_concern_spec.rb'
- 'spec/controllers/home_controller_spec.rb'
- 'spec/controllers/settings/two_factor_authentication/webauthn_credentials_controller_spec.rb'
- 'spec/controllers/statuses_cleanup_controller_spec.rb'
- 'spec/models/concerns/account_finder_concern_spec.rb'
- 'spec/models/concerns/account_interactions_spec.rb'
- 'spec/models/public_feed_spec.rb'
- 'spec/serializers/activitypub/note_serializer_spec.rb'
- 'spec/serializers/activitypub/update_poll_serializer_spec.rb'
- 'spec/services/remove_status_service_spec.rb'
- 'spec/services/search_service_spec.rb'
- 'spec/services/unblock_domain_service_spec.rb'
RSpec/LetSetup:
Exclude:
- 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb'
@ -135,12 +106,6 @@ RSpec/LetSetup:
- 'spec/services/unsuspend_account_service_spec.rb'
- 'spec/workers/scheduler/user_cleanup_scheduler_spec.rb'
RSpec/MessageChain:
Exclude:
- 'spec/models/concerns/remotable_spec.rb'
- 'spec/models/session_activation_spec.rb'
- 'spec/models/setting_spec.rb'
RSpec/MultipleExpectations:
Max: 8
@ -180,11 +145,6 @@ Rails/HasManyOrHasOneDependent:
- 'app/models/user.rb'
- 'app/models/web/push_subscription.rb'
Rails/I18nLocaleTexts:
Exclude:
- 'lib/tasks/mastodon.rake'
- 'spec/helpers/flashes_helper_spec.rb'
# Configuration parameters: Include.
# Include: app/controllers/**/*.rb, app/mailers/**/*.rb
Rails/LexicallyScopedActionFilter:
@ -560,14 +520,6 @@ Style/SingleArgumentDig:
Exclude:
- 'lib/webpacker/manifest_extensions.rb'
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: require_parentheses, require_no_parentheses
Style/StabbyLambdaParentheses:
Exclude:
- 'config/environments/production.rb'
- 'config/initializers/content_security_policy.rb'
# This cop supports safe autocorrection (--autocorrect).
Style/StderrPuts:
Exclude:
@ -626,5 +578,3 @@ Style/TrailingCommaInHashLiteral:
Style/WordArray:
Exclude:
- 'app/helpers/languages_helper.rb'
- 'spec/controllers/settings/imports_controller_spec.rb'
- 'spec/models/form/import_spec.rb'

View File

@ -16,7 +16,7 @@ gem 'dotenv-rails', '~> 2.8'
gem 'aws-sdk-s3', '~> 1.123', require: false
gem 'fog-core', '<= 2.4.0'
gem 'fog-openstack', '~> 0.3', require: false
gem 'fog-openstack', '~> 1.0', require: false
gem 'kt-paperclip', '~> 7.2'
gem 'md-paperclip-azure', '~> 2.2', require: false
gem 'blurhash', '~> 0.1'

View File

@ -263,7 +263,7 @@ GEM
erubi (1.12.0)
et-orbi (1.2.7)
tzinfo
excon (0.100.0)
excon (0.104.0)
fabrication (2.30.0)
faker (3.2.2)
i18n (>= 1.8.11, < 2)
@ -298,19 +298,18 @@ GEM
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
fog-core (2.1.0)
fog-core (2.3.0)
builder
excon (~> 0.58)
formatador (~> 0.2)
excon (~> 0.71)
formatador (>= 0.2, < 2.0)
mime-types
fog-json (1.2.0)
fog-core
multi_json (~> 1.10)
fog-openstack (0.3.10)
fog-core (>= 1.45, <= 2.1.0)
fog-openstack (1.1.0)
fog-core (~> 2.1)
fog-json (>= 1.0)
ipaddress (>= 0.8)
formatador (0.3.0)
formatador (1.1.0)
fugit (1.8.1)
et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4)
@ -370,7 +369,6 @@ GEM
terminal-table (>= 1.5.1)
idn-ruby (0.1.5)
io-console (0.6.0)
ipaddress (0.8.3)
irb (1.8.1)
rdoc
reline (>= 0.3.8)
@ -452,7 +450,7 @@ GEM
memory_profiler (1.0.1)
mime-types (3.5.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2023.0808)
mime-types-data (3.2023.1003)
mini_mime (1.1.5)
mini_portile2 (2.8.5)
minitest (5.20.0)
@ -858,7 +856,7 @@ DEPENDENCIES
fast_blank (~> 1.0)
fastimage
fog-core (<= 2.4.0)
fog-openstack (~> 0.3)
fog-openstack (~> 1.0)
fuubar (~> 2.5)
haml-rails (~> 2.0)
haml_lint

View File

@ -31,6 +31,11 @@ module Admin
private
def batched_ordered_status_edits
@status.edits.reorder(nil).includes(:account, status: [:account]).find_each(order: :asc)
end
helper_method :batched_ordered_status_edits
def admin_status_batch_action_params
params.require(:admin_status_batch_action).permit(status_ids: [])
end

View File

@ -91,6 +91,14 @@ module ApplicationHelper
end
end
def html_title
safe_join(
[content_for(:page_title).to_s.chomp, title]
.select(&:present?),
' - '
)
end
def title
Rails.env.production? ? site_title : "#{site_title} (Dev)"
end

View File

@ -298,5 +298,3 @@ module LanguagesHelper
locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym)
end
end
# rubocop:enable Metrics/ModuleLength

View File

@ -42,4 +42,5 @@ export interface ApiAccountJSON {
suspended?: boolean;
limited?: boolean;
memorial?: boolean;
hide_collections: boolean;
}

View File

@ -45,6 +45,7 @@ const mapStateToProps = (state, { params: { acct, id } }) => {
hasMore: !!state.getIn(['user_lists', 'followers', accountId, 'next']),
isLoading: state.getIn(['user_lists', 'followers', accountId, 'isLoading'], true),
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
hideCollections: state.getIn(['accounts', accountId, 'hide_collections'], false),
hidden: getAccountHidden(state, accountId),
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
};
@ -111,7 +112,7 @@ class Followers extends ImmutablePureComponent {
}, 300, { leading: true });
render () {
const { accountId, accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, suspended, hidden, remote, remoteUrl } = this.props;
const { accountId, accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, suspended, hidden, remote, remoteUrl, hideCollections } = this.props;
if (!isAccount) {
return (
@ -137,6 +138,8 @@ class Followers extends ImmutablePureComponent {
emptyMessage = <LimitedAccountHint accountId={accountId} />;
} else if (blockedBy) {
emptyMessage = <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />;
} else if (hideCollections && accountIds.isEmpty()) {
emptyMessage = <FormattedMessage id='empty_column.account_hides_collections' defaultMessage='This user has chosen to not make this information available' />;
} else if (remote && accountIds.isEmpty()) {
emptyMessage = <RemoteHint url={remoteUrl} />;
} else {

View File

@ -45,6 +45,7 @@ const mapStateToProps = (state, { params: { acct, id } }) => {
hasMore: !!state.getIn(['user_lists', 'following', accountId, 'next']),
isLoading: state.getIn(['user_lists', 'following', accountId, 'isLoading'], true),
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
hideCollections: state.getIn(['accounts', accountId, 'hide_collections'], false),
hidden: getAccountHidden(state, accountId),
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
};
@ -111,7 +112,7 @@ class Following extends ImmutablePureComponent {
}, 300, { leading: true });
render () {
const { accountId, accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, suspended, hidden, remote, remoteUrl } = this.props;
const { accountId, accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, suspended, hidden, remote, remoteUrl, hideCollections } = this.props;
if (!isAccount) {
return (
@ -137,6 +138,8 @@ class Following extends ImmutablePureComponent {
emptyMessage = <LimitedAccountHint accountId={accountId} />;
} else if (blockedBy) {
emptyMessage = <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />;
} else if (hideCollections && accountIds.isEmpty()) {
emptyMessage = <FormattedMessage id='empty_column.account_hides_collections' defaultMessage='This user has chosen to not make this information available' />;
} else if (remote && accountIds.isEmpty()) {
emptyMessage = <RemoteHint url={remoteUrl} />;
} else {

View File

@ -88,7 +88,7 @@
"attachments_list.unprocessed": "(ausstehend)",
"audio.hide": "Audio ausblenden",
"autosuggest_hashtag.per_week": "{count} pro Woche",
"boost_modal.combo": "Drücke {combo}, um das beim nächsten Mal zu überspringen",
"boost_modal.combo": "Mit {combo} wird dieses Fenster beim nächsten Mal nicht mehr angezeigt",
"bundle_column_error.copy_stacktrace": "Fehlerbericht kopieren",
"bundle_column_error.error.body": "Die angeforderte Seite konnte nicht dargestellt werden. Dies könnte auf einen Fehler in unserem Code oder auf ein Browser-Kompatibilitätsproblem zurückzuführen sein.",
"bundle_column_error.error.title": "Oh nein!",

View File

@ -222,6 +222,7 @@
"emoji_button.search_results": "Search results",
"emoji_button.symbols": "Symbols",
"emoji_button.travel": "Travel & Places",
"empty_column.account_hides_collections": "This user has chosen to not make this information available",
"empty_column.account_suspended": "Account suspended",
"empty_column.account_timeline": "No posts here!",
"empty_column.account_unavailable": "Profile unavailable",

View File

@ -62,7 +62,7 @@
"account.share": "שתף את הפרופיל של @{name}",
"account.show_reblogs": "הצג הדהודים מאת @{name}",
"account.statuses_counter": "{count, plural, one {הודעה} two {הודעותיים} many {{count} הודעות} other {{count} הודעות}}",
"account.unblock": "הסר את החסימה של @{name}",
"account.unblock": "להסיר חסימה ל- @{name}",
"account.unblock_domain": "הסירי את החסימה של קהילת {domain}",
"account.unblock_short": "הסר חסימה",
"account.unendorse": "אל תקדם בפרופיל",

View File

@ -222,7 +222,7 @@
"emoji_button.search_results": "Výsledky hľadania",
"emoji_button.symbols": "Symboly",
"emoji_button.travel": "Cestovanie a miesta",
"empty_column.account_suspended": "Účet bol vylúčený",
"empty_column.account_suspended": "Účet bol pozastavený",
"empty_column.account_timeline": "Nie sú tu žiadne príspevky!",
"empty_column.account_unavailable": "Profil nedostupný",
"empty_column.blocks": "Ešte si nikoho nezablokoval/a.",

View File

@ -93,6 +93,7 @@ export const accountDefaultValues: AccountShape = {
memorial: false,
limited: false,
moved: null,
hide_collections: false,
};
const AccountFactory = ImmutableRecord<AccountShape>(accountDefaultValues);

View File

@ -16,12 +16,36 @@ class StatusCacheHydrator
# We take advantage of the fact that some relationships can only occur with an original status, not
# the reblog that wraps it, so we can assume that some values are always false
if payload[:reblog]
hydrate_reblog_payload(payload, account_id)
else
hydrate_non_reblog_payload(payload, account_id)
end
end
private
def hydrate_non_reblog_payload(empty_payload, account_id)
empty_payload.tap do |payload|
payload[:favourited] = Favourite.where(account_id: account_id, status_id: @status.id).exists?
payload[:reblogged] = Status.where(account_id: account_id, reblog_of_id: @status.id).exists?
payload[:muted] = ConversationMute.where(account_id: account_id, conversation_id: @status.conversation_id).exists?
payload[:bookmarked] = Bookmark.where(account_id: account_id, status_id: @status.id).exists?
payload[:pinned] = StatusPin.where(account_id: account_id, status_id: @status.id).exists? if @status.account_id == account_id
payload[:filtered] = mapped_applied_custom_filter(account_id, @status)
if payload[:poll]
payload[:poll][:voted] = @status.account_id == account_id
payload[:poll][:own_votes] = []
end
end
end
def hydrate_reblog_payload(empty_payload, account_id)
empty_payload.tap do |payload|
payload[:muted] = false
payload[:bookmarked] = false
payload[:pinned] = false if @status.account_id == account_id
payload[:filtered] = CustomFilter
.apply_cached_filters(CustomFilter.cached_filters_for(account_id), @status.reblog)
.map { |filter| serialized_filter(filter) }
payload[:filtered] = mapped_applied_custom_filter(account_id, @status.reblog)
# If the reblogged status is being delivered to the author who disabled the display of the application
# used to create the status, we need to hydrate it here too
@ -47,26 +71,14 @@ class StatusCacheHydrator
payload[:favourited] = payload[:reblog][:favourited]
payload[:reblogged] = payload[:reblog][:reblogged]
else
payload[:favourited] = Favourite.where(account_id: account_id, status_id: @status.id).exists?
payload[:reblogged] = Status.where(account_id: account_id, reblog_of_id: @status.id).exists?
payload[:muted] = ConversationMute.where(account_id: account_id, conversation_id: @status.conversation_id).exists?
payload[:bookmarked] = Bookmark.where(account_id: account_id, status_id: @status.id).exists?
payload[:pinned] = StatusPin.where(account_id: account_id, status_id: @status.id).exists? if @status.account_id == account_id
payload[:filtered] = CustomFilter
.apply_cached_filters(CustomFilter.cached_filters_for(account_id), @status)
.map { |filter| serialized_filter(filter) }
if payload[:poll]
payload[:poll][:voted] = @status.account_id == account_id
payload[:poll][:own_votes] = []
end
end
payload
end
private
def mapped_applied_custom_filter(account_id, status)
CustomFilter
.apply_cached_filters(CustomFilter.cached_filters_for(account_id), status)
.map { |filter| serialized_filter(filter) }
end
def serialized_filter(filter)
ActiveModelSerializers::SerializableResource.new(

View File

@ -8,7 +8,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at,
:note, :url, :uri, :avatar, :avatar_static, :header, :header_static,
:followers_count, :following_count, :statuses_count, :last_status_at
:followers_count, :following_count, :statuses_count, :last_status_at, :hide_collections
has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested?

View File

@ -47,7 +47,7 @@
%h3= t('admin.statuses.history')
%ol.history
- @status.edits.reorder(nil).includes(:account, status: [:account]).find_each(order: :asc).with_index do |status_edit, i|
- batched_ordered_status_edits.with_index do |status_edit, i|
%li
.history__entry
%h5

View File

@ -24,7 +24,7 @@
%meta{ name: 'theme-color', content: '#191b22' }/
%meta{ name: 'apple-mobile-web-app-capable', content: 'yes' }/
%title= content_for?(:page_title) ? safe_join([yield(:page_title).chomp.html_safe, title], ' - ') : title
%title= html_title
= javascript_pack_tag "common", crossorigin: 'anonymous'
- if @theme

View File

@ -1,6 +1,6 @@
default: &default
adapter: postgresql
pool: <%= ENV["DB_POOL"] || ENV['MAX_THREADS'] || 5 %>
pool: <%= ENV["DB_POOL"] || (if Sidekiq.server? then Sidekiq[:concurrency] else ENV['MAX_THREADS'] end) || 5 %>
timeout: 5000
connect_timeout: 15
encoding: unicode

View File

@ -44,7 +44,7 @@ Rails.application.configure do
config.force_ssl = true
config.ssl_options = {
redirect: {
exclude: ->request { request.path.start_with?('/health') || request.headers["Host"].end_with?('.onion') || request.headers["Host"].end_with?('.i2p') }
exclude: ->(request) { request.path.start_with?('/health') || request.headers["Host"].end_with?('.onion') || request.headers["Host"].end_with?('.i2p') }
}
}

View File

@ -84,7 +84,7 @@ end
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
# Rails.application.config.content_security_policy_report_only = true
Rails.application.config.content_security_policy_nonce_generator = ->request { SecureRandom.base64(16) }
Rails.application.config.content_security_policy_nonce_generator = ->(_request) { SecureRandom.base64(16) }
Rails.application.config.content_security_policy_nonce_directives = %w(style-src)
@ -109,7 +109,7 @@ Rails.application.reloader.to_prepare do
p.worker_src :none
end
LetterOpenerWeb::LettersController.after_action do |p|
LetterOpenerWeb::LettersController.after_action do
request.content_security_policy_nonce_directives = %w(script-src)
end
end

View File

@ -169,7 +169,7 @@ Doorkeeper.configure do
# Under some circumstances you might want to have applications auto-approved,
# so that the user skips the authorization step.
# For example if dealing with a trusted application.
skip_authorization do |resource_owner, client|
skip_authorization do |_resource_owner, client|
client.application.superapp?
end

View File

@ -11,7 +11,7 @@ Paperclip.interpolates :filename do |attachment, style|
end
end
Paperclip.interpolates :prefix_path do |attachment, style|
Paperclip.interpolates :prefix_path do |attachment, _style|
if attachment.storage_schema_version >= 1 && attachment.instance.respond_to?(:local?) && !attachment.instance.local?
'cache' + File::SEPARATOR
else
@ -19,7 +19,7 @@ Paperclip.interpolates :prefix_path do |attachment, style|
end
end
Paperclip.interpolates :prefix_url do |attachment, style|
Paperclip.interpolates :prefix_url do |attachment, _style|
if attachment.storage_schema_version >= 1 && attachment.instance.respond_to?(:local?) && !attachment.instance.local?
'cache/'
else

View File

@ -175,7 +175,7 @@ SimpleForm.setup do |config|
# config.item_wrapper_class = nil
# How the label text should be generated altogether with the required text.
config.label_text = lambda { |label, required, explicit_label| "#{label} #{required}" }
config.label_text = lambda { |label, required, _explicit_label| "#{label} #{required}" }
# You can define the class to use on all labels. Default is nil.
# config.label_class = nil

View File

@ -1 +1,14 @@
---
lt:
activerecord:
errors:
models:
account:
attributes:
username:
invalid: turi būti tik raidės, skaičiai ir pabraukimai.
reserved: užimtas.
admin/webhook:
attributes:
url:
invalid: nėra tinkamas URL adresas.

View File

@ -556,6 +556,7 @@ be:
total_reported: Скаргі на іх
total_storage: Медыя дадаткі
totals_time_period_hint_html: Паказаныя агульныя значэнні ніжэй уключаюць даныя за ўвесь час.
unknown_instance: На дадзены момант няма запісаў аб гэтым дамене на гэтым серверы.
invites:
deactivate_all: Дэактываваць усё
filter:
@ -1076,6 +1077,14 @@ be:
hint_html: Засталася яшчэ адна рэч! Каб не дапусціць спаму, нам трэба пацвердзіць, што вы чалавек. Разгадайце CAPTCHA ніжэй і націсніце «Працягнуць».
title: Праверка бяспекі
confirmations:
awaiting_review: Ваш электронны адрас пацверджаны! Адміністрацыя %{domain} зараз разглядае вашу рэгістрацыю. Вы атрымаеце паведамленне па электроннай пошце, калі ваш уліковы запіс будзе ўхвалены!
awaiting_review_title: Ваша рэгістрацыя разглядаецца
clicking_this_link: націснуць на гэту спасылку
login_link: увайсці
proceed_to_login_html: Цяпер вы можаце перайсці да %{login_link}.
redirect_to_app_html: Вы павінны былі быць перанакіраваны ў праграму <strong>%{app_name}</strong>. Калі гэтага не адбылося, паспрабуйце %{clicking_this_link} або вярніцеся да праграмы ўручную.
registration_complete: Ваша рэгістрацыя на %{domain} завершана!
welcome_title: Вітаем, %{name}!
wrong_email_hint: Калі гэты адрас электроннай пошты памылковы, вы можаце змяніць яго ў наладах уліковага запісу.
delete_account: Выдаліць уліковы запіс
delete_account_html: Калі вы жадаеце выдаліць ваш уліковы запіс, можаце <a href="%{path}">працягнуць тут</a>. Ад вас будзе запатрабавана пацвярджэнне.
@ -1137,6 +1146,7 @@ be:
functional: Ваш уліковы запіс поўнасцю працуе.
pending: Ваша заяўка разглядаецца нашым супрацоўнікам. Гэта можа заняць некаторы час. Вы атрымаеце электронны ліст, калі заяўка будзе ўхвалена.
redirecting_to: Ваш уліковы запіс неактыўны, бо ў цяперашні час ён перанакіроўваецца на %{acct}.
self_destruct: Паколькі %{domain} зачыняецца, вы атрымаеце толькі абмежаваны доступ да свайго уліковага запісу.
view_strikes: Праглядзець мінулыя папярэджанні для вашага ўліковага запісу
too_fast: Форма адпраўлена занадта хутка, паспрабуйце яшчэ раз.
use_security_key: Выкарыстаеце ключ бяспекі
@ -1622,6 +1632,9 @@ be:
over_daily_limit: Вы перавысілі ліміт ў %{limit} запланаваных на сёння допісаў
over_total_limit: Вы перавысілі ліміт ў %{limit} запланаваных допісаў
too_soon: Запланаваная дата мусіць быць у будучыні
self_destruct:
lead_html: На жаль, дамен <strong>%{domain}</strong> зачыняецца назаўсёды. Калі ў вас быў уліковы запіс, вы не зможаце працягваць выкарыстоўваць яго, але вы ўсё яшчэ можаце запытаць рэзервовае капіраванне вашых даных.
title: Гэты сервер зачыняецца
sessions:
activity: Апошняя актыўнасць
browser: Браўзер

View File

@ -1600,7 +1600,7 @@ ko:
windows: 윈도우
windows_mobile: 윈도우 모바일
windows_phone: 윈도우 폰
revoke: 삭제
revoke: 취소
revoke_success: 세션을 성공적으로 취소하였습니다
title: 세션
view_authentication_history: 내 계정에 대한 인증 이력 보기

View File

@ -232,6 +232,12 @@ lt:
unassign: Nepriskirti
unresolved: Neišspręsti
updated_at: Atnaujinti
roles:
everyone: Numatytieji leidimai
everyone_full_description_html: Tai <strong>bazinis vaidmuo</strong>, turintis įtakos <strong>visiems naudotojams</strong>, net ir tiems, kurie neturi priskirto vaidmens. Visi kiti vaidmenys iš jo paveldi teises.
settings:
domain_blocks:
all: Visiems
statuses:
back_to_account: Atgal į paskyros puslapį
media:
@ -250,6 +256,10 @@ lt:
body: "%{reporter} parašė skundą apie %{target}"
body_remote: Kažkas iš %{domain} parašė skundą apie %{target}
subject: Naujas skundas %{instance} (#%{id})
appearance:
localization:
body: Mastodon verčia savanoriai.
guide_link_text: Visi gali prisidėti.
application_mailer:
notification_preferences: Keisti el pašto parinktis
settings: 'Keisti el pašto parinktis: %{link}'
@ -458,9 +468,9 @@ lt:
private: Tik sekėjams
private_long: Rodyti tik sekėjams
public: Viešas
public_long: Matyti gali visi
public_long: Visi gali matyti
unlisted: Neįtrauktas į sąrašus
unlisted_long: Matyti gali visi, tačiau nėra įtraukta į viešas laiko juostas
unlisted_long: Matyti gali visi, tačiau nėra įtraukti į viešąsias laiko skales
stream_entries:
sensitive_content: Jautrus turinys
themes:
@ -507,4 +517,5 @@ lt:
seamless_external_login: Jūs esate prisijungę per išorini įrenginį, todėl slaptąžodis ir el pašto nustatymai neprieinami.
signed_in_as: 'Prisijungta kaip:'
verification:
hint_html: "<strong>Savo tapatybės patvirtinimas Mastodon skirtas visiems.</strong> Remiantis atviraisiais žiniatinklio standartais, dabar ir visam laikui nemokamas. Viskas, ko tau reikia, yra asmeninė svetainė, pagal kurią žmonės tave atpažįsta. Kai iš savo profilio pateiksi nuorodą į šią svetainę, patikrinsime, ar svetainėje yra nuoroda į tavo profilį, ir parodysime vizualinį indikatorių."
verification: Patvirtinimas

View File

@ -1079,6 +1079,7 @@ ru:
confirmations:
awaiting_review: Ваш адрес электронной почты подтвержден! Сотрудники %{domain} проверяют вашу регистрацию. Вы получите письмо, если они подтвердят вашу учетную запись!
awaiting_review_title: Ваша регистрация проверяется
clicking_this_link: нажатие на эту ссылку
login_link: войти
proceed_to_login_html: Теперь вы можете перейти к %{login_link}.
registration_complete: Ваша регистрация на %{domain} завершена!
@ -1536,7 +1537,7 @@ ru:
update:
subject: "%{name} изменил(а) пост"
notifications:
administration_emails: E-mail уведомления администратора
administration_emails: Уведомления администратора по электронной почте
email_events: События для e-mail уведомлений
email_events_hint: 'Выберите события, для которых вы хотели бы получать уведомления:'
other_settings: Остальные настройки уведомлений
@ -1631,6 +1632,7 @@ ru:
over_total_limit: Вы превысили лимит на %{limit} запланированных постов
too_soon: Запланированная дата должна быть в будущем
self_destruct:
lead_html: К сожалению, <strong>%{domain}</strong> закрывается навсегда. Если вас учётная запись находиться здесь вы не сможете продолжить использовать его, но вы можете запросить резервную копию ваших данных.
title: Этот сервер закрывается
sessions:
activity: Последняя активность

View File

@ -1 +1,45 @@
---
lt:
simple_form:
hints:
account:
discoverable: Tavo vieši įrašai ir profilis gali būti rodomi arba rekomenduojami įvairiose Mastodon vietose, o profilis gali būti siūlomas kitiems naudotojams.
display_name: Tavo pilnas vardas arba smagus vardas.
fields: Tavo pagrindinis puslapis, įvardžiai, amžius, bet kas, ko tik nori.
indexable: Tavo vieši įrašai gali būti rodomi Mastodon paieškos rezultatuose. Žmonės, kurie bendravo su tavo įrašais, gali jų ieškoti nepriklausomai nuo to.
note: 'Gali @paminėti kitus žmones arba #saitažodžius.'
show_collections: Žmonės galės peržiūrėti tavo sekimus ir sekėjus. Žmonės, kuriuos seki, matys, kad juos seki, nepaisant to.
unlocked: Žmonės galės tave sekti nepaprašę patvirtinimo. Panaikink žymėjimą, jei nori peržiūrėti sekimo prašymus ir pasirinkti, ar priimti, ar atmesti naujus sekėjus.
account_warning_preset:
text: Gali naudoti įrašų sintaksę, pavyzdžiui, URL adresus, saitažodus ir paminėjimus
defaults:
header: PNG, GIF arba JPG. Ne daugiau kaip %{size}. Bus sumažintas iki %{dimensions}tšk.
inbox_url: Nukopijuok URL adresą iš pradinio puslapio perdavėjo, kurį nori naudoti
irreversible: Filtruoti įrašai išnyks negrįžtamai, net jei vėliau filtras bus pašalintas
locale: Naudotojo sąsajos kalba, el. laiškai ir stumiamieji pranešimai
password: Naudok bent 8 simbolius
phrase: Bus suderinta, neatsižvelgiant į teksto korpusą arba įrašo turinio įspėjimą
setting_display_media_hide_all: Visada slėpti žiniasklaidą
setting_display_media_show_all: Visada rodyti žiniasklaidą
setting_use_blurhash: Gradientai pagrįsti paslėptų vaizdų spalvomis, tačiau užgožia bet kokias detales
setting_use_pending_items: Slėpti laiko skalės naujienas po paspaudimo, vietoj automatinio kanalo slinkimo
featured_tag:
name: 'Štai keletas pastaruoju metu dažniausiai saitažodžių, kurių tu naudojai:'
form_admin_settings:
peers_api_enabled: Domenų pavadinimų sąrašas, su kuriais šis serveris susidūrė fediverse. Čia nėra duomenų apie tai, ar tu bendrauji su tam tikru serveriu, tik apie tai, kad tavo serveris apie jį žino. Tai naudojama tarnybose, kurios renka federacijos statistiką bendrąja prasme.
site_contact_email: Kaip žmonės gali su tavimi susisiekti teisiniais ar pagalbos užklausimais.
site_contact_username: Kaip žmonės gali tave pasiekti Mastodon.
site_extended_description: Bet kokia papildoma informacija, kuri gali būti naudinga lankytojams ir naudotojams. Gali būti struktūrizuota naudojant Markdown sintaksę.
trends: Trendai rodo, kurios įrašai, saitažodžiai ir naujienų istorijos tavo serveryje sulaukia didžiausio susidomėjimo.
sessions:
webauthn: Jei tai USB raktas, būtinai jį įkišk ir, jei reikia, paliesk.
settings:
indexable: Tavo profilio puslapis gali būti rodomas paieškos rezultatuose Google, Bing ir kituose.
labels:
featured_tag:
name: Saitažodis
tag:
listable: Leisti šį saitažodį rodyti paieškose ir pasiūlymuose
name: Saitažodis
trendable: Leisti šį saitažodį rodyti pagal trendus
usable: Leisti įrašams naudoti šį saitažodį

View File

@ -98,7 +98,7 @@ sk:
disabled: Blokovaný
pending: Čakajúci
silenced: Obmedzený
suspended: Vylúčený/á
suspended: Pozastavený/á
title: Moderácia
moderation_notes: Moderátorské poznámky
most_recent_activity: Posledná aktivita
@ -149,8 +149,8 @@ sk:
statuses: Príspevkov
strikes: Predchádzajúce údery
subscribe: Odoberaj
suspend: Vylúč
suspended: Vylúčený/á
suspend: Pozastav
suspended: Pozastavený/á
suspension_irreversible: Údaje tohto účtu boli nenávratne vymazané. Účet môžete zrušiť, aby sa dal používať, ale neobnovia sa žiadne údaje, ktoré predtým mal.
suspension_reversible_hint_html: Účet bol pozastavený a údaje budú úplne odstránené dňa %{date}. Dovtedy je možné účet obnoviť bez akýchkoľvek nepriaznivých účinkov. Ak chcete okamžite odstrániť všetky údaje účtu, môžete tak urobiť nižšie.
title: Účty
@ -162,6 +162,7 @@ sk:
undo_suspension: Zruš blokovanie
unsilenced_msg: Úspešne zrušené obmedzenie účtu %{username}
unsubscribe: Prestaň odoberať
unsuspended_msg: "%{username} ov/in účet úspešne spojazdnený"
username: Prezývka
view_domain: Ukáž súhrn pre doménu
warn: Varuj
@ -209,7 +210,7 @@ sk:
resolve_report: Vyrieš nahlásený problém
sensitive_account: Vynúť všetky médiá na účte ako chúlostivé
silence_account: Utíš účet
suspend_account: Vylúč účet
suspend_account: Pozastav účet
unassigned_report: Odober priradenie nahlásenia
unblock_email_account: Odblokuj emailovú adresu
unsilence_account: Zvráť obmedzenie účtu
@ -255,6 +256,7 @@ sk:
silence_account_html: "%{name} obmedzil/a účet %{target}"
suspend_account_html: "%{name} zablokoval/a účet používateľa %{target}"
unassigned_report_html: "%{name} odobral/a report od %{target}"
unsuspend_account_html: "%{name} spojazdnil/a účet %{target}"
update_user_role_html: "%{name} zmenil/a rolu pre %{target}"
deleted_account: zmazaný účet
empty: Žiadne záznamy nenájdené.
@ -341,6 +343,7 @@ sk:
confirm_suspension:
cancel: Zruš
confirm: Vylúč
preamble_html: Chystáš sa vylúčiť <strong>%{domain}</strong> a jej poddomény.
title: Potvrď blokovanie domény %{domain}
created_msg: Doména je v štádiu blokovania
destroyed_msg: Blokovanie domény bolo zrušené
@ -355,7 +358,7 @@ sk:
severity:
noop: Nič
silence: Obmedz
suspend: Vylúč
suspend: Pozastav
title: Nové blokovanie domény
not_permitted: Nemáš povolenie na vykonanie tohto kroku
obfuscate: Zatemniť názov domény
@ -416,7 +419,7 @@ sk:
reject_media: Zamietni médiá
reject_reports: Zamietni hlásenia
silence: Obmedzená
suspend: Vylúč
suspend: Pozastav
policy: Zásady
reason: Verejné odôvodnenie
title: Zásady o obsahu
@ -537,7 +540,7 @@ sk:
statuses: Nahlásený obsah
summary:
action_preambles:
suspend_html: 'Chystáš sa <strong>vylúčiť</strong> účet <strong>@%{acct}</strong>. To urobí:'
suspend_html: 'Chystáš sa <strong>pozastaviť</strong> účet <strong>@%{acct}</strong>. To urobí:'
actions:
delete_html: Vymaž pohoršujúce príspevky
mark_as_sensitive_html: Označ médiá pohoršujúcich príspevkov za chúlostivé

View File

@ -427,7 +427,11 @@ namespace :mastodon do
from: env['SMTP_FROM_ADDRESS'],
}
mail = ActionMailer::Base.new.mail to: send_to, subject: 'Test', body: 'Mastodon SMTP configuration works!'
mail = ActionMailer::Base.new.mail(
to: send_to,
subject: 'Test', # rubocop:disable Rails/I18nLocaleTexts
body: 'Mastodon SMTP configuration works!'
)
mail.deliver
break
rescue => e

View File

@ -211,7 +211,7 @@
"husky": "^8.0.3",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"lint-staged": "^13.2.2",
"lint-staged": "^15.0.0",
"prettier": "^3.0.0",
"react-test-renderer": "^18.2.0",
"stylelint": "^15.10.1",

View File

@ -26,7 +26,6 @@ describe Api::V1::StreamingController do
context 'with streaming api on different host' do
before do
Rails.configuration.x.streaming_api_base_url = "wss://streaming-#{Rails.configuration.x.web_domain}"
@streaming_host = URI.parse(Rails.configuration.x.streaming_api_base_url).host
end
describe 'GET #index' do
@ -38,7 +37,13 @@ describe Api::V1::StreamingController do
[:scheme, :path, :query, :fragment].each do |part|
expect(redirect_to_uri.send(part)).to eq(request_uri.send(part)), "redirect target #{part}"
end
expect(redirect_to_uri.host).to eq(@streaming_host), 'redirect target host'
expect(redirect_to_uri.host).to eq(streaming_host), 'redirect target host'
end
private
def streaming_host
URI.parse(Rails.configuration.x.streaming_api_base_url).host
end
end
end

View File

@ -18,12 +18,14 @@ describe Auth::PasswordsController do
before do
request.env['devise.mapping'] = Devise.mappings[:user]
@token = user.send_reset_password_instructions
end
context 'with valid reset_password_token' do
it 'returns http success' do
get :edit, params: { reset_password_token: @token }
token = user.send_reset_password_instructions
get :edit, params: { reset_password_token: token }
expect(response).to have_http_status(200)
end
end
@ -38,9 +40,9 @@ describe Auth::PasswordsController do
describe 'POST #update' do
let(:user) { Fabricate(:user) }
let(:password) { 'reset0password' }
before do
@password = 'reset0password'
request.env['devise.mapping'] = Devise.mappings[:user]
end
@ -50,9 +52,9 @@ describe Auth::PasswordsController do
let!(:web_push_subscription) { Fabricate(:web_push_subscription, access_token: access_token) }
before do
@token = user.send_reset_password_instructions
token = user.send_reset_password_instructions
post :update, params: { user: { password: @password, password_confirmation: @password, reset_password_token: @token } }
post :update, params: { user: { password: password, password_confirmation: password, reset_password_token: token } }
end
it 'redirect to sign in' do
@ -63,7 +65,7 @@ describe Auth::PasswordsController do
this_user = User.find(user.id)
expect(this_user).to_not be_nil
expect(this_user.valid_password?(@password)).to be true
expect(this_user.valid_password?(password)).to be true
end
it 'deactivates all sessions' do
@ -81,7 +83,7 @@ describe Auth::PasswordsController do
context 'with invalid reset_password_token' do
before do
post :update, params: { user: { password: @password, password_confirmation: @password, reset_password_token: 'some_invalid_value' } }
post :update, params: { user: { password: password, password_confirmation: password, reset_password_token: 'some_invalid_value' } }
end
it 'renders reset password' do

View File

@ -11,7 +11,7 @@ describe ExportControllerConcern do
end
def export_data
@export.account.username
'body data value'
end
end
@ -24,7 +24,7 @@ describe ExportControllerConcern do
expect(response).to have_http_status(200)
expect(response.media_type).to eq 'text/csv'
expect(response.headers['Content-Disposition']).to start_with 'attachment; filename="anonymous.csv"'
expect(response.body).to eq user.account.username
expect(response.body).to eq 'body data value'
end
it 'returns unauthorized when not signed in' do

View File

@ -194,7 +194,7 @@ RSpec.describe Settings::ImportsController do
let!(:rows) do
[
{ 'acct' => 'foo@bar' },
{ 'acct' => 'user@bar', 'show_reblogs' => false, 'notify' => true, 'languages' => ['fr', 'de'] },
{ 'acct' => 'user@bar', 'show_reblogs' => false, 'notify' => true, 'languages' => %w(fr de) },
].map { |data| Fabricate(:bulk_import_row, bulk_import: bulk_import, data: data) }
end

View File

@ -5,9 +5,10 @@ require 'rails_helper'
RSpec.describe StatusesCleanupController do
render_views
let!(:user) { Fabricate(:user) }
before do
@user = Fabricate(:user)
sign_in @user, scope: :user
sign_in user, scope: :user
end
describe 'GET #show' do
@ -30,9 +31,9 @@ RSpec.describe StatusesCleanupController do
end
it 'updates the account status cleanup policy' do
expect(@user.account.statuses_cleanup_policy.enabled).to be true
expect(@user.account.statuses_cleanup_policy.keep_direct).to be false
expect(@user.account.statuses_cleanup_policy.keep_polls).to be true
expect(user.account.statuses_cleanup_policy.enabled).to be true
expect(user.account.statuses_cleanup_policy.keep_direct).to be false
expect(user.account.statuses_cleanup_policy.keep_polls).to be true
end
it 'redirects' do

View File

@ -2,5 +2,5 @@
Fabricator(:session_activation) do
user { Fabricate.build(:user) }
session_id 'MyString'
session_id { sequence(:session_id) { |i| "session_id_#{i}" } }
end

View File

@ -296,5 +296,51 @@ describe ApplicationHelper do
expect(helper.title).to eq 'site title'
expect(Rails.env).to have_received(:production?)
end
it 'returns site title with note on non-production environment' do
Setting.site_title = 'site title'
allow(Rails.env).to receive(:production?).and_return(false)
expect(helper.title).to eq 'site title (Dev)'
expect(Rails.env).to have_received(:production?)
end
end
describe 'html_title' do
before do
allow(Rails.env).to receive(:production?).and_return(true)
end
around do |example|
site_title = Setting.site_title
example.run
Setting.site_title = site_title
end
context 'with a page_title content_for value' do
it 'uses the value in the html title' do
Setting.site_title = 'Site Title'
helper.content_for(:page_title, 'Test Value')
expect(helper.html_title).to eq 'Test Value - Site Title'
expect(helper.html_title).to be_html_safe
end
it 'removes extra new lines' do
Setting.site_title = 'Site Title'
helper.content_for(:page_title, "Test Value\n")
expect(helper.html_title).to eq 'Test Value - Site Title'
expect(helper.html_title).to be_html_safe
end
end
context 'without any page_title content_for value' do
it 'returns the site title' do
Setting.site_title = 'Site Title'
expect(helper.html_title).to eq 'Site Title'
expect(helper.html_title).to be_html_safe
end
end
end
end

View File

@ -4,16 +4,23 @@ require 'rails_helper'
describe FlashesHelper do
describe 'user_facing_flashes' do
it 'returns user facing flashes' do
before do
# rubocop:disable Rails/I18nLocaleTexts
flash[:alert] = 'an alert'
flash[:error] = 'an error'
flash[:notice] = 'a notice'
flash[:success] = 'a success'
flash[:not_user_facing] = 'a not user facing flash'
expect(helper.user_facing_flashes).to eq 'alert' => 'an alert',
'error' => 'an error',
'notice' => 'a notice',
'success' => 'a success'
# rubocop:enable Rails/I18nLocaleTexts
end
it 'returns user facing flashes' do
expect(helper.user_facing_flashes).to eq(
'alert' => 'an alert',
'error' => 'an error',
'notice' => 'a notice',
'success' => 'a success'
)
end
end
end

View File

@ -8,12 +8,14 @@ RSpec.describe TranslationService::DeepL do
let(:plan) { 'advanced' }
before do
stub_request(:get, 'https://api.deepl.com/v2/languages?type=source').to_return(
body: '[{"language":"EN","name":"English"},{"language":"UK","name":"Ukrainian"}]'
)
stub_request(:get, 'https://api.deepl.com/v2/languages?type=target').to_return(
body: '[{"language":"EN-GB","name":"English (British)"},{"language":"ZH","name":"Chinese"}]'
)
%w(api-free.deepl.com api.deepl.com).each do |host|
stub_request(:get, "https://#{host}/v2/languages?type=source").to_return(
body: '[{"language":"EN","name":"English"},{"language":"UK","name":"Ukrainian"}]'
)
stub_request(:get, "https://#{host}/v2/languages?type=target").to_return(
body: '[{"language":"EN-GB","name":"English (British)"},{"language":"ZH","name":"Chinese"}]'
)
end
end
describe '#translate' do
@ -73,28 +75,25 @@ RSpec.describe TranslationService::DeepL do
end
end
describe '#request' do
describe 'the paid and free plan api hostnames' do
before do
stub_request(:any, //)
# rubocop:disable Lint/EmptyBlock
service.send(:request, :get, '/v2/languages') { |res| }
# rubocop:enable Lint/EmptyBlock
service.languages
end
it 'uses paid plan base URL' do
expect(a_request(:get, 'https://api.deepl.com/v2/languages')).to have_been_made.once
end
context 'with free plan' do
let(:plan) { 'free' }
it 'uses free plan base URL' do
expect(a_request(:get, 'https://api-free.deepl.com/v2/languages')).to have_been_made.once
context 'without a plan set' do
it 'uses paid plan base URL and sends an API key' do
expect(a_request(:get, 'https://api.deepl.com/v2/languages?type=source').with(headers: { Authorization: 'DeepL-Auth-Key my-api-key' })).to have_been_made.once
expect(a_request(:get, 'https://api.deepl.com/v2/languages?type=target').with(headers: { Authorization: 'DeepL-Auth-Key my-api-key' })).to have_been_made.once
end
end
it 'sends API key' do
expect(a_request(:get, 'https://api.deepl.com/v2/languages').with(headers: { Authorization: 'DeepL-Auth-Key my-api-key' })).to have_been_made.once
context 'with the free plan' do
let(:plan) { 'free' }
it 'uses free plan base URL and sends an API key' do
expect(a_request(:get, 'https://api-free.deepl.com/v2/languages?type=source').with(headers: { Authorization: 'DeepL-Auth-Key my-api-key' })).to have_been_made.once
expect(a_request(:get, 'https://api-free.deepl.com/v2/languages?type=target').with(headers: { Authorization: 'DeepL-Auth-Key my-api-key' })).to have_been_made.once
end
end
end
end

View File

@ -4,17 +4,15 @@ require 'rails_helper'
describe AccountFinderConcern do
describe 'local finders' do
before do
@account = Fabricate(:account, username: 'Alice')
end
let!(:account) { Fabricate(:account, username: 'Alice') }
describe '.find_local' do
it 'returns case-insensitive result' do
expect(Account.find_local('alice')).to eq(@account)
expect(Account.find_local('alice')).to eq(account)
end
it 'returns correctly cased result' do
expect(Account.find_local('Alice')).to eq(@account)
expect(Account.find_local('Alice')).to eq(account)
end
it 'returns nil without a match' do
@ -36,7 +34,7 @@ describe AccountFinderConcern do
describe '.find_local!' do
it 'returns matching result' do
expect(Account.find_local!('alice')).to eq(@account)
expect(Account.find_local!('alice')).to eq(account)
end
it 'raises on non-matching result' do
@ -54,17 +52,15 @@ describe AccountFinderConcern do
end
describe 'remote finders' do
before do
@account = Fabricate(:account, username: 'Alice', domain: 'mastodon.social')
end
let!(:account) { Fabricate(:account, username: 'Alice', domain: 'mastodon.social') }
describe '.find_remote' do
it 'returns exact match result' do
expect(Account.find_remote('alice', 'mastodon.social')).to eq(@account)
expect(Account.find_remote('alice', 'mastodon.social')).to eq(account)
end
it 'returns case-insensitive result' do
expect(Account.find_remote('ALICE', 'MASTODON.SOCIAL')).to eq(@account)
expect(Account.find_remote('ALICE', 'MASTODON.SOCIAL')).to eq(account)
end
it 'returns nil when username does not match' do
@ -90,7 +86,7 @@ describe AccountFinderConcern do
describe '.find_remote!' do
it 'returns matching result' do
expect(Account.find_remote!('alice', 'mastodon.social')).to eq(@account)
expect(Account.find_remote!('alice', 'mastodon.social')).to eq(account)
end
it 'raises on non-matching result' do

View File

@ -655,38 +655,36 @@ describe AccountInteractions do
end
describe 'ignoring reblogs from an account' do
before do
@me = Fabricate(:account, username: 'Me')
@you = Fabricate(:account, username: 'You')
end
let!(:me) { Fabricate(:account, username: 'Me') }
let!(:you) { Fabricate(:account, username: 'You') }
context 'with the reblogs option unspecified' do
before do
@me.follow!(@you)
me.follow!(you)
end
it 'defaults to showing reblogs' do
expect(@me.muting_reblogs?(@you)).to be(false)
expect(me.muting_reblogs?(you)).to be(false)
end
end
context 'with the reblogs option set to false' do
before do
@me.follow!(@you, reblogs: false)
me.follow!(you, reblogs: false)
end
it 'does mute reblogs' do
expect(@me.muting_reblogs?(@you)).to be(true)
expect(me.muting_reblogs?(you)).to be(true)
end
end
context 'with the reblogs option set to true' do
before do
@me.follow!(@you, reblogs: true)
me.follow!(you, reblogs: true)
end
it 'does not mute reblogs' do
expect(@me.muting_reblogs?(@you)).to be(false)
expect(me.muting_reblogs?(you)).to be(false)
end
end
end

View File

@ -69,7 +69,9 @@ RSpec.describe Remotable do
context 'with an invalid URL' do
before do
allow(Addressable::URI).to receive_message_chain(:parse, :normalize).with(url).with(no_args).and_raise(Addressable::URI::InvalidURIError)
parsed = instance_double(Addressable::URI)
allow(parsed).to receive(:normalize).with(no_args).and_raise(Addressable::URI::InvalidURIError)
allow(Addressable::URI).to receive(:parse).with(url).and_return(parsed)
end
it 'makes no request' do

View File

@ -296,7 +296,7 @@ RSpec.describe Form::Import do
it_behaves_like 'on successful import', 'following', 'merge', 'following_accounts.csv', [
{ 'acct' => 'user@example.com', 'show_reblogs' => true, 'notify' => false, 'languages' => nil },
{ 'acct' => 'user@test.com', 'show_reblogs' => true, 'notify' => true, 'languages' => ['en', 'fr'] },
{ 'acct' => 'user@test.com', 'show_reblogs' => true, 'notify' => true, 'languages' => %w(en fr) },
]
it_behaves_like 'on successful import', 'muting', 'merge', 'muted_accounts.csv', [

View File

@ -199,15 +199,13 @@ RSpec.describe PublicFeed do
end
describe 'with an account passed in' do
subject { described_class.new(@account).get(20).map(&:id) }
subject { described_class.new(account).get(20).map(&:id) }
before do
@account = Fabricate(:account)
end
let!(:account) { Fabricate(:account) }
it 'excludes statuses from accounts blocked by the account' do
blocked = Fabricate(:account)
@account.block!(blocked)
account.block!(blocked)
blocked_status = Fabricate(:status, account: blocked)
expect(subject).to_not include(blocked_status.id)
@ -215,7 +213,7 @@ RSpec.describe PublicFeed do
it 'excludes statuses from accounts who have blocked the account' do
blocker = Fabricate(:account)
blocker.block!(@account)
blocker.block!(account)
blocked_status = Fabricate(:status, account: blocker)
expect(subject).to_not include(blocked_status.id)
@ -223,7 +221,7 @@ RSpec.describe PublicFeed do
it 'excludes statuses from accounts muted by the account' do
muted = Fabricate(:account)
@account.mute!(muted)
account.mute!(muted)
muted_status = Fabricate(:status, account: muted)
expect(subject).to_not include(muted_status.id)
@ -231,7 +229,7 @@ RSpec.describe PublicFeed do
it 'excludes statuses from accounts from personally blocked domains' do
blocked = Fabricate(:account, domain: 'example.com')
@account.block_domain!(blocked.domain)
account.block_domain!(blocked.domain)
blocked_status = Fabricate(:status, account: blocked)
expect(subject).to_not include(blocked_status.id)
@ -239,7 +237,7 @@ RSpec.describe PublicFeed do
context 'with language preferences' do
it 'excludes statuses in languages not allowed by the account user' do
@account.user.update(chosen_languages: [:en, :es])
account.user.update(chosen_languages: [:en, :es])
en_status = Fabricate(:status, language: 'en')
es_status = Fabricate(:status, language: 'es')
fr_status = Fabricate(:status, language: 'fr')
@ -250,7 +248,7 @@ RSpec.describe PublicFeed do
end
it 'includes all languages when user does not have a setting' do
@account.user.update(chosen_languages: nil)
account.user.update(chosen_languages: nil)
en_status = Fabricate(:status, language: 'en')
es_status = Fabricate(:status, language: 'es')
@ -260,7 +258,7 @@ RSpec.describe PublicFeed do
end
it 'includes all languages when account does not have a user' do
@account.update(user: nil)
account.update(user: nil)
en_status = Fabricate(:status, language: 'en')
es_status = Fabricate(:status, language: 'es')

View File

@ -98,34 +98,44 @@ RSpec.describe SessionActivation do
end
context 'when id exists' do
let(:id) { '1' }
let!(:session_activation) { Fabricate(:session_activation) }
it 'calls where.destroy_all' do
expect(described_class).to receive_message_chain(:where, :destroy_all)
.with(session_id: id).with(no_args)
it 'destroys the record' do
described_class.deactivate(session_activation.session_id)
described_class.deactivate(id)
expect { session_activation.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
describe '.purge_old' do
it 'calls order.offset.destroy_all' do
expect(described_class).to receive_message_chain(:order, :offset, :destroy_all)
.with('created_at desc').with(Rails.configuration.x.max_session_activations).with(no_args)
around do |example|
before = Rails.configuration.x.max_session_activations
Rails.configuration.x.max_session_activations = 1
example.run
Rails.configuration.x.max_session_activations = before
end
let!(:oldest_session_activation) { Fabricate(:session_activation, created_at: 10.days.ago) }
let!(:newest_session_activation) { Fabricate(:session_activation, created_at: 5.days.ago) }
it 'preserves the newest X records based on config' do
described_class.purge_old
expect { oldest_session_activation.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect { newest_session_activation.reload }.to_not raise_error
end
end
describe '.exclusive' do
let(:id) { '1' }
let!(:unwanted_session_activation) { Fabricate(:session_activation) }
let!(:wanted_session_activation) { Fabricate(:session_activation) }
it 'calls where.destroy_all' do
expect(described_class).to receive_message_chain(:where, :not, :destroy_all)
.with(session_id: id).with(no_args)
it 'preserves supplied record and destroys all others' do
described_class.exclusive(wanted_session_activation.session_id)
described_class.exclusive(id)
expect { unwanted_session_activation.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect { wanted_session_activation.reload }.to_not raise_error
end
end
end

View File

@ -77,10 +77,13 @@ RSpec.describe Setting do
let(:default_value) { { default_value: 'default_value' } }
it 'calls default_value.with_indifferent_access.merge!' do
expect(default_value).to receive_message_chain(:with_indifferent_access, :merge!)
.with(db_val.value)
indifferent_hash = instance_double(Hash, merge!: nil)
allow(default_value).to receive(:with_indifferent_access).and_return(indifferent_hash)
described_class[key]
expect(default_value).to have_received(:with_indifferent_access)
expect(indifferent_hash).to have_received(:merge!).with(db_val.value)
end
end

View File

@ -11,10 +11,6 @@ if RUN_SYSTEM_SPECS
ENV['STREAMING_API_BASE_URL'] = "http://localhost:#{STREAMING_PORT}"
end
if RUN_SEARCH_SPECS
# Include any configuration or setups specific to search tests here
end
require File.expand_path('../config/environment', __dir__)
abort('The Rails environment is running in production mode!') if Rails.env.production?
@ -35,8 +31,6 @@ Sidekiq.logger = nil
# System tests config
DatabaseCleaner.strategy = [:deletion]
streaming_server_manager = StreamingServerManager.new
search_data_manager = SearchDataManager.new
Devise::Test::ControllerHelpers.module_eval do
alias_method :original_sign_in, :sign_in
@ -100,26 +94,7 @@ RSpec.configure do |config|
Capybara.current_driver = :rack_test
end
config.before :suite do
if RUN_SYSTEM_SPECS
Webpacker.compile
streaming_server_manager.start(port: STREAMING_PORT)
end
if RUN_SEARCH_SPECS
Chewy.strategy(:urgent)
search_data_manager.prepare_test_data
end
end
config.after :suite do
streaming_server_manager.stop
search_data_manager.cleanup_test_data if RUN_SEARCH_SPECS
end
config.around :each, type: :system do |example|
# driven_by :selenium, using: :chrome, screen_size: [1600, 1200]
driven_by :selenium, using: :headless_chrome, screen_size: [1600, 1200]
# The streaming server needs access to the database
@ -136,12 +111,6 @@ RSpec.configure do |config|
self.use_transactional_tests = true
end
config.around :each, type: :search do |example|
search_data_manager.populate_indexes
example.run
search_data_manager.remove_indexes
end
config.before do |example|
unless example.metadata[:paperclip_processing]
allow_any_instance_of(Paperclip::Attachment).to receive(:post_process).and_return(true) # rubocop:disable RSpec/AnyInstance

View File

@ -119,24 +119,26 @@ module TestEndpoints
end
describe 'Caching behavior' do
shared_examples 'cachable response' do
it 'does not set cookies' do
shared_examples 'cachable response' do |http_success: false|
it 'does not set cookies or set public cache control', :aggregate_failures do
expect(response.cookies).to be_empty
end
it 'sets public cache control', :aggregate_failures do
# expect(response.cache_control[:max_age]&.to_i).to be_positive
expect(response.cache_control[:public]).to be_truthy
expect(response.cache_control[:private]).to be_falsy
expect(response.cache_control[:no_store]).to be_falsy
expect(response.cache_control[:no_cache]).to be_falsy
expect(response).to have_http_status(200) if http_success
end
end
shared_examples 'non-cacheable response' do
shared_examples 'non-cacheable response' do |http_success: false|
it 'sets private cache control' do
expect(response.cache_control[:private]).to be_truthy
expect(response.cache_control[:no_store]).to be_truthy
expect(response).to have_http_status(200) if http_success
end
end
@ -149,7 +151,7 @@ describe 'Caching behavior' do
shared_examples 'language-dependent' do
it 'has a Vary on Accept-Language' do
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('accept-language')
expect(response_vary_headers).to include('accept-language')
end
end
@ -202,7 +204,7 @@ describe 'Caching behavior' do
it_behaves_like 'cachable response'
it 'has a Vary on Cookie' do
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('cookie')
expect(response_vary_headers).to include('cookie')
end
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
@ -216,7 +218,7 @@ describe 'Caching behavior' do
it_behaves_like 'cachable response'
it 'has a Vary on Authorization' do
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('authorization')
expect(response_vary_headers).to include('authorization')
end
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
@ -302,7 +304,7 @@ describe 'Caching behavior' do
it_behaves_like 'non-cacheable response'
it 'has a Vary on Cookie' do
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('cookie')
expect(response_vary_headers).to include('cookie')
end
end
end
@ -311,11 +313,7 @@ describe 'Caching behavior' do
describe endpoint do
before { get endpoint }
it_behaves_like 'non-cacheable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
it_behaves_like 'non-cacheable response', http_success: true
end
end
@ -351,7 +349,7 @@ describe 'Caching behavior' do
it_behaves_like 'non-cacheable response'
it 'has a Vary on Authorization' do
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('authorization')
expect(response_vary_headers).to include('authorization')
end
end
end
@ -362,11 +360,7 @@ describe 'Caching behavior' do
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
end
it_behaves_like 'non-cacheable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
it_behaves_like 'non-cacheable response', http_success: true
end
end
@ -393,11 +387,7 @@ describe 'Caching behavior' do
context 'when allowed for local users only' do
let(:show_domain_blocks) { 'users' }
it_behaves_like 'non-cacheable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
it_behaves_like 'non-cacheable response', http_success: true
end
context 'when disabled' do
@ -421,11 +411,7 @@ describe 'Caching behavior' do
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'cachable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
it_behaves_like 'cachable response', http_success: true
end
TestEndpoints::REQUIRE_SIGNATURE.each do |endpoint|
@ -434,11 +420,7 @@ describe 'Caching behavior' do
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'non-cacheable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
it_behaves_like 'non-cacheable response', http_success: true
end
end
end
@ -456,11 +438,7 @@ describe 'Caching behavior' do
get '/actor', headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'cachable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
it_behaves_like 'cachable response', http_success: true
end
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
@ -487,11 +465,7 @@ describe 'Caching behavior' do
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'cachable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
it_behaves_like 'cachable response', http_success: true
end
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
@ -500,11 +474,7 @@ describe 'Caching behavior' do
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'non-cacheable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
it_behaves_like 'non-cacheable response', http_success: true
end
end
end
@ -528,11 +498,7 @@ describe 'Caching behavior' do
get '/actor', headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'cachable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
it_behaves_like 'cachable response', http_success: true
end
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
@ -560,11 +526,7 @@ describe 'Caching behavior' do
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'cachable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
it_behaves_like 'cachable response', http_success: true
end
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
@ -573,11 +535,7 @@ describe 'Caching behavior' do
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'non-cacheable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
it_behaves_like 'non-cacheable response', http_success: true
end
end
end
@ -591,11 +549,7 @@ describe 'Caching behavior' do
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'cachable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
it_behaves_like 'cachable response', http_success: true
end
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
@ -667,7 +621,7 @@ describe 'Caching behavior' do
it_behaves_like 'non-cacheable response'
it 'has a Vary on Authorization' do
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('authorization')
expect(response_vary_headers).to include('authorization')
end
end
end
@ -678,13 +632,15 @@ describe 'Caching behavior' do
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
end
it_behaves_like 'non-cacheable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
it_behaves_like 'non-cacheable response', http_success: true
end
end
end
end
private
def response_vary_headers
response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }
end
end

View File

@ -20,71 +20,70 @@ RSpec.describe RemoveStatusService, type: :service do
end
context 'when removed status is not a reblog' do
let!(:status) { PostStatusService.new.call(alice, text: 'Hello @bob@example.com ThisIsASecret') }
before do
@status = PostStatusService.new.call(alice, text: 'Hello @bob@example.com ThisIsASecret')
FavouriteService.new.call(jeff, @status)
Fabricate(:status, account: bill, reblog: @status, uri: 'hoge')
FavouriteService.new.call(jeff, status)
Fabricate(:status, account: bill, reblog: status, uri: 'hoge')
end
it 'removes status from author\'s home feed' do
subject.call(@status)
expect(HomeFeed.new(alice).get(10).pluck(:id)).to_not include(@status.id)
subject.call(status)
expect(HomeFeed.new(alice).get(10).pluck(:id)).to_not include(status.id)
end
it 'removes status from local follower\'s home feed' do
subject.call(@status)
expect(HomeFeed.new(jeff).get(10).pluck(:id)).to_not include(@status.id)
subject.call(status)
expect(HomeFeed.new(jeff).get(10).pluck(:id)).to_not include(status.id)
end
it 'sends Delete activity to followers' do
subject.call(@status)
subject.call(status)
expect(a_request(:post, 'http://example.com/inbox').with(
body: hash_including({
'type' => 'Delete',
'object' => {
'type' => 'Tombstone',
'id' => ActivityPub::TagManager.instance.uri_for(@status),
'atomUri' => OStatus::TagManager.instance.uri_for(@status),
'id' => ActivityPub::TagManager.instance.uri_for(status),
'atomUri' => OStatus::TagManager.instance.uri_for(status),
},
})
)).to have_been_made.once
end
it 'sends Delete activity to rebloggers' do
subject.call(@status)
subject.call(status)
expect(a_request(:post, 'http://example2.com/inbox').with(
body: hash_including({
'type' => 'Delete',
'object' => {
'type' => 'Tombstone',
'id' => ActivityPub::TagManager.instance.uri_for(@status),
'atomUri' => OStatus::TagManager.instance.uri_for(@status),
'id' => ActivityPub::TagManager.instance.uri_for(status),
'atomUri' => OStatus::TagManager.instance.uri_for(status),
},
})
)).to have_been_made.once
end
it 'remove status from notifications' do
expect { subject.call(@status) }.to change {
expect { subject.call(status) }.to change {
Notification.where(activity_type: 'Favourite', from_account: jeff, account: alice).count
}.from(1).to(0)
end
end
context 'when removed status is a private self-reblog' do
before do
@original_status = Fabricate(:status, account: alice, text: 'Hello ThisIsASecret', visibility: :private)
@status = ReblogService.new.call(alice, @original_status)
end
let!(:original_status) { Fabricate(:status, account: alice, text: 'Hello ThisIsASecret', visibility: :private) }
let!(:status) { ReblogService.new.call(alice, original_status) }
it 'sends Undo activity to followers' do
subject.call(@status)
subject.call(status)
expect(a_request(:post, 'http://example.com/inbox').with(
body: hash_including({
'type' => 'Undo',
'object' => hash_including({
'type' => 'Announce',
'object' => ActivityPub::TagManager.instance.uri_for(@original_status),
'object' => ActivityPub::TagManager.instance.uri_for(original_status),
}),
})
)).to have_been_made.once
@ -92,19 +91,17 @@ RSpec.describe RemoveStatusService, type: :service do
end
context 'when removed status is public self-reblog' do
before do
@original_status = Fabricate(:status, account: alice, text: 'Hello ThisIsASecret', visibility: :public)
@status = ReblogService.new.call(alice, @original_status)
end
let!(:original_status) { Fabricate(:status, account: alice, text: 'Hello ThisIsASecret', visibility: :public) }
let!(:status) { ReblogService.new.call(alice, original_status) }
it 'sends Undo activity to followers' do
subject.call(@status)
subject.call(status)
expect(a_request(:post, 'http://example.com/inbox').with(
body: hash_including({
'type' => 'Undo',
'object' => hash_including({
'type' => 'Announce',
'object' => ActivityPub::TagManager.instance.uri_for(@original_status),
'object' => ActivityPub::TagManager.instance.uri_for(original_status),
}),
})
)).to have_been_made.once

View File

@ -19,17 +19,15 @@ describe SearchService, type: :service do
end
describe 'with an url query' do
before do
@query = 'http://test.host/query'
end
let(:query) { 'http://test.host/query' }
context 'when it does not find anything' do
it 'returns the empty results' do
service = instance_double(ResolveURLService, call: nil)
allow(ResolveURLService).to receive(:new).and_return(service)
results = subject.call(@query, nil, 10, resolve: true)
results = subject.call(query, nil, 10, resolve: true)
expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
expect(service).to have_received(:call).with(query, on_behalf_of: nil)
expect(results).to eq empty_results
end
end
@ -40,8 +38,8 @@ describe SearchService, type: :service do
service = instance_double(ResolveURLService, call: account)
allow(ResolveURLService).to receive(:new).and_return(service)
results = subject.call(@query, nil, 10, resolve: true)
expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
results = subject.call(query, nil, 10, resolve: true)
expect(service).to have_received(:call).with(query, on_behalf_of: nil)
expect(results).to eq empty_results.merge(accounts: [account])
end
end
@ -52,8 +50,8 @@ describe SearchService, type: :service do
service = instance_double(ResolveURLService, call: status)
allow(ResolveURLService).to receive(:new).and_return(service)
results = subject.call(@query, nil, 10, resolve: true)
expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
results = subject.call(query, nil, 10, resolve: true)
expect(service).to have_received(:call).with(query, on_behalf_of: nil)
expect(results).to eq empty_results.merge(statuses: [status])
end
end

View File

@ -6,38 +6,36 @@ describe UnblockDomainService, type: :service do
subject { described_class.new }
describe 'call' do
before do
@independently_suspended = Fabricate(:account, domain: 'example.com', suspended_at: 1.hour.ago)
@independently_silenced = Fabricate(:account, domain: 'example.com', silenced_at: 1.hour.ago)
@domain_block = Fabricate(:domain_block, domain: 'example.com')
@silenced = Fabricate(:account, domain: 'example.com', silenced_at: @domain_block.created_at)
@suspended = Fabricate(:account, domain: 'example.com', suspended_at: @domain_block.created_at)
end
let!(:independently_suspended) { Fabricate(:account, domain: 'example.com', suspended_at: 1.hour.ago) }
let!(:independently_silenced) { Fabricate(:account, domain: 'example.com', silenced_at: 1.hour.ago) }
let!(:domain_block) { Fabricate(:domain_block, domain: 'example.com') }
let!(:silenced) { Fabricate(:account, domain: 'example.com', silenced_at: domain_block.created_at) }
let!(:suspended) { Fabricate(:account, domain: 'example.com', suspended_at: domain_block.created_at) }
it 'unsilences accounts and removes block' do
@domain_block.update(severity: :silence)
domain_block.update(severity: :silence)
subject.call(@domain_block)
subject.call(domain_block)
expect_deleted_domain_block
expect(@silenced.reload.silenced?).to be false
expect(@suspended.reload.suspended?).to be true
expect(@independently_suspended.reload.suspended?).to be true
expect(@independently_silenced.reload.silenced?).to be true
expect(silenced.reload.silenced?).to be false
expect(suspended.reload.suspended?).to be true
expect(independently_suspended.reload.suspended?).to be true
expect(independently_silenced.reload.silenced?).to be true
end
it 'unsuspends accounts and removes block' do
@domain_block.update(severity: :suspend)
domain_block.update(severity: :suspend)
subject.call(@domain_block)
subject.call(domain_block)
expect_deleted_domain_block
expect(@suspended.reload.suspended?).to be false
expect(@silenced.reload.silenced?).to be false
expect(@independently_suspended.reload.suspended?).to be true
expect(@independently_silenced.reload.silenced?).to be true
expect(suspended.reload.suspended?).to be false
expect(silenced.reload.silenced?).to be false
expect(independently_suspended.reload.suspended?).to be true
expect(independently_silenced.reload.silenced?).to be true
end
end
def expect_deleted_domain_block
expect { @domain_block.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect { domain_block.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end

View File

@ -41,3 +41,38 @@ class SearchDataManager
Tag.destroy_all
end
end
RSpec.configure do |config|
config.before :suite do
if search_examples_present?
# Configure chewy to use `urgent` strategy to index documents
Chewy.strategy(:urgent)
# Create search data
search_data_manager.prepare_test_data
end
end
config.after :suite do
if search_examples_present?
# Clean up after search data
search_data_manager.cleanup_test_data
end
end
config.around :each, type: :search do |example|
search_data_manager.populate_indexes
example.run
search_data_manager.remove_indexes
end
private
def search_data_manager
@search_data_manager ||= SearchDataManager.new
end
def search_examples_present?
RUN_SEARCH_SPECS
end
end

View File

@ -76,3 +76,32 @@ class StreamingServerManager
@running_thread.join
end
end
RSpec.configure do |config|
config.before :suite do
if streaming_examples_present?
# Compile assets
Webpacker.compile
# Start the node streaming server
streaming_server_manager.start(port: STREAMING_PORT)
end
end
config.after :suite do
if streaming_examples_present?
# Stop the node streaming server
streaming_server_manager.stop
end
end
private
def streaming_server_manager
@streaming_server_manager ||= StreamingServerManager.new
end
def streaming_examples_present?
RUN_SYSTEM_SPECS
end
end

View File

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe BlacklistedEmailValidator, type: :validator do
RSpec.describe BlacklistedEmailValidator do
describe '#validate' do
subject { described_class.new.validate(user); errors }

View File

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe DisallowedHashtagsValidator, type: :validator do
RSpec.describe DisallowedHashtagsValidator do
let(:disallowed_tags) { [] }
describe '#validate' do

View File

@ -2,48 +2,76 @@
require 'rails_helper'
RSpec.describe FollowLimitValidator, type: :validator do
RSpec.describe FollowLimitValidator do
describe '#validate' do
before do
allow_any_instance_of(described_class).to receive(:limit_reached?).with(account) do
limit_reached
end
context 'with a nil account' do
it 'does not add validation errors to base' do
follow = Fabricate.build(:follow, account: nil)
described_class.new.validate(follow)
end
follow.valid?
let(:follow) { instance_double(Follow, account: account, errors: errors) }
let(:errors) { instance_double(ActiveModel::Errors, add: nil) }
let(:account) { instance_double(Account, nil?: _nil, local?: local, following_count: 0, followers_count: 0) }
let(:_nil) { true }
let(:local) { false }
context 'with follow.account.nil? || !follow.account.local?' do
let(:_nil) { true }
it 'not calls errors.add' do
expect(errors).to_not have_received(:add).with(:base, any_args)
expect(follow.errors[:base]).to be_empty
end
end
context 'with !(follow.account.nil? || !follow.account.local?)' do
let(:_nil) { false }
let(:local) { true }
context 'with a non-local account' do
it 'does not add validation errors to base' do
follow = Fabricate.build(:follow, account: Account.new(domain: 'host.example'))
context 'when limit_reached?' do
let(:limit_reached) { true }
follow.valid?
it 'calls errors.add' do
expect(errors).to have_received(:add)
.with(:base, I18n.t('users.follow_limit_reached', limit: FollowLimitValidator::LIMIT))
expect(follow.errors[:base]).to be_empty
end
end
context 'with a local account' do
let(:account) { Account.new }
context 'when the followers count is under the limit' do
before do
allow(account).to receive(:following_count).and_return(described_class::LIMIT - 100)
end
it 'does not add validation errors to base' do
follow = Fabricate.build(:follow, account: account)
follow.valid?
expect(follow.errors[:base]).to be_empty
end
end
context 'with !limit_reached?' do
let(:limit_reached) { false }
context 'when the following count is over the limit' do
before do
allow(account).to receive(:following_count).and_return(described_class::LIMIT + 100)
end
it 'not calls errors.add' do
expect(errors).to_not have_received(:add).with(:base, any_args)
context 'when the followers count is low' do
before do
allow(account).to receive(:followers_count).and_return(10)
end
it 'adds validation errors to base' do
follow = Fabricate.build(:follow, account: account)
follow.valid?
expect(follow.errors[:base]).to include(I18n.t('users.follow_limit_reached', limit: FollowLimitValidator::LIMIT))
end
end
context 'when the followers count is high' do
before do
allow(account).to receive(:followers_count).and_return(100_000)
end
it 'does not add validation errors to base' do
follow = Fabricate.build(:follow, account: account)
follow.valid?
expect(follow.errors[:base]).to be_empty
end
end
end
end

View File

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe PollValidator, type: :validator do
RSpec.describe PollValidator do
describe '#validate' do
before do
validator.validate(poll)

View File

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe StatusPinValidator, type: :validator do
RSpec.describe StatusPinValidator do
describe '#validate' do
before do
subject.validate(pin)

1041
yarn.lock

File diff suppressed because it is too large Load Diff