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

shrike
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 RUN gem install foreman
# [Optional] Uncomment this line to install global node packages. # [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 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 git checkout -- Gemfile.lock
# Fetch Javascript dependencies # Fetch Javascript dependencies
corepack prepare
yarn install --immutable yarn install --immutable
# [re]create, migrate, and seed the test database # [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 // 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. // 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).', 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: [ packageRules: [
{ {
// Require Dependency Dashboard Approval for major version bumps of these node packages // 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/_buttons.html.haml'
- 'app/views/admin/accounts/_local_account.html.haml' - 'app/views/admin/accounts/_local_account.html.haml'
- 'app/views/admin/roles/_form.html.haml' - 'app/views/admin/roles/_form.html.haml'
- 'app/views/layouts/application.html.haml'

View File

@ -24,15 +24,6 @@ Lint/NonLocalExitFromIterator:
Exclude: Exclude:
- 'app/helpers/jsonld_helper.rb' - '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. # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize: Metrics/AbcSize:
Max: 144 Max: 144
@ -73,26 +64,6 @@ RSpec/AnyInstance:
RSpec/ExampleLength: RSpec/ExampleLength:
Max: 22 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: RSpec/LetSetup:
Exclude: Exclude:
- 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb' - 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb'
@ -135,12 +106,6 @@ RSpec/LetSetup:
- 'spec/services/unsuspend_account_service_spec.rb' - 'spec/services/unsuspend_account_service_spec.rb'
- 'spec/workers/scheduler/user_cleanup_scheduler_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: RSpec/MultipleExpectations:
Max: 8 Max: 8
@ -180,11 +145,6 @@ Rails/HasManyOrHasOneDependent:
- 'app/models/user.rb' - 'app/models/user.rb'
- 'app/models/web/push_subscription.rb' - 'app/models/web/push_subscription.rb'
Rails/I18nLocaleTexts:
Exclude:
- 'lib/tasks/mastodon.rake'
- 'spec/helpers/flashes_helper_spec.rb'
# Configuration parameters: Include. # Configuration parameters: Include.
# Include: app/controllers/**/*.rb, app/mailers/**/*.rb # Include: app/controllers/**/*.rb, app/mailers/**/*.rb
Rails/LexicallyScopedActionFilter: Rails/LexicallyScopedActionFilter:
@ -560,14 +520,6 @@ Style/SingleArgumentDig:
Exclude: Exclude:
- 'lib/webpacker/manifest_extensions.rb' - '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). # This cop supports safe autocorrection (--autocorrect).
Style/StderrPuts: Style/StderrPuts:
Exclude: Exclude:
@ -626,5 +578,3 @@ Style/TrailingCommaInHashLiteral:
Style/WordArray: Style/WordArray:
Exclude: Exclude:
- 'app/helpers/languages_helper.rb' - '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 'aws-sdk-s3', '~> 1.123', require: false
gem 'fog-core', '<= 2.4.0' 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 'kt-paperclip', '~> 7.2'
gem 'md-paperclip-azure', '~> 2.2', require: false gem 'md-paperclip-azure', '~> 2.2', require: false
gem 'blurhash', '~> 0.1' gem 'blurhash', '~> 0.1'

View File

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

View File

@ -31,6 +31,11 @@ module Admin
private 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 def admin_status_batch_action_params
params.require(:admin_status_batch_action).permit(status_ids: []) params.require(:admin_status_batch_action).permit(status_ids: [])
end end

View File

@ -91,6 +91,14 @@ module ApplicationHelper
end end
end end
def html_title
safe_join(
[content_for(:page_title).to_s.chomp, title]
.select(&:present?),
' - '
)
end
def title def title
Rails.env.production? ? site_title : "#{site_title} (Dev)" Rails.env.production? ? site_title : "#{site_title} (Dev)"
end 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) locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym)
end end
end end
# rubocop:enable Metrics/ModuleLength

View File

@ -42,4 +42,5 @@ export interface ApiAccountJSON {
suspended?: boolean; suspended?: boolean;
limited?: boolean; limited?: boolean;
memorial?: 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']), hasMore: !!state.getIn(['user_lists', 'followers', accountId, 'next']),
isLoading: state.getIn(['user_lists', 'followers', accountId, 'isLoading'], true), isLoading: state.getIn(['user_lists', 'followers', accountId, 'isLoading'], true),
suspended: state.getIn(['accounts', accountId, 'suspended'], false), suspended: state.getIn(['accounts', accountId, 'suspended'], false),
hideCollections: state.getIn(['accounts', accountId, 'hide_collections'], false),
hidden: getAccountHidden(state, accountId), hidden: getAccountHidden(state, accountId),
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false), blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
}; };
@ -111,7 +112,7 @@ class Followers extends ImmutablePureComponent {
}, 300, { leading: true }); }, 300, { leading: true });
render () { 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) { if (!isAccount) {
return ( return (
@ -137,6 +138,8 @@ class Followers extends ImmutablePureComponent {
emptyMessage = <LimitedAccountHint accountId={accountId} />; emptyMessage = <LimitedAccountHint accountId={accountId} />;
} else if (blockedBy) { } else if (blockedBy) {
emptyMessage = <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />; 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()) { } else if (remote && accountIds.isEmpty()) {
emptyMessage = <RemoteHint url={remoteUrl} />; emptyMessage = <RemoteHint url={remoteUrl} />;
} else { } else {

View File

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

View File

@ -88,7 +88,7 @@
"attachments_list.unprocessed": "(ausstehend)", "attachments_list.unprocessed": "(ausstehend)",
"audio.hide": "Audio ausblenden", "audio.hide": "Audio ausblenden",
"autosuggest_hashtag.per_week": "{count} pro Woche", "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.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.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!", "bundle_column_error.error.title": "Oh nein!",

View File

@ -222,6 +222,7 @@
"emoji_button.search_results": "Search results", "emoji_button.search_results": "Search results",
"emoji_button.symbols": "Symbols", "emoji_button.symbols": "Symbols",
"emoji_button.travel": "Travel & Places", "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_suspended": "Account suspended",
"empty_column.account_timeline": "No posts here!", "empty_column.account_timeline": "No posts here!",
"empty_column.account_unavailable": "Profile unavailable", "empty_column.account_unavailable": "Profile unavailable",

View File

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

View File

@ -222,7 +222,7 @@
"emoji_button.search_results": "Výsledky hľadania", "emoji_button.search_results": "Výsledky hľadania",
"emoji_button.symbols": "Symboly", "emoji_button.symbols": "Symboly",
"emoji_button.travel": "Cestovanie a miesta", "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_timeline": "Nie sú tu žiadne príspevky!",
"empty_column.account_unavailable": "Profil nedostupný", "empty_column.account_unavailable": "Profil nedostupný",
"empty_column.blocks": "Ešte si nikoho nezablokoval/a.", "empty_column.blocks": "Ešte si nikoho nezablokoval/a.",

View File

@ -93,6 +93,7 @@ export const accountDefaultValues: AccountShape = {
memorial: false, memorial: false,
limited: false, limited: false,
moved: null, moved: null,
hide_collections: false,
}; };
const AccountFactory = ImmutableRecord<AccountShape>(accountDefaultValues); 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 # 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 # the reblog that wraps it, so we can assume that some values are always false
if payload[:reblog] 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[:muted] = false
payload[:bookmarked] = false payload[:bookmarked] = false
payload[:pinned] = false if @status.account_id == account_id payload[:pinned] = false if @status.account_id == account_id
payload[:filtered] = CustomFilter payload[:filtered] = mapped_applied_custom_filter(account_id, @status.reblog)
.apply_cached_filters(CustomFilter.cached_filters_for(account_id), @status.reblog)
.map { |filter| serialized_filter(filter) }
# If the reblogged status is being delivered to the author who disabled the display of the application # 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 # used to create the status, we need to hydrate it here too
@ -47,26 +71,14 @@ class StatusCacheHydrator
payload[:favourited] = payload[:reblog][:favourited] payload[:favourited] = payload[:reblog][:favourited]
payload[:reblogged] = payload[:reblog][:reblogged] 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 end
payload
end 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) def serialized_filter(filter)
ActiveModelSerializers::SerializableResource.new( 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, attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at,
:note, :url, :uri, :avatar, :avatar_static, :header, :header_static, :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? 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') %h3= t('admin.statuses.history')
%ol.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 %li
.history__entry .history__entry
%h5 %h5

View File

@ -24,7 +24,7 @@
%meta{ name: 'theme-color', content: '#191b22' }/ %meta{ name: 'theme-color', content: '#191b22' }/
%meta{ name: 'apple-mobile-web-app-capable', content: 'yes' }/ %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' = javascript_pack_tag "common", crossorigin: 'anonymous'
- if @theme - if @theme

View File

@ -1,6 +1,6 @@
default: &default default: &default
adapter: postgresql 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 timeout: 5000
connect_timeout: 15 connect_timeout: 15
encoding: unicode encoding: unicode

View File

@ -44,7 +44,7 @@ Rails.application.configure do
config.force_ssl = true config.force_ssl = true
config.ssl_options = { config.ssl_options = {
redirect: { 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 # 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_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) 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 p.worker_src :none
end end
LetterOpenerWeb::LettersController.after_action do |p| LetterOpenerWeb::LettersController.after_action do
request.content_security_policy_nonce_directives = %w(script-src) request.content_security_policy_nonce_directives = %w(script-src)
end end
end end

View File

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

View File

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

View File

@ -175,7 +175,7 @@ SimpleForm.setup do |config|
# config.item_wrapper_class = nil # config.item_wrapper_class = nil
# How the label text should be generated altogether with the required text. # 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. # You can define the class to use on all labels. Default is nil.
# config.label_class = nil # config.label_class = nil

View File

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

View File

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

View File

@ -232,6 +232,12 @@ lt:
unassign: Nepriskirti unassign: Nepriskirti
unresolved: Neišspręsti unresolved: Neišspręsti
updated_at: Atnaujinti 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: statuses:
back_to_account: Atgal į paskyros puslapį back_to_account: Atgal į paskyros puslapį
media: media:
@ -250,6 +256,10 @@ lt:
body: "%{reporter} parašė skundą apie %{target}" body: "%{reporter} parašė skundą apie %{target}"
body_remote: Kažkas iš %{domain} parašė skundą apie %{target} body_remote: Kažkas iš %{domain} parašė skundą apie %{target}
subject: Naujas skundas %{instance} (#%{id}) subject: Naujas skundas %{instance} (#%{id})
appearance:
localization:
body: Mastodon verčia savanoriai.
guide_link_text: Visi gali prisidėti.
application_mailer: application_mailer:
notification_preferences: Keisti el pašto parinktis notification_preferences: Keisti el pašto parinktis
settings: 'Keisti el pašto parinktis: %{link}' settings: 'Keisti el pašto parinktis: %{link}'
@ -458,9 +468,9 @@ lt:
private: Tik sekėjams private: Tik sekėjams
private_long: Rodyti tik sekėjams private_long: Rodyti tik sekėjams
public: Viešas public: Viešas
public_long: Matyti gali visi public_long: Visi gali matyti
unlisted: Neįtrauktas į sąrašus 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: stream_entries:
sensitive_content: Jautrus turinys sensitive_content: Jautrus turinys
themes: 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. 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:' signed_in_as: 'Prisijungta kaip:'
verification: 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 verification: Patvirtinimas

View File

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

View File

@ -1 +1,45 @@
---
lt: 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ý disabled: Blokovaný
pending: Čakajúci pending: Čakajúci
silenced: Obmedzený silenced: Obmedzený
suspended: Vylúčený/á suspended: Pozastavený/á
title: Moderácia title: Moderácia
moderation_notes: Moderátorské poznámky moderation_notes: Moderátorské poznámky
most_recent_activity: Posledná aktivita most_recent_activity: Posledná aktivita
@ -149,8 +149,8 @@ sk:
statuses: Príspevkov statuses: Príspevkov
strikes: Predchádzajúce údery strikes: Predchádzajúce údery
subscribe: Odoberaj subscribe: Odoberaj
suspend: Vylúč suspend: Pozastav
suspended: Vylúčený/á 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_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. 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 title: Účty
@ -162,6 +162,7 @@ sk:
undo_suspension: Zruš blokovanie undo_suspension: Zruš blokovanie
unsilenced_msg: Úspešne zrušené obmedzenie účtu %{username} unsilenced_msg: Úspešne zrušené obmedzenie účtu %{username}
unsubscribe: Prestaň odoberať unsubscribe: Prestaň odoberať
unsuspended_msg: "%{username} ov/in účet úspešne spojazdnený"
username: Prezývka username: Prezývka
view_domain: Ukáž súhrn pre doménu view_domain: Ukáž súhrn pre doménu
warn: Varuj warn: Varuj
@ -209,7 +210,7 @@ sk:
resolve_report: Vyrieš nahlásený problém resolve_report: Vyrieš nahlásený problém
sensitive_account: Vynúť všetky médiá na účte ako chúlostivé sensitive_account: Vynúť všetky médiá na účte ako chúlostivé
silence_account: Utíš účet silence_account: Utíš účet
suspend_account: Vylúč účet suspend_account: Pozastav účet
unassigned_report: Odober priradenie nahlásenia unassigned_report: Odober priradenie nahlásenia
unblock_email_account: Odblokuj emailovú adresu unblock_email_account: Odblokuj emailovú adresu
unsilence_account: Zvráť obmedzenie účtu unsilence_account: Zvráť obmedzenie účtu
@ -255,6 +256,7 @@ sk:
silence_account_html: "%{name} obmedzil/a účet %{target}" silence_account_html: "%{name} obmedzil/a účet %{target}"
suspend_account_html: "%{name} zablokoval/a účet používateľa %{target}" suspend_account_html: "%{name} zablokoval/a účet používateľa %{target}"
unassigned_report_html: "%{name} odobral/a report od %{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}" update_user_role_html: "%{name} zmenil/a rolu pre %{target}"
deleted_account: zmazaný účet deleted_account: zmazaný účet
empty: Žiadne záznamy nenájdené. empty: Žiadne záznamy nenájdené.
@ -341,6 +343,7 @@ sk:
confirm_suspension: confirm_suspension:
cancel: Zruš cancel: Zruš
confirm: Vylúč confirm: Vylúč
preamble_html: Chystáš sa vylúčiť <strong>%{domain}</strong> a jej poddomény.
title: Potvrď blokovanie domény %{domain} title: Potvrď blokovanie domény %{domain}
created_msg: Doména je v štádiu blokovania created_msg: Doména je v štádiu blokovania
destroyed_msg: Blokovanie domény bolo zrušené destroyed_msg: Blokovanie domény bolo zrušené
@ -355,7 +358,7 @@ sk:
severity: severity:
noop: Nič noop: Nič
silence: Obmedz silence: Obmedz
suspend: Vylúč suspend: Pozastav
title: Nové blokovanie domény title: Nové blokovanie domény
not_permitted: Nemáš povolenie na vykonanie tohto kroku not_permitted: Nemáš povolenie na vykonanie tohto kroku
obfuscate: Zatemniť názov domény obfuscate: Zatemniť názov domény
@ -416,7 +419,7 @@ sk:
reject_media: Zamietni médiá reject_media: Zamietni médiá
reject_reports: Zamietni hlásenia reject_reports: Zamietni hlásenia
silence: Obmedzená silence: Obmedzená
suspend: Vylúč suspend: Pozastav
policy: Zásady policy: Zásady
reason: Verejné odôvodnenie reason: Verejné odôvodnenie
title: Zásady o obsahu title: Zásady o obsahu
@ -537,7 +540,7 @@ sk:
statuses: Nahlásený obsah statuses: Nahlásený obsah
summary: summary:
action_preambles: 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: actions:
delete_html: Vymaž pohoršujúce príspevky delete_html: Vymaž pohoršujúce príspevky
mark_as_sensitive_html: Označ médiá pohoršujúcich príspevkov za chúlostivé 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'], 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 mail.deliver
break break
rescue => e rescue => e

View File

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

View File

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

View File

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

View File

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

View File

@ -194,7 +194,7 @@ RSpec.describe Settings::ImportsController do
let!(:rows) do let!(:rows) do
[ [
{ 'acct' => 'foo@bar' }, { '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) } ].map { |data| Fabricate(:bulk_import_row, bulk_import: bulk_import, data: data) }
end end

View File

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

View File

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

View File

@ -296,5 +296,51 @@ describe ApplicationHelper do
expect(helper.title).to eq 'site title' expect(helper.title).to eq 'site title'
expect(Rails.env).to have_received(:production?) expect(Rails.env).to have_received(:production?)
end 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
end end

View File

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

View File

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

View File

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

View File

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

View File

@ -69,7 +69,9 @@ RSpec.describe Remotable do
context 'with an invalid URL' do context 'with an invalid URL' do
before 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 end
it 'makes no request' do 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', [ it_behaves_like 'on successful import', 'following', 'merge', 'following_accounts.csv', [
{ 'acct' => 'user@example.com', 'show_reblogs' => true, 'notify' => false, 'languages' => nil }, { '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', [ it_behaves_like 'on successful import', 'muting', 'merge', 'muted_accounts.csv', [

View File

@ -199,15 +199,13 @@ RSpec.describe PublicFeed do
end end
describe 'with an account passed in' do 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 let!(:account) { Fabricate(:account) }
@account = Fabricate(:account)
end
it 'excludes statuses from accounts blocked by the account' do it 'excludes statuses from accounts blocked by the account' do
blocked = Fabricate(:account) blocked = Fabricate(:account)
@account.block!(blocked) account.block!(blocked)
blocked_status = Fabricate(:status, account: blocked) blocked_status = Fabricate(:status, account: blocked)
expect(subject).to_not include(blocked_status.id) 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 it 'excludes statuses from accounts who have blocked the account' do
blocker = Fabricate(:account) blocker = Fabricate(:account)
blocker.block!(@account) blocker.block!(account)
blocked_status = Fabricate(:status, account: blocker) blocked_status = Fabricate(:status, account: blocker)
expect(subject).to_not include(blocked_status.id) 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 it 'excludes statuses from accounts muted by the account' do
muted = Fabricate(:account) muted = Fabricate(:account)
@account.mute!(muted) account.mute!(muted)
muted_status = Fabricate(:status, account: muted) muted_status = Fabricate(:status, account: muted)
expect(subject).to_not include(muted_status.id) 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 it 'excludes statuses from accounts from personally blocked domains' do
blocked = Fabricate(:account, domain: 'example.com') blocked = Fabricate(:account, domain: 'example.com')
@account.block_domain!(blocked.domain) account.block_domain!(blocked.domain)
blocked_status = Fabricate(:status, account: blocked) blocked_status = Fabricate(:status, account: blocked)
expect(subject).to_not include(blocked_status.id) expect(subject).to_not include(blocked_status.id)
@ -239,7 +237,7 @@ RSpec.describe PublicFeed do
context 'with language preferences' do context 'with language preferences' do
it 'excludes statuses in languages not allowed by the account user' 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') en_status = Fabricate(:status, language: 'en')
es_status = Fabricate(:status, language: 'es') es_status = Fabricate(:status, language: 'es')
fr_status = Fabricate(:status, language: 'fr') fr_status = Fabricate(:status, language: 'fr')
@ -250,7 +248,7 @@ RSpec.describe PublicFeed do
end end
it 'includes all languages when user does not have a setting' do 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') en_status = Fabricate(:status, language: 'en')
es_status = Fabricate(:status, language: 'es') es_status = Fabricate(:status, language: 'es')
@ -260,7 +258,7 @@ RSpec.describe PublicFeed do
end end
it 'includes all languages when account does not have a user' do 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') en_status = Fabricate(:status, language: 'en')
es_status = Fabricate(:status, language: 'es') es_status = Fabricate(:status, language: 'es')

View File

@ -98,34 +98,44 @@ RSpec.describe SessionActivation do
end end
context 'when id exists' do context 'when id exists' do
let(:id) { '1' } let!(:session_activation) { Fabricate(:session_activation) }
it 'calls where.destroy_all' do it 'destroys the record' do
expect(described_class).to receive_message_chain(:where, :destroy_all) described_class.deactivate(session_activation.session_id)
.with(session_id: id).with(no_args)
described_class.deactivate(id) expect { session_activation.reload }.to raise_error(ActiveRecord::RecordNotFound)
end end
end end
end end
describe '.purge_old' do describe '.purge_old' do
it 'calls order.offset.destroy_all' do around do |example|
expect(described_class).to receive_message_chain(:order, :offset, :destroy_all) before = Rails.configuration.x.max_session_activations
.with('created_at desc').with(Rails.configuration.x.max_session_activations).with(no_args) 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 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
end end
describe '.exclusive' do 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 it 'preserves supplied record and destroys all others' do
expect(described_class).to receive_message_chain(:where, :not, :destroy_all) described_class.exclusive(wanted_session_activation.session_id)
.with(session_id: id).with(no_args)
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 end
end end

View File

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

View File

@ -11,10 +11,6 @@ if RUN_SYSTEM_SPECS
ENV['STREAMING_API_BASE_URL'] = "http://localhost:#{STREAMING_PORT}" ENV['STREAMING_API_BASE_URL'] = "http://localhost:#{STREAMING_PORT}"
end end
if RUN_SEARCH_SPECS
# Include any configuration or setups specific to search tests here
end
require File.expand_path('../config/environment', __dir__) require File.expand_path('../config/environment', __dir__)
abort('The Rails environment is running in production mode!') if Rails.env.production? abort('The Rails environment is running in production mode!') if Rails.env.production?
@ -35,8 +31,6 @@ Sidekiq.logger = nil
# System tests config # System tests config
DatabaseCleaner.strategy = [:deletion] DatabaseCleaner.strategy = [:deletion]
streaming_server_manager = StreamingServerManager.new
search_data_manager = SearchDataManager.new
Devise::Test::ControllerHelpers.module_eval do Devise::Test::ControllerHelpers.module_eval do
alias_method :original_sign_in, :sign_in alias_method :original_sign_in, :sign_in
@ -100,26 +94,7 @@ RSpec.configure do |config|
Capybara.current_driver = :rack_test Capybara.current_driver = :rack_test
end 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| 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] driven_by :selenium, using: :headless_chrome, screen_size: [1600, 1200]
# The streaming server needs access to the database # The streaming server needs access to the database
@ -136,12 +111,6 @@ RSpec.configure do |config|
self.use_transactional_tests = true self.use_transactional_tests = true
end 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| config.before do |example|
unless example.metadata[:paperclip_processing] unless example.metadata[:paperclip_processing]
allow_any_instance_of(Paperclip::Attachment).to receive(:post_process).and_return(true) # rubocop:disable RSpec/AnyInstance 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 end
describe 'Caching behavior' do describe 'Caching behavior' do
shared_examples 'cachable response' do shared_examples 'cachable response' do |http_success: false|
it 'does not set cookies' do it 'does not set cookies or set public cache control', :aggregate_failures do
expect(response.cookies).to be_empty 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[:max_age]&.to_i).to be_positive
expect(response.cache_control[:public]).to be_truthy expect(response.cache_control[:public]).to be_truthy
expect(response.cache_control[:private]).to be_falsy expect(response.cache_control[:private]).to be_falsy
expect(response.cache_control[:no_store]).to be_falsy expect(response.cache_control[:no_store]).to be_falsy
expect(response.cache_control[:no_cache]).to be_falsy expect(response.cache_control[:no_cache]).to be_falsy
expect(response).to have_http_status(200) if http_success
end end
end end
shared_examples 'non-cacheable response' do shared_examples 'non-cacheable response' do |http_success: false|
it 'sets private cache control' do it 'sets private cache control' do
expect(response.cache_control[:private]).to be_truthy expect(response.cache_control[:private]).to be_truthy
expect(response.cache_control[:no_store]).to be_truthy expect(response.cache_control[:no_store]).to be_truthy
expect(response).to have_http_status(200) if http_success
end end
end end
@ -149,7 +151,7 @@ describe 'Caching behavior' do
shared_examples 'language-dependent' do shared_examples 'language-dependent' do
it 'has a Vary on Accept-Language' 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
end end
@ -202,7 +204,7 @@ describe 'Caching behavior' do
it_behaves_like 'cachable response' it_behaves_like 'cachable response'
it 'has a Vary on Cookie' do 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
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint) 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_behaves_like 'cachable response'
it 'has a Vary on Authorization' do 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
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint) 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_behaves_like 'non-cacheable response'
it 'has a Vary on Cookie' do 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 end
end end
@ -311,11 +313,7 @@ describe 'Caching behavior' do
describe endpoint do describe endpoint do
before { get endpoint } before { get endpoint }
it_behaves_like 'non-cacheable response' it_behaves_like 'non-cacheable response', http_success: true
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end end
end end
@ -351,7 +349,7 @@ describe 'Caching behavior' do
it_behaves_like 'non-cacheable response' it_behaves_like 'non-cacheable response'
it 'has a Vary on Authorization' do 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 end
end end
@ -362,11 +360,7 @@ describe 'Caching behavior' do
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" } get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
end end
it_behaves_like 'non-cacheable response' it_behaves_like 'non-cacheable response', http_success: true
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end end
end end
@ -393,11 +387,7 @@ describe 'Caching behavior' do
context 'when allowed for local users only' do context 'when allowed for local users only' do
let(:show_domain_blocks) { 'users' } let(:show_domain_blocks) { 'users' }
it_behaves_like 'non-cacheable response' it_behaves_like 'non-cacheable response', http_success: true
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end end
context 'when disabled' do context 'when disabled' do
@ -421,11 +411,7 @@ describe 'Caching behavior' do
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' } get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end end
it_behaves_like 'cachable response' it_behaves_like 'cachable response', http_success: true
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end end
TestEndpoints::REQUIRE_SIGNATURE.each do |endpoint| 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' } get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end end
it_behaves_like 'non-cacheable response' it_behaves_like 'non-cacheable response', http_success: true
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end end
end end
end end
@ -456,11 +438,7 @@ describe 'Caching behavior' do
get '/actor', headers: { 'Accept' => 'application/activity+json' } get '/actor', headers: { 'Accept' => 'application/activity+json' }
end end
it_behaves_like 'cachable response' it_behaves_like 'cachable response', http_success: true
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end end
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint| (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' } get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end end
it_behaves_like 'cachable response' it_behaves_like 'cachable response', http_success: true
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end end
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint| (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' } get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end end
it_behaves_like 'non-cacheable response' it_behaves_like 'non-cacheable response', http_success: true
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end end
end end
end end
@ -528,11 +498,7 @@ describe 'Caching behavior' do
get '/actor', headers: { 'Accept' => 'application/activity+json' } get '/actor', headers: { 'Accept' => 'application/activity+json' }
end end
it_behaves_like 'cachable response' it_behaves_like 'cachable response', http_success: true
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end end
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint| (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' } get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end end
it_behaves_like 'cachable response' it_behaves_like 'cachable response', http_success: true
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end end
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint| (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' } get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end end
it_behaves_like 'non-cacheable response' it_behaves_like 'non-cacheable response', http_success: true
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end end
end end
end end
@ -591,11 +549,7 @@ describe 'Caching behavior' do
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' } get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end end
it_behaves_like 'cachable response' it_behaves_like 'cachable response', http_success: true
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end end
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint| (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_behaves_like 'non-cacheable response'
it 'has a Vary on Authorization' do 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 end
end end
@ -678,13 +632,15 @@ describe 'Caching behavior' do
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" } get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
end end
it_behaves_like 'non-cacheable response' it_behaves_like 'non-cacheable response', http_success: true
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end end
end end
end end
end end
private
def response_vary_headers
response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }
end
end end

View File

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

View File

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

View File

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

View File

@ -41,3 +41,38 @@ class SearchDataManager
Tag.destroy_all Tag.destroy_all
end end
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 @running_thread.join
end end
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' require 'rails_helper'
RSpec.describe BlacklistedEmailValidator, type: :validator do RSpec.describe BlacklistedEmailValidator do
describe '#validate' do describe '#validate' do
subject { described_class.new.validate(user); errors } subject { described_class.new.validate(user); errors }

View File

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

View File

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

View File

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

View File

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

1041
yarn.lock

File diff suppressed because it is too large Load Diff