1
0
Fork 0
forked from gitea/nas

Merge commit 'f378f10404' into kb_migration

This commit is contained in:
KMY 2023-06-07 11:45:52 +09:00
commit edb2a5dcf3
153 changed files with 1225 additions and 1025 deletions

View file

@ -1,99 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: npm
directory: '/'
schedule:
interval: weekly
open-pull-requests-limit: 99
allow:
- dependency-type: direct
ignore:
# This version needs to match Rails major version, so stick to 6.x only
- dependency-name: '@rails/ujs'
versions:
- '>= 7'
# TODO: This requires code changes for migration
- dependency-name: 'tesseract.js'
versions:
- '>= 3'
# TODO: This version needs manual updates for breaking changes
- dependency-name: 'react-hotkeys'
versions:
- '>= 2'
# TODO: This version requires code changes
- dependency-name: 'webpack-dev-server'
versions:
- '>= 4'
# TODO: This version was ignored in https://github.com/mastodon/mastodon/pull/15238
- dependency-name: 'webpack-cli'
versions:
- '>= 4'
- package-ecosystem: bundler
directory: '/'
schedule:
interval: weekly
open-pull-requests-limit: 99
allow:
- dependency-type: direct
ignore:
# This version needs to match Rails major version, so stick to 6.x only
- dependency-name: 'rails-i18n'
versions:
- '>= 7.0.0'
# This version needs manual updates https://github.com/rails/sprockets/blob/master/UPGRADING.md#guide-to-upgrading-from-sprockets-3x-to-4x
- dependency-name: 'sprockets'
versions:
- '>= 4.0.0'
# This version needs manual updates https://github.com/rails/sprockets/blob/master/UPGRADING.md#guide-to-upgrading-from-sprockets-3x-to-4x
- dependency-name: 'strong_migrations'
versions:
- '>= 1.0.0'
# This version needs updates and to sync with sidekiq-unique-jobs
- dependency-name: 'sidekiq'
versions:
- '>= 7.0.0'
# This version needs updates and to sync with sidekiq
- dependency-name: 'sidekiq-unique-jobs'
versions:
- '>= 8.0.0'
# This version needs updates and to sync with sidekiq and sidekiq-unique-jobs
- dependency-name: 'redis'
versions:
- '>= 5.0.0'
# TODO: was ignored in https://github.com/mastodon/mastodon/pull/13964
- dependency-name: 'fog-openstack'
versions:
- '>= 1.0.0'
# sassc dependency issue tracked in https://github.com/BetterErrors/better_errors/issues/516
- dependency-name: 'better_errors'
versions:
- '2.10.0'
- package-ecosystem: github-actions
directory: '/'
schedule:
interval: weekly
open-pull-requests-limit: 99
allow:
- dependency-type: direct
- package-ecosystem: docker
directory: '/'
schedule:
interval: weekly
open-pull-requests-limit: 99
ignore:
- dependency-name: 'moritzheiber/ruby-jemalloc'
update-types:
# only suggest patch releases for ruby and needs to sync with .ruby-version
- 'version-update:semver-minor'
- dependency-name: 'node'
update-types:
# only node minor releases allowed unless .nvmrc major is changed
- 'version-update:semver-major'

114
.github/renovate.json5 vendored Normal file
View file

@ -0,0 +1,114 @@
{
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
extends: [
'config:base',
':dependencyDashboard',
':labels(dependencies)',
':maintainLockFilesMonthly', // update non-direct dependencies monthly
':prConcurrentLimit10', // only 10 open PRs at the same time
],
stabilityDays: 3, // Wait 3 days after the package has been published before upgrading it
// packageRules order is important, they are applied from top to bottom and are merged,
// so for example grouping rules needs to be at the bottom
packageRules: [
{
// Ignore major version bumps for these node packages
matchManagers: ['npm'],
matchPackageNames: [
'@rails/ujs', // Needs to match the major Rails version
'tesseract.js', // Requires code changes
'react-hotkeys', // Requires code changes
// Requires Webpacker upgrade or replacement
'@types/webpack',
'babel-loader',
'compression-webpack-plugin',
'css-loader',
'imports-loader',
'mini-css-extract-plugin',
'postcss-loader',
'sass-loader',
'terser-webpack-plugin',
'webpack',
'webpack-assets-manifest',
'webpack-bundle-analyzer',
'webpack-dev-server',
'webpack-cli',
// react-router: Requires manual upgrade
'history',
'react-router-dom',
],
matchUpdateTypes: ['major'],
enabled: false,
},
{
// Ignore major version bumps for these Ruby packages
matchManagers: ['bundler'],
matchPackageNames: [
'sprockets', // Requires manual upgrade https://github.com/rails/sprockets/blob/master/UPGRADING.md#guide-to-upgrading-from-sprockets-3x-to-4x
'strong_migrations', // Requires manual upgrade
'sidekiq', // Requires manual upgrade
'sidekiq-unique-jobs', // Requires manual upgrades and sync with Sidekiq version
'redis', // Requires manual upgrade and sync with Sidekiq version
'fog-openstack', // TODO: was ignored in https://github.com/mastodon/mastodon/pull/13964
// Needs major Rails version bump
'rack',
'rails',
'rails-i18n',
],
matchUpdateTypes: ['major'],
enabled: false,
},
{
// Update Github Actions and Docker images weekly
matchManagers: ['github-actions', 'dockerfile', 'docker-compose'],
extends: ['schedule:weekly'],
},
{
// Ignore major & minor bumps for the ruby image, this needs to be synced with .ruby-version
matchManagers: ['dockerfile'],
matchPackageNames: ['moritzheiber/ruby-jemalloc'],
matchUpdateTypes: ['minor', 'major'],
enabled: false,
},
{
// Ignore major bump for the node image, this needs to be synced with .nvmrc
matchManagers: ['dockerfile'],
matchPackageNames: ['node'],
matchUpdateTypes: ['major'],
enabled: false,
},
{
// Ignore major postgres bumps in the docker-compose file, as those break dev environments
matchManagers: ['docker-compose'],
matchPackageNames: ['postgres'],
matchUpdateTypes: ['major'],
enabled: false,
},
{
// Update devDependencies every week, with one grouped PR
matchDepTypes: 'devDependencies',
matchUpdateTypes: ['patch', 'minor'],
excludePackageNames: [
'typescript', // Typescript has many changes in minor versions, needs to be checked every time
],
groupName: 'devDependencies (non-major)',
extends: ['schedule:weekly'],
},
{
// Update @types/* packages every week, with one grouped PR
matchPackagePrefixes: '@types/',
matchUpdateTypes: ['patch', 'minor'],
groupName: 'DefinitelyTyped types (non-major)',
extends: ['schedule:weekly'],
addLabels: ['typescript'],
},
// Add labels depending on package manager
{ matchManagers: ['npm', 'nvm'], addLabels: ['javascript'] },
{ matchManagers: ['bundler', 'ruby-version'], addLabels: ['ruby'] },
{ matchManagers: ['docker-compose', 'dockerfile'], addLabels: ['docker'] },
{ matchManagers: ['github-actions'], addLabels: ['github_actions'] },
],
}

View file

@ -3,6 +3,7 @@ on:
push: push:
branches-ignore: branches-ignore:
- 'dependabot/**' - 'dependabot/**'
- 'renovate/**'
paths: paths:
- 'package.json' - 'package.json'
- 'yarn.lock' - 'yarn.lock'

View file

@ -3,6 +3,7 @@ on:
push: push:
branches-ignore: branches-ignore:
- 'dependabot/**' - 'dependabot/**'
- 'renovate/**'
paths: paths:
- '.github/workflows/haml-lint-problem-matcher.json' - '.github/workflows/haml-lint-problem-matcher.json'
- '.github/workflows/lint-haml.yml' - '.github/workflows/lint-haml.yml'

View file

@ -3,6 +3,7 @@ on:
push: push:
branches-ignore: branches-ignore:
- 'dependabot/**' - 'dependabot/**'
- 'renovate/**'
paths: paths:
- 'package.json' - 'package.json'
- 'yarn.lock' - 'yarn.lock'

View file

@ -3,6 +3,7 @@ on:
push: push:
branches-ignore: branches-ignore:
- 'dependabot/**' - 'dependabot/**'
- 'renovate/**'
paths: paths:
- 'package.json' - 'package.json'
- 'yarn.lock' - 'yarn.lock'

View file

@ -3,6 +3,7 @@ on:
push: push:
branches-ignore: branches-ignore:
- 'dependabot/**' - 'dependabot/**'
- 'renovate/**'
paths: paths:
- '.github/workflows/lint-md.yml' - '.github/workflows/lint-md.yml'
- '.nvmrc' - '.nvmrc'

View file

@ -3,6 +3,7 @@ on:
push: push:
branches-ignore: branches-ignore:
- 'dependabot/**' - 'dependabot/**'
- 'renovate/**'
paths: paths:
- 'Gemfile*' - 'Gemfile*'
- '.rubocop*.yml' - '.rubocop*.yml'

View file

@ -3,6 +3,7 @@ on:
push: push:
branches-ignore: branches-ignore:
- 'dependabot/**' - 'dependabot/**'
- 'renovate/**'
paths: paths:
- 'package.json' - 'package.json'
- 'yarn.lock' - 'yarn.lock'

View file

@ -4,10 +4,12 @@ on:
push: push:
branches-ignore: branches-ignore:
- 'dependabot/**' - 'dependabot/**'
- 'renovate/**'
- 'l10n_main' - 'l10n_main'
pull_request_target: pull_request_target:
branches-ignore: branches-ignore:
- 'dependabot/**' - 'dependabot/**'
- 'renovate/**'
- 'l10n_main' - 'l10n_main'
types: [synchronize] types: [synchronize]

View file

@ -3,6 +3,7 @@ on:
push: push:
branches-ignore: branches-ignore:
- 'dependabot/**' - 'dependabot/**'
- 'renovate/**'
paths: paths:
- 'package.json' - 'package.json'
- 'yarn.lock' - 'yarn.lock'

View file

@ -3,6 +3,7 @@ on:
push: push:
branches-ignore: branches-ignore:
- 'dependabot/**' - 'dependabot/**'
- 'renovate/**'
pull_request: pull_request:
jobs: jobs:

View file

@ -3,6 +3,7 @@ on:
push: push:
branches-ignore: branches-ignore:
- 'dependabot/**' - 'dependabot/**'
- 'renovate/**'
pull_request: pull_request:
jobs: jobs:

View file

@ -4,6 +4,7 @@ on:
push: push:
branches-ignore: branches-ignore:
- 'dependabot/**' - 'dependabot/**'
- 'renovate/**'
pull_request: pull_request:
env: env:

View file

@ -237,79 +237,6 @@ RSpec/AnyInstance:
- 'spec/workers/activitypub/delivery_worker_spec.rb' - 'spec/workers/activitypub/delivery_worker_spec.rb'
- 'spec/workers/web/push_notification_worker_spec.rb' - 'spec/workers/web/push_notification_worker_spec.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: SkipBlocks, EnforcedStyle.
# SupportedStyles: described_class, explicit
RSpec/DescribedClass:
Exclude:
- 'spec/controllers/concerns/cache_concern_spec.rb'
- 'spec/controllers/concerns/challengable_concern_spec.rb'
- 'spec/lib/entity_cache_spec.rb'
- 'spec/lib/extractor_spec.rb'
- 'spec/lib/feed_manager_spec.rb'
- 'spec/lib/hash_object_spec.rb'
- 'spec/lib/ostatus/tag_manager_spec.rb'
- 'spec/lib/request_spec.rb'
- 'spec/lib/tag_manager_spec.rb'
- 'spec/lib/webfinger_resource_spec.rb'
- 'spec/mailers/notification_mailer_spec.rb'
- 'spec/mailers/user_mailer_spec.rb'
- 'spec/models/account_conversation_spec.rb'
- 'spec/models/account_domain_block_spec.rb'
- 'spec/models/account_migration_spec.rb'
- 'spec/models/account_spec.rb'
- 'spec/models/block_spec.rb'
- 'spec/models/domain_block_spec.rb'
- 'spec/models/email_domain_block_spec.rb'
- 'spec/models/export_spec.rb'
- 'spec/models/favourite_spec.rb'
- 'spec/models/follow_spec.rb'
- 'spec/models/identity_spec.rb'
- 'spec/models/import_spec.rb'
- 'spec/models/media_attachment_spec.rb'
- 'spec/models/notification_spec.rb'
- 'spec/models/relationship_filter_spec.rb'
- 'spec/models/report_filter_spec.rb'
- 'spec/models/session_activation_spec.rb'
- 'spec/models/setting_spec.rb'
- 'spec/models/site_upload_spec.rb'
- 'spec/models/status_pin_spec.rb'
- 'spec/models/status_spec.rb'
- 'spec/models/user_spec.rb'
- 'spec/policies/account_moderation_note_policy_spec.rb'
- 'spec/presenters/account_relationships_presenter_spec.rb'
- 'spec/presenters/status_relationships_presenter_spec.rb'
- 'spec/serializers/activitypub/note_serializer_spec.rb'
- 'spec/serializers/activitypub/update_poll_serializer_spec.rb'
- 'spec/serializers/rest/account_serializer_spec.rb'
- 'spec/services/activitypub/fetch_remote_account_service_spec.rb'
- 'spec/services/activitypub/fetch_remote_actor_service_spec.rb'
- 'spec/services/activitypub/fetch_remote_key_service_spec.rb'
- 'spec/services/after_block_domain_from_account_service_spec.rb'
- 'spec/services/authorize_follow_service_spec.rb'
- 'spec/services/batched_remove_status_service_spec.rb'
- 'spec/services/block_domain_service_spec.rb'
- 'spec/services/block_service_spec.rb'
- 'spec/services/bootstrap_timeline_service_spec.rb'
- 'spec/services/clear_domain_media_service_spec.rb'
- 'spec/services/favourite_service_spec.rb'
- 'spec/services/follow_service_spec.rb'
- 'spec/services/import_service_spec.rb'
- 'spec/services/post_status_service_spec.rb'
- 'spec/services/precompute_feed_service_spec.rb'
- 'spec/services/process_mentions_service_spec.rb'
- 'spec/services/purge_domain_service_spec.rb'
- 'spec/services/reblog_service_spec.rb'
- 'spec/services/reject_follow_service_spec.rb'
- 'spec/services/remove_from_followers_service_spec.rb'
- 'spec/services/remove_status_service_spec.rb'
- 'spec/services/unallow_domain_service_spec.rb'
- 'spec/services/unblock_service_spec.rb'
- 'spec/services/unfollow_service_spec.rb'
- 'spec/services/unmute_service_spec.rb'
- 'spec/services/update_account_service_spec.rb'
- 'spec/validators/note_length_validator_spec.rb'
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
RSpec/EmptyExampleGroup: RSpec/EmptyExampleGroup:
Exclude: Exclude:
@ -466,30 +393,6 @@ RSpec/MessageSpies:
- 'spec/spec_helper.rb' - 'spec/spec_helper.rb'
- 'spec/validators/status_length_validator_spec.rb' - 'spec/validators/status_length_validator_spec.rb'
RSpec/MissingExampleGroupArgument:
Exclude:
- 'spec/controllers/accounts_controller_spec.rb'
- 'spec/controllers/activitypub/collections_controller_spec.rb'
- 'spec/controllers/admin/statuses_controller_spec.rb'
- 'spec/controllers/admin/users/roles_controller_spec.rb'
- 'spec/controllers/api/v1/accounts_controller_spec.rb'
- 'spec/controllers/api/v1/admin/account_actions_controller_spec.rb'
- 'spec/controllers/api/v1/admin/domain_allows_controller_spec.rb'
- 'spec/controllers/api/v1/statuses_controller_spec.rb'
- 'spec/controllers/auth/registrations_controller_spec.rb'
- 'spec/features/log_in_spec.rb'
- 'spec/lib/activitypub/activity/undo_spec.rb'
- 'spec/lib/status_reach_finder_spec.rb'
- 'spec/models/account_spec.rb'
- 'spec/models/email_domain_block_spec.rb'
- 'spec/models/trends/statuses_spec.rb'
- 'spec/models/trends/tags_spec.rb'
- 'spec/models/user_role_spec.rb'
- 'spec/models/user_spec.rb'
- 'spec/services/fetch_link_card_service_spec.rb'
- 'spec/services/notify_service_spec.rb'
- 'spec/services/process_mentions_service_spec.rb'
RSpec/MultipleExpectations: RSpec/MultipleExpectations:
Max: 19 Max: 19
@ -1491,52 +1394,6 @@ Style/RedundantFetchBlock:
- 'config/initializers/paperclip.rb' - 'config/initializers/paperclip.rb'
- 'config/puma.rb' - 'config/puma.rb'
# This cop supports safe autocorrection (--autocorrect).
Style/RedundantRegexpCharacterClass:
Exclude:
- 'app/lib/link_details_extractor.rb'
- 'app/lib/tag_manager.rb'
- 'app/models/domain_allow.rb'
- 'app/models/domain_block.rb'
- 'app/services/fetch_oembed_service.rb'
- 'config/initializers/rack_attack.rb'
- 'lib/tasks/emojis.rake'
- 'lib/tasks/mastodon.rake'
# This cop supports safe autocorrection (--autocorrect).
Style/RedundantRegexpEscape:
Exclude:
- 'app/lib/webfinger_resource.rb'
- 'app/models/account.rb'
- 'app/models/tag.rb'
- 'app/services/fetch_link_card_service.rb'
- 'config/initializers/twitter_regex.rb'
- 'lib/paperclip/color_extractor.rb'
- 'lib/tasks/mastodon.rake'
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed
Style/RegexpLiteral:
Exclude:
- 'app/lib/link_details_extractor.rb'
- 'app/lib/plain_text_formatter.rb'
- 'app/lib/tag_manager.rb'
- 'app/lib/text_formatter.rb'
- 'app/models/account.rb'
- 'app/models/domain_allow.rb'
- 'app/models/domain_block.rb'
- 'app/models/site_upload.rb'
- 'app/models/tag.rb'
- 'app/services/backup_service.rb'
- 'app/services/fetch_oembed_service.rb'
- 'app/services/search_service.rb'
- 'config/initializers/rack_attack.rb'
- 'config/initializers/twitter_regex.rb'
- 'config/routes.rb'
- 'lib/mastodon/premailer_webpack_strategy.rb'
- 'lib/tasks/mastodon.rake'
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength.
# AllowedMethods: present?, blank?, presence, try, try! # AllowedMethods: present?, blank?, presence, try, try!

View file

@ -71,7 +71,7 @@ module Admin
end end
def resource_params def resource_params
params.require(:webhook).permit(:url, events: []) params.require(:webhook).permit(:url, :template, events: [])
end end
end end
end end

View file

@ -121,10 +121,10 @@ class DropdownMenu extends PureComponent {
return <li key={`sep-${i}`} className='dropdown-menu__separator' />; return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
} }
const { text, href = '#', target = '_blank', method } = option; const { text, href = '#', target = '_blank', method, dangerous } = option;
return ( return (
<li className='dropdown-menu__item' key={`${text}-${i}`}> <li className={classNames('dropdown-menu__item', { 'dropdown-menu__item--dangerous': dangerous })} key={`${text}-${i}`}>
<a href={href} target={target} data-method={method} rel='noopener noreferrer' role='button' tabIndex={0} ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}> <a href={href} target={target} data-method={method} rel='noopener noreferrer' role='button' tabIndex={0} ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
{text} {text}
</a> </a>

View file

@ -1,28 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { FormattedMessage } from 'react-intl';
export default class LoadMore extends PureComponent {
static propTypes = {
onClick: PropTypes.func,
disabled: PropTypes.bool,
visible: PropTypes.bool,
};
static defaultProps = {
visible: true,
};
render() {
const { disabled, visible } = this.props;
return (
<button type='button' className='load-more' disabled={disabled || !visible} style={{ visibility: visible ? 'visible' : 'hidden' }} onClick={this.props.onClick}>
<FormattedMessage id='status.load_more' defaultMessage='Load more' />
</button>
);
}
}

View file

@ -0,0 +1,24 @@
import { FormattedMessage } from 'react-intl';
interface Props {
onClick: (event: React.MouseEvent) => void;
disabled?: boolean;
visible?: boolean;
}
export const LoadMore: React.FC<Props> = ({
onClick,
disabled,
visible = true,
}) => {
return (
<button
type='button'
className='load-more'
disabled={disabled || !visible}
style={{ visibility: visible ? 'visible' : 'hidden' }}
onClick={onClick}
>
<FormattedMessage id='status.load_more' defaultMessage='Load more' />
</button>
);
};

View file

@ -15,7 +15,7 @@ import IntersectionObserverArticleContainer from '../containers/intersection_obs
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen'; import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen';
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper'; import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
import LoadMore from './load_more'; import { LoadMore } from './load_more';
import LoadPending from './load_pending'; import LoadPending from './load_pending';
import LoadingIndicator from './loading_indicator'; import LoadingIndicator from './loading_indicator';

View file

@ -304,8 +304,8 @@ class StatusActionBar extends ImmutablePureComponent {
if (writtenByMe) { if (writtenByMe) {
menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick }); menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick });
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick, dangerous: true });
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick }); menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick, dangerous: true });
} else { } else {
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.handleMentionClick }); menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.handleMentionClick });
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick }); menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick });
@ -314,22 +314,22 @@ class StatusActionBar extends ImmutablePureComponent {
if (relationship && relationship.get('muting')) { if (relationship && relationship.get('muting')) {
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick }); menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
} else { } else {
menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.handleMuteClick }); menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.handleMuteClick, dangerous: true });
} }
if (relationship && relationship.get('blocking')) { if (relationship && relationship.get('blocking')) {
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick }); menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick });
} else { } else {
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick }); menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick, dangerous: true });
} }
if (!this.props.onFilter) { if (!this.props.onFilter) {
menu.push(null); menu.push(null);
menu.push({ text: intl.formatMessage(messages.filter), action: this.handleFilterClick }); menu.push({ text: intl.formatMessage(messages.filter), action: this.handleFilterClick, dangerous: true });
menu.push(null); menu.push(null);
} }
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.handleReport }); menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.handleReport, dangerous: true });
if (account.get('acct') !== account.get('username')) { if (account.get('acct') !== account.get('username')) {
const domain = account.get('acct').split('@')[1]; const domain = account.get('acct').split('@')[1];
@ -339,7 +339,7 @@ class StatusActionBar extends ImmutablePureComponent {
if (relationship && relationship.get('domain_blocking')) { if (relationship && relationship.get('domain_blocking')) {
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain }); menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain });
} else { } else {
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain }); menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain, dangerous: true });
} }
} }

View file

@ -335,16 +335,16 @@ class Header extends ImmutablePureComponent {
if (account.getIn(['relationship', 'muting'])) { if (account.getIn(['relationship', 'muting'])) {
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute }); menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute });
} else { } else {
menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.props.onMute }); menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.props.onMute, dangerous: true });
} }
if (account.getIn(['relationship', 'blocking'])) { if (account.getIn(['relationship', 'blocking'])) {
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock }); menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock });
} else { } else {
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock }); menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock, dangerous: true });
} }
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport }); menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport, dangerous: true });
} }
if (signedIn && isRemote) { if (signedIn && isRemote) {
@ -353,7 +353,7 @@ class Header extends ImmutablePureComponent {
if (account.getIn(['relationship', 'domain_blocking'])) { if (account.getIn(['relationship', 'domain_blocking'])) {
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain: remoteDomain }), action: this.props.onUnblockDomain }); menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain: remoteDomain }), action: this.props.onUnblockDomain });
} else { } else {
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain: remoteDomain }), action: this.props.onBlockDomain }); menu.push({ text: intl.formatMessage(messages.blockDomain, { domain: remoteDomain }), action: this.props.onBlockDomain, dangerous: true });
} }
} }

View file

@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import { lookupAccount, fetchAccount } from 'mastodon/actions/accounts'; import { lookupAccount, fetchAccount } from 'mastodon/actions/accounts';
import { openModal } from 'mastodon/actions/modal'; import { openModal } from 'mastodon/actions/modal';
import ColumnBackButton from 'mastodon/components/column_back_button'; import ColumnBackButton from 'mastodon/components/column_back_button';
import LoadMore from 'mastodon/components/load_more'; import { LoadMore } from 'mastodon/components/load_more';
import LoadingIndicator from 'mastodon/components/loading_indicator'; import LoadingIndicator from 'mastodon/components/loading_indicator';
import ScrollContainer from 'mastodon/containers/scroll_container'; import ScrollContainer from 'mastodon/containers/scroll_container';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error'; import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';

View file

@ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import LoadMore from 'mastodon/components/load_more'; import { LoadMore } from 'mastodon/components/load_more';
import { ImmutableHashtag as Hashtag } from '../../../components/hashtag'; import { ImmutableHashtag as Hashtag } from '../../../components/hashtag';
import AccountContainer from '../../../containers/account_container'; import AccountContainer from '../../../containers/account_container';

View file

@ -13,7 +13,7 @@ import { addColumn, removeColumn, moveColumn, changeColumnParams } from 'mastodo
import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory'; import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory';
import Column from 'mastodon/components/column'; import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header'; import ColumnHeader from 'mastodon/components/column_header';
import LoadMore from 'mastodon/components/load_more'; import { LoadMore } from 'mastodon/components/load_more';
import LoadingIndicator from 'mastodon/components/loading_indicator'; import LoadingIndicator from 'mastodon/components/loading_indicator';
import { RadioButton } from 'mastodon/components/radio_button'; import { RadioButton } from 'mastodon/components/radio_button';
import ScrollContainer from 'mastodon/containers/scroll_container'; import ScrollContainer from 'mastodon/containers/scroll_container';

View file

@ -11,7 +11,7 @@ import { connect } from 'react-redux';
import { expandSearch } from 'mastodon/actions/search'; import { expandSearch } from 'mastodon/actions/search';
import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag'; import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
import LoadMore from 'mastodon/components/load_more'; import { LoadMore } from 'mastodon/components/load_more';
import LoadingIndicator from 'mastodon/components/loading_indicator'; import LoadingIndicator from 'mastodon/components/loading_indicator';
import Account from 'mastodon/containers/account_container'; import Account from 'mastodon/containers/account_container';
import Status from 'mastodon/containers/status_container'; import Status from 'mastodon/containers/status_container';

View file

@ -16,6 +16,8 @@ const messages = defineMessages({
dislike_description: { id: 'report.reasons.dislike_description', defaultMessage: 'It is not something you want to see' }, dislike_description: { id: 'report.reasons.dislike_description', defaultMessage: 'It is not something you want to see' },
spam: { id: 'report.reasons.spam', defaultMessage: 'It\'s spam' }, spam: { id: 'report.reasons.spam', defaultMessage: 'It\'s spam' },
spam_description: { id: 'report.reasons.spam_description', defaultMessage: 'Malicious links, fake engagement, or repetitive replies' }, spam_description: { id: 'report.reasons.spam_description', defaultMessage: 'Malicious links, fake engagement, or repetitive replies' },
legal: { id: 'report.reasons.legal', defaultMessage: 'It\'s illegal' },
legal_description: { id: 'report.reasons.legal_description', defaultMessage: 'You believe it violates the law of your or the server\'s country' },
violation: { id: 'report.reasons.violation', defaultMessage: 'It violates server rules' }, violation: { id: 'report.reasons.violation', defaultMessage: 'It violates server rules' },
violation_description: { id: 'report.reasons.violation_description', defaultMessage: 'You are aware that it breaks specific rules' }, violation_description: { id: 'report.reasons.violation_description', defaultMessage: 'You are aware that it breaks specific rules' },
other: { id: 'report.reasons.other', defaultMessage: 'It\'s something else' }, other: { id: 'report.reasons.other', defaultMessage: 'It\'s something else' },
@ -69,11 +71,13 @@ class Category extends PureComponent {
const options = rules.size > 0 ? [ const options = rules.size > 0 ? [
'dislike', 'dislike',
'spam', 'spam',
'legal',
'violation', 'violation',
'other', 'other',
] : [ ] : [
'dislike', 'dislike',
'spam', 'spam',
'legal',
'other', 'other',
]; ];

View file

@ -239,8 +239,8 @@ class ActionBar extends PureComponent {
menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick }); menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
menu.push(null); menu.push(null);
menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick }); menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick });
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick, dangerous: true });
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick }); menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick, dangerous: true });
} else { } else {
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
menu.push(null); menu.push(null);
@ -248,16 +248,16 @@ class ActionBar extends PureComponent {
if (relationship && relationship.get('muting')) { if (relationship && relationship.get('muting')) {
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick }); menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
} else { } else {
menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.handleMuteClick }); menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.handleMuteClick, dangerous: true });
} }
if (relationship && relationship.get('blocking')) { if (relationship && relationship.get('blocking')) {
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick }); menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick });
} else { } else {
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick }); menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick, dangerous: true });
} }
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport }); menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport, dangerous: true });
if (account.get('acct') !== account.get('username')) { if (account.get('acct') !== account.get('username')) {
const domain = account.get('acct').split('@')[1]; const domain = account.get('acct').split('@')[1];
@ -267,7 +267,7 @@ class ActionBar extends PureComponent {
if (relationship && relationship.get('domain_blocking')) { if (relationship && relationship.get('domain_blocking')) {
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain }); menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain });
} else { } else {
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain }); menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain, dangerous: true });
} }
} }

View file

@ -548,6 +548,8 @@
"report.placeholder": "Additional comments", "report.placeholder": "Additional comments",
"report.reasons.dislike": "I don't like it", "report.reasons.dislike": "I don't like it",
"report.reasons.dislike_description": "It is not something you want to see", "report.reasons.dislike_description": "It is not something you want to see",
"report.reasons.legal": "It's illegal",
"report.reasons.legal_description": "You believe it violates the law of your or the server's country",
"report.reasons.other": "It's something else", "report.reasons.other": "It's something else",
"report.reasons.other_description": "The issue does not fit into other categories", "report.reasons.other_description": "The issue does not fit into other categories",
"report.reasons.spam": "It's spam", "report.reasons.spam": "It's spam",

View file

@ -1656,105 +1656,6 @@ a .account__avatar {
gap: 4px; gap: 4px;
} }
.account__disclaimer {
padding: 10px;
border-top: 1px solid lighten($ui-base-color, 8%);
color: $dark-text-color;
strong {
font-weight: 500;
@each $lang in $cjk-langs {
&:lang(#{$lang}) {
font-weight: 700;
}
}
}
a {
font-weight: 500;
color: inherit;
text-decoration: underline;
&:hover,
&:focus,
&:active {
text-decoration: none;
}
}
}
.account__action-bar {
border-top: 1px solid lighten($ui-base-color, 8%);
border-bottom: 1px solid lighten($ui-base-color, 8%);
line-height: 36px;
overflow: hidden;
flex: 0 0 auto;
display: flex;
}
.account__action-bar-dropdown {
padding: 10px;
.icon-button {
vertical-align: middle;
}
.dropdown--active {
.dropdown__content.dropdown__right {
inset-inline-start: 6px;
inset-inline-end: initial;
}
&::after {
bottom: initial;
margin-inline-start: 11px;
margin-top: -7px;
inset-inline-end: initial;
}
}
}
.account__action-bar-links {
display: flex;
flex: 1 1 auto;
line-height: 18px;
text-align: center;
}
.account__action-bar__tab {
text-decoration: none;
overflow: hidden;
flex: 0 1 100%;
border-inline-end: 1px solid lighten($ui-base-color, 8%);
padding: 10px 0;
border-bottom: 4px solid transparent;
&.active {
border-bottom: 4px solid $ui-highlight-color;
}
& > span {
display: block;
text-transform: uppercase;
font-size: 11px;
color: $darker-text-color;
}
strong {
display: block;
font-size: 15px;
font-weight: 500;
color: $primary-text-color;
@each $lang in $cjk-langs {
&:lang(#{$lang}) {
font-weight: 700;
}
}
}
}
.account-authorize { .account-authorize {
padding: 14px 10px; padding: 14px 10px;
@ -2106,36 +2007,18 @@ a.account__display-name {
} }
.dropdown-animation { .dropdown-animation {
animation: dropdown 300ms cubic-bezier(0.1, 0.7, 0.1, 1); animation: dropdown 150ms cubic-bezier(0.1, 0.7, 0.1, 1);
@keyframes dropdown { @keyframes dropdown {
from { from {
opacity: 0; opacity: 0;
transform: scaleX(0.85) scaleY(0.75);
} }
to { to {
opacity: 1; opacity: 1;
transform: scaleX(1) scaleY(1);
} }
} }
&.top {
transform-origin: bottom;
}
&.right {
transform-origin: left;
}
&.bottom {
transform-origin: top;
}
&.left {
transform-origin: right;
}
.reduce-motion & { .reduce-motion & {
animation: none; animation: none;
} }
@ -2151,16 +2034,17 @@ a.account__display-name {
} }
.dropdown-menu__separator { .dropdown-menu__separator {
border-bottom: 1px solid darken($ui-secondary-color, 8%); border-bottom: 1px solid var(--dropdown-border-color);
margin: 5px 7px 6px; margin: 5px 0;
height: 0; height: 0;
} }
.dropdown-menu { .dropdown-menu {
background: $ui-secondary-color; background: var(--dropdown-background-color);
padding: 4px 0; border: 1px solid var(--dropdown-border-color);
padding: 4px;
border-radius: 4px; border-radius: 4px;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4); box-shadow: var(--dropdown-shadow);
z-index: 9999; z-index: 9999;
&__text-button { &__text-button {
@ -2181,12 +2065,13 @@ a.account__display-name {
&__container { &__container {
&__header { &__header {
border-bottom: 1px solid darken($ui-secondary-color, 8%); border-bottom: 1px solid var(--dropdown-border-color);
padding: 4px 14px; padding: 10px 14px;
padding-bottom: 8px; padding-bottom: 14px;
margin-bottom: 4px;
font-size: 13px; font-size: 13px;
line-height: 18px; line-height: 18px;
color: $inverted-text-color; color: $darker-text-color;
} }
&__list { &__list {
@ -2223,103 +2108,43 @@ a.account__display-name {
} }
} }
.dropdown-menu__arrow {
position: absolute;
&::before {
content: '';
display: block;
width: 14px;
height: 5px;
background-color: $ui-secondary-color;
mask-image: url("data:image/svg+xml;utf8,<svg width='14' height='5' xmlns='http://www.w3.org/2000/svg'><path d='M7 0L0 5h14L7 0z' fill='white'/></svg>");
}
&.top {
bottom: -5px;
&::before {
transform: rotate(180deg);
}
}
&.right {
inset-inline-start: -9px;
&::before {
transform: rotate(-90deg);
}
}
&.bottom {
top: -5px;
}
&.left {
inset-inline-end: -9px;
&::before {
transform: rotate(90deg);
}
}
}
.dropdown-menu__item { .dropdown-menu__item {
font-size: 13px; font-size: 13px;
line-height: 18px; line-height: 18px;
font-weight: 500;
display: block; display: block;
color: $inverted-text-color;
&--dangerous {
color: $error-value-color;
}
a, a,
button { button {
font-family: inherit; font: inherit;
font-size: inherit;
line-height: inherit;
display: block; display: block;
width: 100%; width: 100%;
padding: 4px 14px; padding: 10px 14px;
border: 0; border: 0;
margin: 0; margin: 0;
background: transparent;
box-sizing: border-box; box-sizing: border-box;
text-decoration: none; text-decoration: none;
background: $ui-secondary-color;
color: inherit; color: inherit;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
text-align: inherit; text-align: inherit;
border-radius: 4px;
&:focus, &:focus,
&:hover, &:hover,
&:active { &:active {
background: $ui-highlight-color; background: var(--dropdown-border-color);
color: $secondary-text-color;
outline: 0; outline: 0;
} }
} }
} }
.dropdown-menu__item--text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 4px 14px;
}
.dropdown-menu__item.edited-timestamp__history__item {
border-bottom: 1px solid darken($ui-secondary-color, 8%);
&:last-child {
border-bottom: 0;
}
&.dropdown-menu__item--text,
a,
button {
padding: 8px 14px;
}
}
.inline-account { .inline-account {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@ -2335,62 +2160,6 @@ a.account__display-name {
} }
} }
.dropdown--active .dropdown__content {
display: block;
line-height: 18px;
max-width: 311px;
inset-inline-end: 0;
text-align: start;
z-index: 9999;
& > ul {
list-style: none;
background: $ui-secondary-color;
padding: 4px 0;
border-radius: 4px;
box-shadow: 0 0 15px rgba($base-shadow-color, 0.4);
min-width: 140px;
position: relative;
}
&.dropdown__right {
inset-inline-end: 0;
}
&.dropdown__left {
& > ul {
inset-inline-start: -98px;
}
}
& > ul > li > a {
font-size: 13px;
line-height: 18px;
display: block;
padding: 4px 14px;
box-sizing: border-box;
text-decoration: none;
background: $ui-secondary-color;
color: $inverted-text-color;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:focus {
outline: 0;
}
&:hover {
background: $ui-highlight-color;
color: $secondary-text-color;
}
}
}
.dropdown__icon {
vertical-align: middle;
}
.columns-area { .columns-area {
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 auto;
@ -3168,10 +2937,10 @@ $ui-header-height: 55px;
.compose-form__highlightable { .compose-form__highlightable {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden;
flex: 0 1 auto; flex: 0 1 auto;
border-radius: 4px; border-radius: 4px;
transition: box-shadow 300ms linear; transition: box-shadow 300ms linear;
min-height: 0;
&.active { &.active {
transition: none; transition: none;
@ -3213,7 +2982,6 @@ $ui-header-height: 55px;
.compose-form { .compose-form {
flex: 1; flex: 1;
overflow-y: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 310px; min-height: 310px;

View file

@ -62,3 +62,10 @@ $no-gap-breakpoint: 1175px;
$font-sans-serif: 'mastodon-font-sans-serif' !default; $font-sans-serif: 'mastodon-font-sans-serif' !default;
$font-display: 'mastodon-font-display' !default; $font-display: 'mastodon-font-display' !default;
$font-monospace: 'mastodon-font-monospace' !default; $font-monospace: 'mastodon-font-monospace' !default;
:root {
--dropdown-border-color: #{lighten($ui-base-color, 12%)};
--dropdown-background-color: #{lighten($ui-base-color, 4%)};
--dropdown-shadow: 0 20px 25px -5px #{rgba($base-shadow-color, 0.25)},
0 8px 10px -6px #{rgba($base-shadow-color, 0.25)};
}

View file

@ -7,15 +7,15 @@ class LinkDetailsExtractor
# Some publications wrap their JSON-LD data in their <script> tags # Some publications wrap their JSON-LD data in their <script> tags
# in commented-out CDATA blocks, they need to be removed before # in commented-out CDATA blocks, they need to be removed before
# attempting to parse JSON # attempting to parse JSON
CDATA_JUNK_PATTERN = %r{^[\s]*( CDATA_JUNK_PATTERN = %r{^\s*(
(/\*[\s]*<!\[CDATA\[[\s]*\*/) # Block comment style opening (/\*\s*<!\[CDATA\[\s*\*/) # Block comment style opening
| |
(//[\s]*<!\[CDATA\[) # Single-line comment style opening (//\s*<!\[CDATA\[) # Single-line comment style opening
| |
(/\*[\s]*\]\]>[\s]*\*/) # Block comment style closing (/\*\s*\]\]>\s*\*/) # Block comment style closing
| |
(//[\s]*\]\]>) # Single-line comment style closing (//\s*\]\]>) # Single-line comment style closing
)[\s]*$}x )\s*$}x
class StructuredData class StructuredData
SUPPORTED_TYPES = %w( SUPPORTED_TYPES = %w(
@ -204,7 +204,7 @@ class LinkDetailsExtractor
def host_to_url(str) def host_to_url(str)
return if str.blank? return if str.blank?
str.start_with?(/https?:\/\//) ? str : "http://#{str}" str.start_with?(%r{https?://}) ? str : "http://#{str}"
end end
def valid_url_or_nil(str, same_origin_only: false) def valid_url_or_nil(str, same_origin_only: false)

View file

@ -3,7 +3,7 @@
class PlainTextFormatter class PlainTextFormatter
include ActionView::Helpers::TextHelper include ActionView::Helpers::TextHelper
NEWLINE_TAGS_RE = /(<br \/>|<br>|<\/p>)+/ NEWLINE_TAGS_RE = %r{(<br />|<br>|</p>)+}
attr_reader :text, :local attr_reader :text, :local

View file

@ -7,18 +7,18 @@ class TagManager
include RoutingHelper include RoutingHelper
def web_domain?(domain) def web_domain?(domain)
domain.nil? || domain.gsub(/[\/]/, '').casecmp(Rails.configuration.x.web_domain).zero? domain.nil? || domain.delete('/').casecmp(Rails.configuration.x.web_domain).zero?
end end
def local_domain?(domain) def local_domain?(domain)
domain.nil? || domain.gsub(/[\/]/, '').casecmp(Rails.configuration.x.local_domain).zero? domain.nil? || domain.delete('/').casecmp(Rails.configuration.x.local_domain).zero?
end end
def normalize_domain(domain) def normalize_domain(domain)
return if domain.nil? return if domain.nil?
uri = Addressable::URI.new uri = Addressable::URI.new
uri.host = domain.gsub(/[\/]/, '') uri.host = domain.delete('/')
uri.normalized_host uri.normalized_host
end end

View file

@ -5,7 +5,7 @@ class TextFormatter
include ERB::Util include ERB::Util
include RoutingHelper include RoutingHelper
URL_PREFIX_REGEX = /\A(https?:\/\/(www\.)?|xmpp:)/ URL_PREFIX_REGEX = %r{\A(https?://(www\.)?|xmpp:)}
DEFAULT_REL = %w(nofollow noopener noreferrer).freeze DEFAULT_REL = %w(nofollow noopener noreferrer).freeze

View file

@ -13,7 +13,7 @@ class WebfingerResource
case resource case resource
when /\Ahttps?/i when /\Ahttps?/i
username_from_url username_from_url
when /\@/ when /@/
username_from_acct username_from_acct
else else
raise InvalidRequest raise InvalidRequest

View file

@ -0,0 +1,67 @@
# frozen_string_literal: true
class Webhooks::PayloadRenderer
class DocumentTraverser
INT_REGEX = /[0-9]+/
def initialize(document)
@document = document.with_indifferent_access
end
def get(path)
value = @document.dig(*parse_path(path))
string = Oj.dump(value)
# We want to make sure people can use the variable inside
# other strings, so it can't be wrapped in quotes.
if value.is_a?(String)
string[1...-1]
else
string
end
end
private
def parse_path(path)
path.split('.').filter_map do |segment|
if segment.match(INT_REGEX)
segment.to_i
else
segment.presence
end
end
end
end
class TemplateParser < Parslet::Parser
rule(:dot) { str('.') }
rule(:digit) { match('[0-9]') }
rule(:property_name) { match('[a-z_]').repeat(1) }
rule(:array_index) { digit.repeat(1) }
rule(:segment) { (property_name | array_index) }
rule(:path) { property_name >> (dot >> segment).repeat }
rule(:variable) { (str('}}').absent? >> path).repeat.as(:variable) }
rule(:expression) { str('{{') >> variable >> str('}}') }
rule(:text) { (str('{{').absent? >> any).repeat(1) }
rule(:text_with_expressions) { (text.as(:text) | expression).repeat.as(:text) }
root(:text_with_expressions)
end
EXPRESSION_REGEXP = /
\{\{
[a-z_]+
(\.
([a-z_]+|[0-9]+)
)*
\}\}
/iox
def initialize(json)
@document = DocumentTraverser.new(Oj.load(json))
end
def render(template)
template.gsub(EXPRESSION_REGEXP) { |match| @document.get(match[2...-2]) }
end
end

View file

@ -66,9 +66,9 @@ class Account < ApplicationRecord
trust_level trust_level
) )
USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i USERNAME_RE = /[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?/i
MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[[:word:]\.\-]+[[:word:]]+)?)/i MENTION_RE = %r{(?<=^|[^/[:word:]])@((#{USERNAME_RE})(?:@[[:word:].-]+[[:word:]]+)?)}i
URL_PREFIX_RE = /\Ahttp(s?):\/\/[^\/]+/ URL_PREFIX_RE = %r{\Ahttp(s?)://[^/]+}
USERNAME_ONLY_RE = /\A#{USERNAME_RE}\z/i USERNAME_ONLY_RE = /\A#{USERNAME_RE}\z/i
include Attachmentable include Attachmentable

View file

@ -35,7 +35,7 @@ class DomainAllow < ApplicationRecord
def rule_for(domain) def rule_for(domain)
return if domain.blank? return if domain.blank?
uri = Addressable::URI.new.tap { |u| u.host = domain.gsub(/[\/]/, '') } uri = Addressable::URI.new.tap { |u| u.host = domain.delete('/') }
find_by(domain: uri.normalized_host) find_by(domain: uri.normalized_host)
end end

View file

@ -119,7 +119,7 @@ class DomainBlock < ApplicationRecord
def rule_for(domain) def rule_for(domain)
return if domain.blank? return if domain.blank?
uri = Addressable::URI.new.tap { |u| u.host = domain.strip.gsub(/[\/]/, '') } uri = Addressable::URI.new.tap { |u| u.host = domain.strip.delete('/') }
segments = uri.normalized_host.split('.') segments = uri.normalized_host.split('.')
variants = segments.map.with_index { |_, i| segments[i..-1].join('.') } variants = segments.map.with_index { |_, i| segments[i..-1].join('.') }

View file

@ -51,6 +51,7 @@ class Report < ApplicationRecord
enum category: { enum category: {
other: 0, other: 0,
spam: 1_000, spam: 1_000,
legal: 1_500,
violation: 2_000, violation: 2_000,
} }

View file

@ -43,7 +43,7 @@ class SiteUpload < ApplicationRecord
has_attached_file :file, styles: ->(file) { STYLES[file.instance.var.to_sym] }, convert_options: { all: '-coalesce +profile "!icc,*" +set modify-date +set create-date' }, processors: [:lazy_thumbnail, :blurhash_transcoder, :type_corrector] has_attached_file :file, styles: ->(file) { STYLES[file.instance.var.to_sym] }, convert_options: { all: '-coalesce +profile "!icc,*" +set modify-date +set create-date' }, processors: [:lazy_thumbnail, :blurhash_transcoder, :type_corrector]
validates_attachment_content_type :file, content_type: /\Aimage\/.*\z/ validates_attachment_content_type :file, content_type: %r{\Aimage/.*\z}
validates :file, presence: true validates :file, presence: true
validates :var, presence: true, uniqueness: true validates :var, presence: true, uniqueness: true

View file

@ -36,7 +36,7 @@ class Tag < ApplicationRecord
HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)' HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)'
HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}" HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}"
HASHTAG_RE = /(?:^|[^\/\)\w])#(#{HASHTAG_NAME_PAT})/i HASHTAG_RE = %r{(?:^|[^/)\w])#(#{HASHTAG_NAME_PAT})}i
HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i
HASHTAG_INVALID_CHARS_RE = /[^[:alnum:]#{HASHTAG_SEPARATORS}]/ HASHTAG_INVALID_CHARS_RE = /[^[:alnum:]#{HASHTAG_SEPARATORS}]/

View file

@ -11,6 +11,7 @@
# enabled :boolean default(TRUE), not null # enabled :boolean default(TRUE), not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# template :text
# #
class Webhook < ApplicationRecord class Webhook < ApplicationRecord
@ -30,6 +31,7 @@ class Webhook < ApplicationRecord
validates :events, presence: true validates :events, presence: true
validate :validate_events validate :validate_events
validate :validate_template
before_validation :strip_events before_validation :strip_events
before_validation :generate_secret before_validation :generate_secret
@ -49,7 +51,18 @@ class Webhook < ApplicationRecord
private private
def validate_events def validate_events
errors.add(:events, :invalid) if events.any? { |e| !EVENTS.include?(e) } errors.add(:events, :invalid) if events.any? { |e| EVENTS.exclude?(e) }
end
def validate_template
return if template.blank?
begin
parser = Webhooks::PayloadRenderer::TemplateParser.new
parser.parse(template)
rescue Parslet::ParseFailed
errors.add(:template, :invalid)
end
end end
def strip_events def strip_events

View file

@ -77,8 +77,8 @@ class BackupService < BaseService
path = m.file&.path path = m.file&.path
next unless path next unless path
path = path.gsub(/\A.*\/system\//, '') path = path.gsub(%r{\A.*/system/}, '')
path = path.gsub(/\A\/+/, '') path = path.gsub(%r{\A/+}, '')
download_to_zip(zipfile, m.file, path) download_to_zip(zipfile, m.file, path)
end end

View file

@ -7,7 +7,7 @@ class FetchLinkCardService < BaseService
URL_PATTERN = %r{ URL_PATTERN = %r{
(#{Twitter::TwitterText::Regex[:valid_url_preceding_chars]}) # $1 preceding chars (#{Twitter::TwitterText::Regex[:valid_url_preceding_chars]}) # $1 preceding chars
( # $2 URL ( # $2 URL
(https?:\/\/) # $3 Protocol (required) (https?://) # $3 Protocol (required)
(#{Twitter::TwitterText::Regex[:valid_domain]}) # $4 Domain(s) (#{Twitter::TwitterText::Regex[:valid_domain]}) # $4 Domain(s)
(?::(#{Twitter::TwitterText::Regex[:valid_port_number]}))? # $5 Port number (optional) (?::(#{Twitter::TwitterText::Regex[:valid_port_number]}))? # $5 Port number (optional)
(/#{Twitter::TwitterText::Regex[:valid_url_path]}*)? # $6 URL Path and anchor (/#{Twitter::TwitterText::Regex[:valid_url_path]}*)? # $6 URL Path and anchor

View file

@ -2,7 +2,7 @@
class FetchOEmbedService class FetchOEmbedService
ENDPOINT_CACHE_EXPIRES_IN = 24.hours.freeze ENDPOINT_CACHE_EXPIRES_IN = 24.hours.freeze
URL_REGEX = /(=(http[s]?(%3A|:)(\/\/|%2F%2F)))([^&]*)/i URL_REGEX = %r{(=(https?(%3A|:)(//|%2F%2F)))([^&]*)}i
attr_reader :url, :options, :format, :endpoint_url attr_reader :url, :options, :format, :endpoint_url

View file

@ -92,7 +92,7 @@ class SearchService < BaseService
end end
def url_query? def url_query?
@resolve && /\Ahttps?:\/\//.match?(@query) @resolve && %r{\Ahttps?://}.match?(@query)
end end
def url_resource_results def url_resource_results

View file

@ -7,5 +7,8 @@
.fields-group .fields-group
= f.input :events, collection: Webhook::EVENTS, wrapper: :with_block_label, include_blank: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' = f.input :events, collection: Webhook::EVENTS, wrapper: :with_block_label, include_blank: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
.fields-group
= f.input :template, wrapper: :with_block_label, input_html: { placeholder: '{ "content": "Hello {{object.username}}" }' }
.actions .actions
= f.button :button, @webhook.new_record? ? t('admin.webhooks.add_new') : t('generic.save_changes'), type: :submit = f.button :button, @webhook.new_record? ? t('admin.webhooks.add_new') : t('generic.save_changes'), type: :submit

View file

@ -2,14 +2,14 @@
= t('admin.webhooks.title') = t('admin.webhooks.title')
- content_for :heading do - content_for :heading do
%h2 .content__heading__row
%small %h2
= fa_icon 'inbox' %small
= t('admin.webhooks.webhook') = fa_icon 'inbox'
= @webhook.url = t('admin.webhooks.webhook')
= @webhook.url
- content_for :heading_actions do .content__heading__actions
= link_to t('admin.webhooks.edit'), edit_admin_webhook_path, class: 'button' if can?(:update, @webhook) = link_to t('admin.webhooks.edit'), edit_admin_webhook_path, class: 'button' if can?(:update, @webhook)
.table-wrapper .table-wrapper
%table.table.horizontal-table %table.table.horizontal-table

View file

@ -8,7 +8,7 @@ class Webhooks::DeliveryWorker
def perform(webhook_id, body) def perform(webhook_id, body)
@webhook = Webhook.find(webhook_id) @webhook = Webhook.find(webhook_id)
@body = body @body = @webhook.template.blank? ? body : Webhooks::PayloadRenderer.new(body).render(@webhook.template)
@response = nil @response = nil
perform_request perform_request

View file

@ -79,7 +79,7 @@ class Rack::Attack
end end
throttle('throttle_api_media', limit: 30, period: 30.minutes) do |req| throttle('throttle_api_media', limit: 30, period: 30.minutes) do |req|
req.authenticated_user_id if req.post? && req.path.match?(/\A\/api\/v\d+\/media\z/i) req.authenticated_user_id if req.post? && req.path.match?(%r{\A/api/v\d+/media\z}i)
end end
throttle('throttle_media_proxy', limit: 30, period: 10.minutes) do |req| throttle('throttle_media_proxy', limit: 30, period: 10.minutes) do |req|
@ -98,8 +98,8 @@ class Rack::Attack
req.throttleable_remote_ip if req.paging_request? && req.unauthenticated? req.throttleable_remote_ip if req.paging_request? && req.unauthenticated?
end end
API_DELETE_REBLOG_REGEX = /\A\/api\/v1\/statuses\/[\d]+\/unreblog\z/ API_DELETE_REBLOG_REGEX = %r{\A/api/v1/statuses/\d+/unreblog\z}
API_DELETE_STATUS_REGEX = /\A\/api\/v1\/statuses\/[\d]+\z/ API_DELETE_STATUS_REGEX = %r{\A/api/v1/statuses/\d+\z}
throttle('throttle_api_delete', limit: 30, period: 30.minutes) do |req| throttle('throttle_api_delete', limit: 30, period: 30.minutes) do |req|
req.authenticated_user_id if (req.post? && req.path.match?(API_DELETE_REBLOG_REGEX)) || (req.delete? && req.path.match?(API_DELETE_STATUS_REGEX)) req.authenticated_user_id if (req.post? && req.path.match?(API_DELETE_REBLOG_REGEX)) || (req.delete? && req.path.match?(API_DELETE_STATUS_REGEX))

View file

@ -1,3 +1,4 @@
# frozen_string_literal: true # frozen_string_literal: true
StrongMigrations.start_after = 2017_09_24_022025 StrongMigrations.start_after = 2017_09_24_022025
StrongMigrations.target_version = 10

View file

@ -6,8 +6,8 @@ module Twitter::TwitterText
end end
class Regex class Regex
REGEXEN[:valid_general_url_path_chars] = /[^\p{White_Space}<>\(\)\?]/iou REGEXEN[:valid_general_url_path_chars] = /[^\p{White_Space}<>()?]/iou
REGEXEN[:valid_url_path_ending_chars] = /[^\p{White_Space}\(\)\?!\*"'「」<>;:=\,\.\$%\[\]~&\|@]|(?:#{REGEXEN[:valid_url_balanced_parens]})/iou REGEXEN[:valid_url_path_ending_chars] = /[^\p{White_Space}()?!*"'「」<>;:=,.$%\[\]~&|@]|(?:#{REGEXEN[:valid_url_balanced_parens]})/iou
REGEXEN[:valid_url_balanced_parens] = / REGEXEN[:valid_url_balanced_parens] = /
\( \(
(?: (?:
@ -25,20 +25,20 @@ module Twitter::TwitterText
\) \)
/iox /iox
UCHARS = '\u{A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}\u{10000}-\u{1FFFD}\u{20000}-\u{2FFFD}\u{30000}-\u{3FFFD}\u{40000}-\u{4FFFD}\u{50000}-\u{5FFFD}\u{60000}-\u{6FFFD}\u{70000}-\u{7FFFD}\u{80000}-\u{8FFFD}\u{90000}-\u{9FFFD}\u{A0000}-\u{AFFFD}\u{B0000}-\u{BFFFD}\u{C0000}-\u{CFFFD}\u{D0000}-\u{DFFFD}\u{E1000}-\u{EFFFD}\u{E000}-\u{F8FF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}' UCHARS = '\u{A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}\u{10000}-\u{1FFFD}\u{20000}-\u{2FFFD}\u{30000}-\u{3FFFD}\u{40000}-\u{4FFFD}\u{50000}-\u{5FFFD}\u{60000}-\u{6FFFD}\u{70000}-\u{7FFFD}\u{80000}-\u{8FFFD}\u{90000}-\u{9FFFD}\u{A0000}-\u{AFFFD}\u{B0000}-\u{BFFFD}\u{C0000}-\u{CFFFD}\u{D0000}-\u{DFFFD}\u{E1000}-\u{EFFFD}\u{E000}-\u{F8FF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}'
REGEXEN[:valid_url_query_chars] = /[a-z0-9!?\*'\(\);:&=\+\$\/%#\[\]\-_\.,~|@\^#{UCHARS}]/iou REGEXEN[:valid_url_query_chars] = %r{[a-z0-9!?*'();:&=+$/%#\[\]\-_.,~|@\^#{UCHARS}]}iou
REGEXEN[:valid_url_query_ending_chars] = /[a-z0-9_&=#\/\-#{UCHARS}]/iou REGEXEN[:valid_url_query_ending_chars] = %r{[a-z0-9_&=#/\-#{UCHARS}]}iou
REGEXEN[:valid_url_path] = /(?: REGEXEN[:valid_url_path] = %r{(?:
(?: (?:
#{REGEXEN[:valid_general_url_path_chars]}* #{REGEXEN[:valid_general_url_path_chars]}*
(?:#{REGEXEN[:valid_url_balanced_parens]} #{REGEXEN[:valid_general_url_path_chars]}*)* (?:#{REGEXEN[:valid_url_balanced_parens]} #{REGEXEN[:valid_general_url_path_chars]}*)*
#{REGEXEN[:valid_url_path_ending_chars]} #{REGEXEN[:valid_url_path_ending_chars]}
)|(?:#{REGEXEN[:valid_general_url_path_chars]}+\/) )|(?:#{REGEXEN[:valid_general_url_path_chars]}+/)
)/iox )}iox
REGEXEN[:valid_url] = %r{ REGEXEN[:valid_url] = %r{
( # $1 total match ( # $1 total match
(#{REGEXEN[:valid_url_preceding_chars]}) # $2 Preceding character (#{REGEXEN[:valid_url_preceding_chars]}) # $2 Preceding character
( # $3 URL ( # $3 URL
((?:https?|dat|dweb|ipfs|ipns|ssb|gopher|gemini):\/\/)? # $4 Protocol (optional) ((?:https?|dat|dweb|ipfs|ipns|ssb|gopher|gemini)://)? # $4 Protocol (optional)
(#{REGEXEN[:valid_domain]}) # $5 Domain(s) (#{REGEXEN[:valid_domain]}) # $5 Domain(s)
(?::(#{REGEXEN[:valid_port_number]}))? # $6 Port number (optional) (?::(#{REGEXEN[:valid_port_number]}))? # $6 Port number (optional)
(/#{REGEXEN[:valid_url_path]}*)? # $7 URL Path and anchor (/#{REGEXEN[:valid_url_path]}*)? # $7 URL Path and anchor

View file

@ -135,6 +135,7 @@ en:
position: Higher role decides conflict resolution in certain situations. Certain actions can only be performed on roles with a lower priority position: Higher role decides conflict resolution in certain situations. Certain actions can only be performed on roles with a lower priority
webhook: webhook:
events: Select events to send events: Select events to send
template: Compose your own JSON payload using variable interpolation. Leave blank for default JSON.
url: Where events will be sent to url: Where events will be sent to
kmyblue: kmyblue kmyblue: kmyblue
labels: labels:
@ -329,6 +330,7 @@ en:
position: Priority position: Priority
webhook: webhook:
events: Enabled events events: Enabled events
template: Payload template
url: Endpoint URL url: Endpoint URL
'no': 'No' 'no': 'No'
not_recommended: Not recommended not_recommended: Not recommended

View file

@ -117,21 +117,21 @@ Rails.application.routes.draw do
get '/:encoded_at(*path)', to: redirect("/@%{path}"), constraints: { encoded_at: /%40/ } get '/:encoded_at(*path)', to: redirect("/@%{path}"), constraints: { encoded_at: /%40/ }
constraints(username: /[^@\/.]+/) do constraints(username: %r{[^@/.]+}) do
get '/@:username', to: 'accounts#show', as: :short_account get '/@:username', to: 'accounts#show', as: :short_account
get '/@:username/with_replies', to: 'accounts#show', as: :short_account_with_replies get '/@:username/with_replies', to: 'accounts#show', as: :short_account_with_replies
get '/@:username/media', to: 'accounts#show', as: :short_account_media get '/@:username/media', to: 'accounts#show', as: :short_account_media
get '/@:username/tagged/:tag', to: 'accounts#show', as: :short_account_tag get '/@:username/tagged/:tag', to: 'accounts#show', as: :short_account_tag
end end
constraints(account_username: /[^@\/.]+/) do constraints(account_username: %r{[^@/.]+}) do
get '/@:account_username/following', to: 'following_accounts#index' get '/@:account_username/following', to: 'following_accounts#index'
get '/@:account_username/followers', to: 'follower_accounts#index' get '/@:account_username/followers', to: 'follower_accounts#index'
get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status
get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status
end end
get '/@:username_with_domain/(*any)', to: 'home#index', constraints: { username_with_domain: /([^\/])+?/ }, format: false get '/@:username_with_domain/(*any)', to: 'home#index', constraints: { username_with_domain: %r{([^/])+?} }, format: false
get '/settings', to: redirect('/settings/profile') get '/settings', to: redirect('/settings/profile')
draw(:settings) draw(:settings)

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddTemplateToWebhooks < ActiveRecord::Migration[6.1]
def change
add_column :webhooks, :template, :text
end
end

View file

@ -1,7 +1,17 @@
# frozen_string_literal: true # frozen_string_literal: true
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
class AddExclusiveToLists < ActiveRecord::Migration[6.1] class AddExclusiveToLists < ActiveRecord::Migration[6.1]
def change include Mastodon::MigrationHelpers
add_column :lists, :exclusive, :boolean, null: false, default: false
disable_ddl_transaction!
def up
safety_assured { add_column_with_default :lists, :exclusive, :boolean, default: false, allow_null: false }
end
def down
remove_column :lists, :exclusive
end end
end end

View file

@ -1259,6 +1259,7 @@ ActiveRecord::Schema.define(version: 2023_06_05_085710) do
t.boolean "enabled", default: true, null: false t.boolean "enabled", default: true, null: false
t.datetime "created_at", precision: 6, null: false t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false
t.text "template"
t.index ["url"], name: "index_webhooks_on_url", unique: true t.index ["url"], name: "index_webhooks_on_url", unique: true
end end

View file

@ -16,7 +16,7 @@ module PremailerWebpackStrategy
Rails.public_path.join(url).read Rails.public_path.join(url).read
end end
css.gsub(/url\(\//, "url(#{asset_host}/") css.gsub(%r{url\(/}, "url(#{asset_host}/")
end end
module_function :load module_function :load

View file

@ -171,7 +171,7 @@ module Paperclip
end end
def palette_from_histogram(result, quantity) def palette_from_histogram(result, quantity)
frequencies = result.scan(/([0-9]+)\:/).flatten.map(&:to_f) frequencies = result.scan(/([0-9]+):/).flatten.map(&:to_f)
hex_values = result.scan(/\#([0-9A-Fa-f]{6,8})/).flatten hex_values = result.scan(/\#([0-9A-Fa-f]{6,8})/).flatten
total_frequencies = frequencies.sum.to_f total_frequencies = frequencies.sum.to_f

View file

@ -31,7 +31,7 @@ def gen_border(codepoint, color)
end end
def codepoints_to_filename(codepoints) def codepoints_to_filename(codepoints)
codepoints.downcase.gsub(/\A[0]+/, '').tr(' ', '-') codepoints.downcase.gsub(/\A0+/, '').tr(' ', '-')
end end
def codepoints_to_unicode(codepoints) def codepoints_to_unicode(codepoints)

View file

@ -21,7 +21,7 @@ namespace :mastodon do
env['LOCAL_DOMAIN'] = prompt.ask('Domain name:') do |q| env['LOCAL_DOMAIN'] = prompt.ask('Domain name:') do |q|
q.required true q.required true
q.modify :strip q.modify :strip
q.validate(/\A[a-z0-9\.\-]+\z/i) q.validate(/\A[a-z0-9.-]+\z/i)
q.messages[:valid?] = 'Invalid domain. If you intend to use unicode characters, enter punycode here' q.messages[:valid?] = 'Invalid domain. If you intend to use unicode characters, enter punycode here'
end end
@ -240,7 +240,7 @@ namespace :mastodon do
end end
env['S3_PROTOCOL'] = env['S3_ENDPOINT'].start_with?('https') ? 'https' : 'http' env['S3_PROTOCOL'] = env['S3_ENDPOINT'].start_with?('https') ? 'https' : 'http'
env['S3_HOSTNAME'] = env['S3_ENDPOINT'].gsub(/\Ahttps?:\/\//, '') env['S3_HOSTNAME'] = env['S3_ENDPOINT'].gsub(%r{\Ahttps?://}, '')
env['S3_BUCKET'] = prompt.ask('Minio bucket name:') do |q| env['S3_BUCKET'] = prompt.ask('Minio bucket name:') do |q|
q.required true q.required true
@ -269,7 +269,7 @@ namespace :mastodon do
end end
env['S3_PROTOCOL'] = env['S3_ENDPOINT'].start_with?('https') ? 'https' : 'http' env['S3_PROTOCOL'] = env['S3_ENDPOINT'].start_with?('https') ? 'https' : 'http'
env['S3_HOSTNAME'] = env['S3_ENDPOINT'].gsub(/\Ahttps?:\/\//, '') env['S3_HOSTNAME'] = env['S3_ENDPOINT'].gsub(%r{\Ahttps?://}, '')
env['S3_BUCKET'] = prompt.ask('Storj DCS bucket name:') do |q| env['S3_BUCKET'] = prompt.ask('Storj DCS bucket name:') do |q|
q.required true q.required true
@ -573,7 +573,7 @@ def dotenv_escape(value)
# As long as the value doesn't include single quotes, we can safely # As long as the value doesn't include single quotes, we can safely
# rely on single quotes # rely on single quotes
return "'#{value}'" unless /[']/.match?(value) return "'#{value}'" unless value.include?("'")
# If the value contains the string '\n' or '\r' we simply can't use # If the value contains the string '\n' or '\r' we simply can't use
# a double-quoted string, because Dotenv will expand \n or \r no # a double-quoted string, because Dotenv will expand \n or \r no

View file

@ -0,0 +1,6 @@
inherit_from: ../../.rubocop.yml
# Anonymous controllers in specs cannot access described_class
# https://github.com/rubocop/rubocop-rspec/blob/master/lib/rubocop/cop/rspec/described_class.rb#L36-L39
RSpec/DescribedClass:
SkipBlocks: true

View file

@ -99,7 +99,7 @@ RSpec.describe AccountsController do
end end
end end
context do context 'with a normal account in an HTML request' do
before do before do
get :show, params: { username: account.username, format: format } get :show, params: { username: account.username, format: format }
end end
@ -173,7 +173,7 @@ RSpec.describe AccountsController do
end end
end end
context do context 'with a normal account in a JSON request' do
before do before do
get :show, params: { username: account.username, format: format } get :show, params: { username: account.username, format: format }
end end
@ -314,7 +314,7 @@ RSpec.describe AccountsController do
it_behaves_like 'cacheable response' it_behaves_like 'cacheable response'
end end
context do context 'with a normal account in an RSS request' do
before do before do
get :show, params: { username: account.username, format: format } get :show, params: { username: account.username, format: format }
end end

View file

@ -88,7 +88,7 @@ RSpec.describe ActivityPub::CollectionsController do
context 'with signature' do context 'with signature' do
let(:remote_account) { Fabricate(:account, domain: 'example.com') } let(:remote_account) { Fabricate(:account, domain: 'example.com') }
context do context 'when getting a featured resource' do
before do before do
get :show, params: { id: 'featured', account_username: account.username } get :show, params: { id: 'featured', account_username: account.username }
end end

View file

@ -20,4 +20,16 @@ describe Admin::AccountActionsController do
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
end end
end end
describe 'POST #create' do
let(:account) { Fabricate(:account) }
it 'records the account action' do
expect do
post :create, params: { account_id: account.id, admin_account_action: { type: 'silence' } }
end.to change { account.strikes.count }.by(1)
expect(response).to redirect_to(admin_account_path(account.id))
end
end
end end

View file

@ -309,4 +309,128 @@ RSpec.describe Admin::AccountsController do
end end
end end
end end
describe 'POST #unsensitive' do
subject { post :unsensitive, params: { id: account.id } }
let(:current_user) { Fabricate(:user, role: role) }
let(:account) { Fabricate(:account, sensitized_at: 1.year.ago) }
context 'when user is admin' do
let(:role) { UserRole.find_by(name: 'Admin') }
it 'marks accounts not sensitized' do
subject
expect(account.reload).to_not be_sensitized
expect(response).to redirect_to admin_account_path(account.id)
end
end
context 'when user is not admin' do
let(:role) { UserRole.everyone }
it 'fails to change account' do
subject
expect(response).to have_http_status 403
end
end
end
describe 'POST #unsilence' do
subject { post :unsilence, params: { id: account.id } }
let(:current_user) { Fabricate(:user, role: role) }
let(:account) { Fabricate(:account, silenced_at: 1.year.ago) }
context 'when user is admin' do
let(:role) { UserRole.find_by(name: 'Admin') }
it 'marks accounts not silenced' do
subject
expect(account.reload).to_not be_silenced
expect(response).to redirect_to admin_account_path(account.id)
end
end
context 'when user is not admin' do
let(:role) { UserRole.everyone }
it 'fails to change account' do
subject
expect(response).to have_http_status 403
end
end
end
describe 'POST #unsuspend' do
subject { post :unsuspend, params: { id: account.id } }
let(:current_user) { Fabricate(:user, role: role) }
let(:account) { Fabricate(:account) }
before do
account.suspend!
end
context 'when user is admin' do
let(:role) { UserRole.find_by(name: 'Admin') }
it 'marks accounts not suspended' do
subject
expect(account.reload).to_not be_suspended
expect(response).to redirect_to admin_account_path(account.id)
end
end
context 'when user is not admin' do
let(:role) { UserRole.everyone }
it 'fails to change account' do
subject
expect(response).to have_http_status 403
end
end
end
describe 'POST #destroy' do
subject { post :destroy, params: { id: account.id } }
let(:current_user) { Fabricate(:user, role: role) }
let(:account) { Fabricate(:account) }
before do
account.suspend!
end
context 'when user is admin' do
let(:role) { UserRole.find_by(name: 'Admin') }
before do
allow(Admin::AccountDeletionWorker).to receive(:perform_async).with(account.id)
end
it 'destroys the account' do
subject
expect(Admin::AccountDeletionWorker).to have_received(:perform_async).with(account.id)
expect(response).to redirect_to admin_account_path(account.id)
end
end
context 'when user is not admin' do
let(:role) { UserRole.everyone }
it 'fails to change account' do
subject
expect(response).to have_http_status 403
end
end
end
end end

View file

@ -73,4 +73,30 @@ describe Admin::AnnouncementsController do
expect(flash.notice).to match(I18n.t('admin.announcements.destroyed_msg')) expect(flash.notice).to match(I18n.t('admin.announcements.destroyed_msg'))
end end
end end
describe 'POST #publish' do
subject { post :publish, params: { id: announcement.id } }
let(:announcement) { Fabricate(:announcement, published_at: nil) }
it 'marks announcement published' do
subject
expect(announcement.reload).to be_published
expect(response).to redirect_to admin_announcements_path
end
end
describe 'POST #unpublish' do
subject { post :unpublish, params: { id: announcement.id } }
let(:announcement) { Fabricate(:announcement, published_at: 4.days.ago) }
it 'marks announcement as not published' do
subject
expect(announcement.reload).to_not be_published
expect(response).to redirect_to admin_announcements_path
end
end
end end

View file

@ -56,4 +56,45 @@ describe Admin::RelaysController do
end end
end end
end end
describe 'DELETE #destroy' do
let(:relay) { Fabricate(:relay) }
it 'deletes an existing relay' do
delete :destroy, params: { id: relay.id }
expect { relay.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect(response).to redirect_to(admin_relays_path)
end
end
describe 'POST #enable' do
let(:relay) { Fabricate(:relay, state: :idle) }
before do
stub_request(:post, /example.com/).to_return(status: 200)
end
it 'updates a relay from idle to pending' do
post :enable, params: { id: relay.id }
expect(relay.reload).to be_pending
expect(response).to redirect_to(admin_relays_path)
end
end
describe 'POST #disable' do
let(:relay) { Fabricate(:relay, state: :pending) }
before do
stub_request(:post, /example.com/).to_return(status: 200)
end
it 'updates a relay from pending to idle' do
post :disable, params: { id: relay.id }
expect(relay.reload).to be_idle
expect(response).to redirect_to(admin_relays_path)
end
end
end end

View file

@ -20,7 +20,7 @@ describe Admin::StatusesController do
end end
describe 'GET #index' do describe 'GET #index' do
context do context 'with a valid account' do
before do before do
get :index, params: { account_id: account.id } get :index, params: { account_id: account.id }
end end
@ -41,6 +41,16 @@ describe Admin::StatusesController do
end end
end end
describe 'GET #show' do
before do
get :show, params: { account_id: account.id, id: status.id }
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
describe 'POST #batch' do describe 'POST #batch' do
before do before do
post :batch, params: { :account_id => account.id, action => '', :admin_status_batch_action => { status_ids: status_ids } } post :batch, params: { :account_id => account.id, action => '', :admin_status_batch_action => { status_ids: status_ids } }

View file

@ -40,7 +40,7 @@ describe Admin::Users::RolesController do
put :update, params: { user_id: user.id, user: { role_id: selected_role.id } } put :update, params: { user_id: user.id, user: { role_id: selected_role.id } }
end end
context do context 'with manage roles permissions' do
let(:permissions) { UserRole::FLAGS[:manage_roles] } let(:permissions) { UserRole::FLAGS[:manage_roles] }
let(:position) { 1 } let(:position) { 1 }

View file

@ -18,4 +18,68 @@ describe Admin::WarningPresetsController do
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
end end
end end
describe 'GET #edit' do
let(:account_warning_preset) { Fabricate(:account_warning_preset) }
it 'returns http success and renders edit' do
get :edit, params: { id: account_warning_preset.id }
expect(response).to have_http_status(:success)
expect(response).to render_template(:edit)
end
end
describe 'POST #create' do
context 'with valid data' do
it 'creates a new account_warning_preset and redirects' do
expect do
post :create, params: { account_warning_preset: { text: 'The account_warning_preset text.' } }
end.to change(AccountWarningPreset, :count).by(1)
expect(response).to redirect_to(admin_warning_presets_path)
end
end
context 'with invalid data' do
it 'does creates a new account_warning_preset and renders index' do
expect do
post :create, params: { account_warning_preset: { text: '' } }
end.to_not change(AccountWarningPreset, :count)
expect(response).to render_template(:index)
end
end
end
describe 'PUT #update' do
let(:account_warning_preset) { Fabricate(:account_warning_preset, text: 'Original text') }
context 'with valid data' do
it 'updates the account_warning_preset and redirects' do
put :update, params: { id: account_warning_preset.id, account_warning_preset: { text: 'Updated text.' } }
expect(response).to redirect_to(admin_warning_presets_path)
end
end
context 'with invalid data' do
it 'does not update the account_warning_preset and renders index' do
put :update, params: { id: account_warning_preset.id, account_warning_preset: { text: '' } }
expect(response).to render_template(:edit)
end
end
end
describe 'DELETE #destroy' do
let!(:account_warning_preset) { Fabricate(:account_warning_preset) }
it 'destroys the account_warning_preset and redirects' do
delete :destroy, params: { id: account_warning_preset.id }
expect { account_warning_preset.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect(response).to redirect_to(admin_warning_presets_path)
end
end
end end

View file

@ -73,7 +73,7 @@ RSpec.describe Api::V1::AccountsController do
let(:scopes) { 'write:follows' } let(:scopes) { 'write:follows' }
let(:other_account) { Fabricate(:account, username: 'bob', locked: locked) } let(:other_account) { Fabricate(:account, username: 'bob', locked: locked) }
context do context 'when posting to an other account' do
before do before do
post :follow, params: { id: other_account.id } post :follow, params: { id: other_account.id }
end end

View file

@ -32,7 +32,7 @@ RSpec.describe Api::V1::Admin::AccountActionsController do
end end
describe 'POST #create' do describe 'POST #create' do
context do context 'with type of disable' do
before do before do
post :create, params: { account_id: account.id, type: 'disable' } post :create, params: { account_id: account.id, type: 'disable' }
end end

View file

@ -96,7 +96,7 @@ RSpec.describe Api::V1::Admin::DomainAllowsController do
describe 'POST #create' do describe 'POST #create' do
let!(:domain_allow) { Fabricate(:domain_allow, domain: 'example.com') } let!(:domain_allow) { Fabricate(:domain_allow, domain: 'example.com') }
context do context 'with a valid domain' do
before do before do
post :create, params: { domain: 'foo.bar.com' } post :create, params: { domain: 'foo.bar.com' }
end end

View file

@ -120,7 +120,7 @@ RSpec.describe Api::V1::StatusesController do
describe 'POST #create' do describe 'POST #create' do
let(:scopes) { 'write:statuses' } let(:scopes) { 'write:statuses' }
context do context 'with a basic status body' do
before do before do
post :create, params: { status: 'Hello world' } post :create, params: { status: 'Hello world' }
end end

View file

@ -79,7 +79,7 @@ RSpec.describe Auth::RegistrationsController do
request.env['devise.mapping'] = Devise.mappings[:user] request.env['devise.mapping'] = Devise.mappings[:user]
end end
context do context 'with open registrations' do
around do |example| around do |example|
registrations_mode = Setting.registrations_mode registrations_mode = Setting.registrations_mode
example.run example.run
@ -111,7 +111,7 @@ RSpec.describe Auth::RegistrationsController do
end end
end end
context do context 'when an accept language is present in headers' do
subject do subject do
Setting.registrations_mode = 'open' Setting.registrations_mode = 'open'
request.headers['Accept-Language'] = accept_language request.headers['Accept-Language'] = accept_language

View file

@ -32,7 +32,7 @@ describe 'Log in' do
expect(subject).to have_css('.flash-message', text: failure_message('invalid')) expect(subject).to have_css('.flash-message', text: failure_message('invalid'))
end end
context do context 'when confirmed at is nil' do
let(:confirmed_at) { nil } let(:confirmed_at) { nil }
it 'A unconfirmed user is able to log in' do it 'A unconfirmed user is able to log in' do

View file

@ -31,7 +31,7 @@ RSpec.describe ActivityPub::Activity::Undo do
} }
end end
context do context 'when not atomUri' do
before do before do
Fabricate(:status, reblog: status, account: sender, uri: 'bar') Fabricate(:status, reblog: status, account: sender, uri: 'bar')
end end

View file

@ -7,7 +7,7 @@ RSpec.describe EntityCache do
let(:remote_account) { Fabricate(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/') } let(:remote_account) { Fabricate(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/') }
describe '#emoji' do describe '#emoji' do
subject { EntityCache.instance.emoji(shortcodes, domain) } subject { described_class.instance.emoji(shortcodes, domain) }
context 'when called with an empty list of shortcodes' do context 'when called with an empty list of shortcodes' do
let(:shortcodes) { [] } let(:shortcodes) { [] }

View file

@ -6,19 +6,19 @@ describe Extractor do
describe 'extract_mentions_or_lists_with_indices' do describe 'extract_mentions_or_lists_with_indices' do
it 'returns an empty array if the given string does not have at signs' do it 'returns an empty array if the given string does not have at signs' do
text = 'a string without at signs' text = 'a string without at signs'
extracted = Extractor.extract_mentions_or_lists_with_indices(text) extracted = described_class.extract_mentions_or_lists_with_indices(text)
expect(extracted).to eq [] expect(extracted).to eq []
end end
it 'does not extract mentions which ends with particular characters' do it 'does not extract mentions which ends with particular characters' do
text = '@screen_name@' text = '@screen_name@'
extracted = Extractor.extract_mentions_or_lists_with_indices(text) extracted = described_class.extract_mentions_or_lists_with_indices(text)
expect(extracted).to eq [] expect(extracted).to eq []
end end
it 'returns mentions as an array' do it 'returns mentions as an array' do
text = '@screen_name' text = '@screen_name'
extracted = Extractor.extract_mentions_or_lists_with_indices(text) extracted = described_class.extract_mentions_or_lists_with_indices(text)
expect(extracted).to eq [ expect(extracted).to eq [
{ screen_name: 'screen_name', indices: [0, 12] }, { screen_name: 'screen_name', indices: [0, 12] },
] ]
@ -26,7 +26,7 @@ describe Extractor do
it 'yields mentions if a block is given' do it 'yields mentions if a block is given' do
text = '@screen_name' text = '@screen_name'
Extractor.extract_mentions_or_lists_with_indices(text) do |screen_name, start_position, end_position| described_class.extract_mentions_or_lists_with_indices(text) do |screen_name, start_position, end_position|
expect(screen_name).to eq 'screen_name' expect(screen_name).to eq 'screen_name'
expect(start_position).to eq 0 expect(start_position).to eq 0
expect(end_position).to eq 12 expect(end_position).to eq 12
@ -37,31 +37,31 @@ describe Extractor do
describe 'extract_hashtags_with_indices' do describe 'extract_hashtags_with_indices' do
it 'returns an empty array if it does not have #' do it 'returns an empty array if it does not have #' do
text = 'a string without hash sign' text = 'a string without hash sign'
extracted = Extractor.extract_hashtags_with_indices(text) extracted = described_class.extract_hashtags_with_indices(text)
expect(extracted).to eq [] expect(extracted).to eq []
end end
it 'does not exclude normal hash text before ://' do it 'does not exclude normal hash text before ://' do
text = '#hashtag://' text = '#hashtag://'
extracted = Extractor.extract_hashtags_with_indices(text) extracted = described_class.extract_hashtags_with_indices(text)
expect(extracted).to eq [{ hashtag: 'hashtag', indices: [0, 8] }] expect(extracted).to eq [{ hashtag: 'hashtag', indices: [0, 8] }]
end end
it 'excludes http://' do it 'excludes http://' do
text = '#hashtaghttp://' text = '#hashtaghttp://'
extracted = Extractor.extract_hashtags_with_indices(text) extracted = described_class.extract_hashtags_with_indices(text)
expect(extracted).to eq [{ hashtag: 'hashtag', indices: [0, 8] }] expect(extracted).to eq [{ hashtag: 'hashtag', indices: [0, 8] }]
end end
it 'excludes https://' do it 'excludes https://' do
text = '#hashtaghttps://' text = '#hashtaghttps://'
extracted = Extractor.extract_hashtags_with_indices(text) extracted = described_class.extract_hashtags_with_indices(text)
expect(extracted).to eq [{ hashtag: 'hashtag', indices: [0, 8] }] expect(extracted).to eq [{ hashtag: 'hashtag', indices: [0, 8] }]
end end
it 'yields hashtags if a block is given' do it 'yields hashtags if a block is given' do
text = '#hashtag' text = '#hashtag'
Extractor.extract_hashtags_with_indices(text) do |hashtag, start_position, end_position| described_class.extract_hashtags_with_indices(text) do |hashtag, start_position, end_position|
expect(hashtag).to eq 'hashtag' expect(hashtag).to eq 'hashtag'
expect(start_position).to eq 0 expect(start_position).to eq 0
expect(end_position).to eq 8 expect(end_position).to eq 8
@ -72,7 +72,7 @@ describe Extractor do
describe 'extract_cashtags_with_indices' do describe 'extract_cashtags_with_indices' do
it 'returns []' do it 'returns []' do
text = '$cashtag' text = '$cashtag'
extracted = Extractor.extract_cashtags_with_indices(text) extracted = described_class.extract_cashtags_with_indices(text)
expect(extracted).to eq [] expect(extracted).to eq []
end end
end end

View file

@ -15,7 +15,7 @@ RSpec.describe FeedManager do
end end
describe '#key' do describe '#key' do
subject { FeedManager.instance.key(:home, 1) } subject { described_class.instance.key(:home, 1) }
it 'returns a string' do it 'returns a string' do
expect(subject).to be_a String expect(subject).to be_a String
@ -32,26 +32,26 @@ RSpec.describe FeedManager do
it 'returns false for followee\'s status' do it 'returns false for followee\'s status' do
status = Fabricate(:status, text: 'Hello world', account: alice) status = Fabricate(:status, text: 'Hello world', account: alice)
bob.follow!(alice) bob.follow!(alice)
expect(FeedManager.instance.filter?(:home, status, bob)).to be false expect(described_class.instance.filter?(:home, status, bob)).to be false
end end
it 'returns false for reblog by followee' do it 'returns false for reblog by followee' do
status = Fabricate(:status, text: 'Hello world', account: jeff) status = Fabricate(:status, text: 'Hello world', account: jeff)
reblog = Fabricate(:status, reblog: status, account: alice) reblog = Fabricate(:status, reblog: status, account: alice)
bob.follow!(alice) bob.follow!(alice)
expect(FeedManager.instance.filter?(:home, reblog, bob)).to be false expect(described_class.instance.filter?(:home, reblog, bob)).to be false
end end
it 'returns true for post from account who blocked me' do it 'returns true for post from account who blocked me' do
status = Fabricate(:status, text: 'Hello, World', account: alice) status = Fabricate(:status, text: 'Hello, World', account: alice)
alice.block!(bob) alice.block!(bob)
expect(FeedManager.instance.filter?(:home, status, bob)).to be true expect(described_class.instance.filter?(:home, status, bob)).to be true
end end
it 'returns true for post from blocked account' do it 'returns true for post from blocked account' do
status = Fabricate(:status, text: 'Hello, World', account: alice) status = Fabricate(:status, text: 'Hello, World', account: alice)
bob.block!(alice) bob.block!(alice)
expect(FeedManager.instance.filter?(:home, status, bob)).to be true expect(described_class.instance.filter?(:home, status, bob)).to be true
end end
it 'returns true for reblog by followee of blocked account' do it 'returns true for reblog by followee of blocked account' do
@ -59,7 +59,7 @@ RSpec.describe FeedManager do
reblog = Fabricate(:status, reblog: status, account: alice) reblog = Fabricate(:status, reblog: status, account: alice)
bob.follow!(alice) bob.follow!(alice)
bob.block!(jeff) bob.block!(jeff)
expect(FeedManager.instance.filter?(:home, reblog, bob)).to be true expect(described_class.instance.filter?(:home, reblog, bob)).to be true
end end
it 'returns true for reblog by followee of muted account' do it 'returns true for reblog by followee of muted account' do
@ -67,7 +67,7 @@ RSpec.describe FeedManager do
reblog = Fabricate(:status, reblog: status, account: alice) reblog = Fabricate(:status, reblog: status, account: alice)
bob.follow!(alice) bob.follow!(alice)
bob.mute!(jeff) bob.mute!(jeff)
expect(FeedManager.instance.filter?(:home, reblog, bob)).to be true expect(described_class.instance.filter?(:home, reblog, bob)).to be true
end end
it 'returns true for reblog by followee of someone who is blocking recipient' do it 'returns true for reblog by followee of someone who is blocking recipient' do
@ -75,14 +75,14 @@ RSpec.describe FeedManager do
reblog = Fabricate(:status, reblog: status, account: alice) reblog = Fabricate(:status, reblog: status, account: alice)
bob.follow!(alice) bob.follow!(alice)
jeff.block!(bob) jeff.block!(bob)
expect(FeedManager.instance.filter?(:home, reblog, bob)).to be true expect(described_class.instance.filter?(:home, reblog, bob)).to be true
end end
it 'returns true for reblog from account with reblogs disabled' do it 'returns true for reblog from account with reblogs disabled' do
status = Fabricate(:status, text: 'Hello world', account: jeff) status = Fabricate(:status, text: 'Hello world', account: jeff)
reblog = Fabricate(:status, reblog: status, account: alice) reblog = Fabricate(:status, reblog: status, account: alice)
bob.follow!(alice, reblogs: false) bob.follow!(alice, reblogs: false)
expect(FeedManager.instance.filter?(:home, reblog, bob)).to be true expect(described_class.instance.filter?(:home, reblog, bob)).to be true
end end
it 'returns false for reply by followee to another followee' do it 'returns false for reply by followee to another followee' do
@ -90,49 +90,49 @@ RSpec.describe FeedManager do
reply = Fabricate(:status, text: 'Nay', thread: status, account: alice) reply = Fabricate(:status, text: 'Nay', thread: status, account: alice)
bob.follow!(alice) bob.follow!(alice)
bob.follow!(jeff) bob.follow!(jeff)
expect(FeedManager.instance.filter?(:home, reply, bob)).to be false expect(described_class.instance.filter?(:home, reply, bob)).to be false
end end
it 'returns false for reply by followee to recipient' do it 'returns false for reply by followee to recipient' do
status = Fabricate(:status, text: 'Hello world', account: bob) status = Fabricate(:status, text: 'Hello world', account: bob)
reply = Fabricate(:status, text: 'Nay', thread: status, account: alice) reply = Fabricate(:status, text: 'Nay', thread: status, account: alice)
bob.follow!(alice) bob.follow!(alice)
expect(FeedManager.instance.filter?(:home, reply, bob)).to be false expect(described_class.instance.filter?(:home, reply, bob)).to be false
end end
it 'returns false for reply by followee to self' do it 'returns false for reply by followee to self' do
status = Fabricate(:status, text: 'Hello world', account: alice) status = Fabricate(:status, text: 'Hello world', account: alice)
reply = Fabricate(:status, text: 'Nay', thread: status, account: alice) reply = Fabricate(:status, text: 'Nay', thread: status, account: alice)
bob.follow!(alice) bob.follow!(alice)
expect(FeedManager.instance.filter?(:home, reply, bob)).to be false expect(described_class.instance.filter?(:home, reply, bob)).to be false
end end
it 'returns true for reply by followee to non-followed account' do it 'returns true for reply by followee to non-followed account' do
status = Fabricate(:status, text: 'Hello world', account: jeff) status = Fabricate(:status, text: 'Hello world', account: jeff)
reply = Fabricate(:status, text: 'Nay', thread: status, account: alice) reply = Fabricate(:status, text: 'Nay', thread: status, account: alice)
bob.follow!(alice) bob.follow!(alice)
expect(FeedManager.instance.filter?(:home, reply, bob)).to be true expect(described_class.instance.filter?(:home, reply, bob)).to be true
end end
it 'returns true for the second reply by followee to a non-federated status' do it 'returns true for the second reply by followee to a non-federated status' do
reply = Fabricate(:status, text: 'Reply 1', reply: true, account: alice) reply = Fabricate(:status, text: 'Reply 1', reply: true, account: alice)
second_reply = Fabricate(:status, text: 'Reply 2', thread: reply, account: alice) second_reply = Fabricate(:status, text: 'Reply 2', thread: reply, account: alice)
bob.follow!(alice) bob.follow!(alice)
expect(FeedManager.instance.filter?(:home, second_reply, bob)).to be true expect(described_class.instance.filter?(:home, second_reply, bob)).to be true
end end
it 'returns false for status by followee mentioning another account' do it 'returns false for status by followee mentioning another account' do
bob.follow!(alice) bob.follow!(alice)
jeff.follow!(alice) jeff.follow!(alice)
status = PostStatusService.new.call(alice, text: 'Hey @jeff') status = PostStatusService.new.call(alice, text: 'Hey @jeff')
expect(FeedManager.instance.filter?(:home, status, bob)).to be false expect(described_class.instance.filter?(:home, status, bob)).to be false
end end
it 'returns true for status by followee mentioning blocked account' do it 'returns true for status by followee mentioning blocked account' do
bob.block!(jeff) bob.block!(jeff)
bob.follow!(alice) bob.follow!(alice)
status = PostStatusService.new.call(alice, text: 'Hey @jeff') status = PostStatusService.new.call(alice, text: 'Hey @jeff')
expect(FeedManager.instance.filter?(:home, status, bob)).to be true expect(described_class.instance.filter?(:home, status, bob)).to be true
end end
it 'returns true for reblog of a personally blocked domain' do it 'returns true for reblog of a personally blocked domain' do
@ -140,19 +140,19 @@ RSpec.describe FeedManager do
alice.follow!(jeff) alice.follow!(jeff)
status = Fabricate(:status, text: 'Hello world', account: bob) status = Fabricate(:status, text: 'Hello world', account: bob)
reblog = Fabricate(:status, reblog: status, account: jeff) reblog = Fabricate(:status, reblog: status, account: jeff)
expect(FeedManager.instance.filter?(:home, reblog, alice)).to be true expect(described_class.instance.filter?(:home, reblog, alice)).to be true
end end
it 'returns true for German post when follow is set to English only' do it 'returns true for German post when follow is set to English only' do
alice.follow!(bob, languages: %w(en)) alice.follow!(bob, languages: %w(en))
status = Fabricate(:status, text: 'Hallo Welt', account: bob, language: 'de') status = Fabricate(:status, text: 'Hallo Welt', account: bob, language: 'de')
expect(FeedManager.instance.filter?(:home, status, alice)).to be true expect(described_class.instance.filter?(:home, status, alice)).to be true
end end
it 'returns false for German post when follow is set to German' do it 'returns false for German post when follow is set to German' do
alice.follow!(bob, languages: %w(de)) alice.follow!(bob, languages: %w(de))
status = Fabricate(:status, text: 'Hallo Welt', account: bob, language: 'de') status = Fabricate(:status, text: 'Hallo Welt', account: bob, language: 'de')
expect(FeedManager.instance.filter?(:home, status, alice)).to be false expect(described_class.instance.filter?(:home, status, alice)).to be false
end end
it 'returns true for post from followee on exclusive list' do it 'returns true for post from followee on exclusive list' do
@ -161,7 +161,7 @@ RSpec.describe FeedManager do
list.accounts << bob list.accounts << bob
allow(List).to receive(:where).and_return(list) allow(List).to receive(:where).and_return(list)
status = Fabricate(:status, text: 'I post a lot', account: bob) status = Fabricate(:status, text: 'I post a lot', account: bob)
expect(FeedManager.instance.filter?(:home, status, alice)).to be true expect(described_class.instance.filter?(:home, status, alice)).to be true
end end
it 'returns true for reblog from followee on exclusive list' do it 'returns true for reblog from followee on exclusive list' do
@ -171,7 +171,7 @@ RSpec.describe FeedManager do
allow(List).to receive(:where).and_return(list) allow(List).to receive(:where).and_return(list)
status = Fabricate(:status, text: 'I post a lot', account: bob) status = Fabricate(:status, text: 'I post a lot', account: bob)
reblog = Fabricate(:status, reblog: status, account: jeff) reblog = Fabricate(:status, reblog: status, account: jeff)
expect(FeedManager.instance.filter?(:home, reblog, alice)).to be true expect(described_class.instance.filter?(:home, reblog, alice)).to be true
end end
it 'returns false for post from followee on non-exclusive list' do it 'returns false for post from followee on non-exclusive list' do
@ -179,7 +179,7 @@ RSpec.describe FeedManager do
alice.follow!(bob) alice.follow!(bob)
list.accounts << bob list.accounts << bob
status = Fabricate(:status, text: 'I post a lot', account: bob) status = Fabricate(:status, text: 'I post a lot', account: bob)
expect(FeedManager.instance.filter?(:home, status, alice)).to be false expect(described_class.instance.filter?(:home, status, alice)).to be false
end end
it 'returns false for reblog from followee on non-exclusive list' do it 'returns false for reblog from followee on non-exclusive list' do
@ -188,7 +188,7 @@ RSpec.describe FeedManager do
list.accounts << jeff list.accounts << jeff
status = Fabricate(:status, text: 'I post a lot', account: bob) status = Fabricate(:status, text: 'I post a lot', account: bob)
reblog = Fabricate(:status, reblog: status, account: jeff) reblog = Fabricate(:status, reblog: status, account: jeff)
expect(FeedManager.instance.filter?(:home, reblog, alice)).to be false expect(described_class.instance.filter?(:home, reblog, alice)).to be false
end end
end end
@ -196,27 +196,27 @@ RSpec.describe FeedManager do
it 'returns true for status that mentions blocked account' do it 'returns true for status that mentions blocked account' do
bob.block!(jeff) bob.block!(jeff)
status = PostStatusService.new.call(alice, text: 'Hey @jeff') status = PostStatusService.new.call(alice, text: 'Hey @jeff')
expect(FeedManager.instance.filter?(:mentions, status, bob)).to be true expect(described_class.instance.filter?(:mentions, status, bob)).to be true
end end
it 'returns true for status that replies to a blocked account' do it 'returns true for status that replies to a blocked account' do
status = Fabricate(:status, text: 'Hello world', account: jeff) status = Fabricate(:status, text: 'Hello world', account: jeff)
reply = Fabricate(:status, text: 'Nay', thread: status, account: alice) reply = Fabricate(:status, text: 'Nay', thread: status, account: alice)
bob.block!(jeff) bob.block!(jeff)
expect(FeedManager.instance.filter?(:mentions, reply, bob)).to be true expect(described_class.instance.filter?(:mentions, reply, bob)).to be true
end end
it 'returns true for status by silenced account who recipient is not following' do it 'returns true for status by silenced account who recipient is not following' do
status = Fabricate(:status, text: 'Hello world', account: alice) status = Fabricate(:status, text: 'Hello world', account: alice)
alice.silence! alice.silence!
expect(FeedManager.instance.filter?(:mentions, status, bob)).to be true expect(described_class.instance.filter?(:mentions, status, bob)).to be true
end end
it 'returns false for status by followed silenced account' do it 'returns false for status by followed silenced account' do
status = Fabricate(:status, text: 'Hello world', account: alice) status = Fabricate(:status, text: 'Hello world', account: alice)
alice.silence! alice.silence!
bob.follow!(alice) bob.follow!(alice)
expect(FeedManager.instance.filter?(:mentions, status, bob)).to be false expect(described_class.instance.filter?(:mentions, status, bob)).to be false
end end
end end
end end
@ -228,7 +228,7 @@ RSpec.describe FeedManager do
members = Array.new(FeedManager::MAX_ITEMS) { |count| [count, count] } members = Array.new(FeedManager::MAX_ITEMS) { |count| [count, count] }
redis.zadd("feed:home:#{account.id}", members) redis.zadd("feed:home:#{account.id}", members)
FeedManager.instance.push_to_home(account, status) described_class.instance.push_to_home(account, status)
expect(redis.zcard("feed:home:#{account.id}")).to eq FeedManager::MAX_ITEMS expect(redis.zcard("feed:home:#{account.id}")).to eq FeedManager::MAX_ITEMS
end end
@ -239,7 +239,7 @@ RSpec.describe FeedManager do
reblogged = Fabricate(:status) reblogged = Fabricate(:status)
reblog = Fabricate(:status, reblog: reblogged) reblog = Fabricate(:status, reblog: reblogged)
expect(FeedManager.instance.push_to_home(account, reblog)).to be true expect(described_class.instance.push_to_home(account, reblog)).to be true
end end
it 'does not save a new reblog of a recent status' do it 'does not save a new reblog of a recent status' do
@ -247,9 +247,9 @@ RSpec.describe FeedManager do
reblogged = Fabricate(:status) reblogged = Fabricate(:status)
reblog = Fabricate(:status, reblog: reblogged) reblog = Fabricate(:status, reblog: reblogged)
FeedManager.instance.push_to_home(account, reblogged) described_class.instance.push_to_home(account, reblogged)
expect(FeedManager.instance.push_to_home(account, reblog)).to be false expect(described_class.instance.push_to_home(account, reblog)).to be false
end end
it 'saves a new reblog of an old status' do it 'saves a new reblog of an old status' do
@ -257,14 +257,14 @@ RSpec.describe FeedManager do
reblogged = Fabricate(:status) reblogged = Fabricate(:status)
reblog = Fabricate(:status, reblog: reblogged) reblog = Fabricate(:status, reblog: reblogged)
FeedManager.instance.push_to_home(account, reblogged) described_class.instance.push_to_home(account, reblogged)
# Fill the feed with intervening statuses # Fill the feed with intervening statuses
FeedManager::REBLOG_FALLOFF.times do FeedManager::REBLOG_FALLOFF.times do
FeedManager.instance.push_to_home(account, Fabricate(:status)) described_class.instance.push_to_home(account, Fabricate(:status))
end end
expect(FeedManager.instance.push_to_home(account, reblog)).to be true expect(described_class.instance.push_to_home(account, reblog)).to be true
end end
it 'does not save a new reblog of a recently-reblogged status' do it 'does not save a new reblog of a recently-reblogged status' do
@ -273,10 +273,10 @@ RSpec.describe FeedManager do
reblogs = Array.new(2) { Fabricate(:status, reblog: reblogged) } reblogs = Array.new(2) { Fabricate(:status, reblog: reblogged) }
# The first reblog will be accepted # The first reblog will be accepted
FeedManager.instance.push_to_home(account, reblogs.first) described_class.instance.push_to_home(account, reblogs.first)
# The second reblog should be ignored # The second reblog should be ignored
expect(FeedManager.instance.push_to_home(account, reblogs.last)).to be false expect(described_class.instance.push_to_home(account, reblogs.last)).to be false
end end
it 'saves a new reblog of a recently-reblogged status when previous reblog has been deleted' do it 'saves a new reblog of a recently-reblogged status when previous reblog has been deleted' do
@ -285,15 +285,15 @@ RSpec.describe FeedManager do
old_reblog = Fabricate(:status, reblog: reblogged) old_reblog = Fabricate(:status, reblog: reblogged)
# The first reblog should be accepted # The first reblog should be accepted
expect(FeedManager.instance.push_to_home(account, old_reblog)).to be true expect(described_class.instance.push_to_home(account, old_reblog)).to be true
# The first reblog should be successfully removed # The first reblog should be successfully removed
expect(FeedManager.instance.unpush_from_home(account, old_reblog)).to be true expect(described_class.instance.unpush_from_home(account, old_reblog)).to be true
reblog = Fabricate(:status, reblog: reblogged) reblog = Fabricate(:status, reblog: reblogged)
# The second reblog should be accepted # The second reblog should be accepted
expect(FeedManager.instance.push_to_home(account, reblog)).to be true expect(described_class.instance.push_to_home(account, reblog)).to be true
end end
it 'does not save a new reblog of a multiply-reblogged-then-unreblogged status' do it 'does not save a new reblog of a multiply-reblogged-then-unreblogged status' do
@ -302,14 +302,14 @@ RSpec.describe FeedManager do
reblogs = Array.new(3) { Fabricate(:status, reblog: reblogged) } reblogs = Array.new(3) { Fabricate(:status, reblog: reblogged) }
# Accept the reblogs # Accept the reblogs
FeedManager.instance.push_to_home(account, reblogs[0]) described_class.instance.push_to_home(account, reblogs[0])
FeedManager.instance.push_to_home(account, reblogs[1]) described_class.instance.push_to_home(account, reblogs[1])
# Unreblog the first one # Unreblog the first one
FeedManager.instance.unpush_from_home(account, reblogs[0]) described_class.instance.unpush_from_home(account, reblogs[0])
# The last reblog should still be ignored # The last reblog should still be ignored
expect(FeedManager.instance.push_to_home(account, reblogs.last)).to be false expect(described_class.instance.push_to_home(account, reblogs.last)).to be false
end end
it 'saves a new reblog of a long-ago-reblogged status' do it 'saves a new reblog of a long-ago-reblogged status' do
@ -318,15 +318,15 @@ RSpec.describe FeedManager do
reblogs = Array.new(2) { Fabricate(:status, reblog: reblogged) } reblogs = Array.new(2) { Fabricate(:status, reblog: reblogged) }
# The first reblog will be accepted # The first reblog will be accepted
FeedManager.instance.push_to_home(account, reblogs.first) described_class.instance.push_to_home(account, reblogs.first)
# Fill the feed with intervening statuses # Fill the feed with intervening statuses
FeedManager::REBLOG_FALLOFF.times do FeedManager::REBLOG_FALLOFF.times do
FeedManager.instance.push_to_home(account, Fabricate(:status)) described_class.instance.push_to_home(account, Fabricate(:status))
end end
# The second reblog should also be accepted # The second reblog should also be accepted
expect(FeedManager.instance.push_to_home(account, reblogs.last)).to be true expect(described_class.instance.push_to_home(account, reblogs.last)).to be true
end end
end end
@ -334,9 +334,9 @@ RSpec.describe FeedManager do
account = Fabricate(:account) account = Fabricate(:account)
reblog = Fabricate(:status) reblog = Fabricate(:status)
status = Fabricate(:status, reblog: reblog) status = Fabricate(:status, reblog: reblog)
FeedManager.instance.push_to_home(account, status) described_class.instance.push_to_home(account, status)
expect(FeedManager.instance.push_to_home(account, reblog)).to be false expect(described_class.instance.push_to_home(account, reblog)).to be false
end end
end end
@ -359,9 +359,9 @@ RSpec.describe FeedManager do
it "does not push when the given status's reblog is already inserted" do it "does not push when the given status's reblog is already inserted" do
reblog = Fabricate(:status) reblog = Fabricate(:status)
status = Fabricate(:status, reblog: reblog) status = Fabricate(:status, reblog: reblog)
FeedManager.instance.push_to_list(list, status) described_class.instance.push_to_list(list, status)
expect(FeedManager.instance.push_to_list(list, reblog)).to be false expect(described_class.instance.push_to_list(list, reblog)).to be false
end end
context 'when replies policy is set to no replies' do context 'when replies policy is set to no replies' do
@ -371,19 +371,19 @@ RSpec.describe FeedManager do
it 'pushes statuses that are not replies' do it 'pushes statuses that are not replies' do
status = Fabricate(:status, text: 'Hello world', account: bob) status = Fabricate(:status, text: 'Hello world', account: bob)
expect(FeedManager.instance.push_to_list(list, status)).to be true expect(described_class.instance.push_to_list(list, status)).to be true
end end
it 'pushes statuses that are replies to list owner' do it 'pushes statuses that are replies to list owner' do
status = Fabricate(:status, text: 'Hello world', account: owner) status = Fabricate(:status, text: 'Hello world', account: owner)
reply = Fabricate(:status, text: 'Nay', thread: status, account: bob) reply = Fabricate(:status, text: 'Nay', thread: status, account: bob)
expect(FeedManager.instance.push_to_list(list, reply)).to be true expect(described_class.instance.push_to_list(list, reply)).to be true
end end
it 'does not push replies to another member of the list' do it 'does not push replies to another member of the list' do
status = Fabricate(:status, text: 'Hello world', account: alice) status = Fabricate(:status, text: 'Hello world', account: alice)
reply = Fabricate(:status, text: 'Nay', thread: status, account: bob) reply = Fabricate(:status, text: 'Nay', thread: status, account: bob)
expect(FeedManager.instance.push_to_list(list, reply)).to be false expect(described_class.instance.push_to_list(list, reply)).to be false
end end
end end
@ -394,25 +394,25 @@ RSpec.describe FeedManager do
it 'pushes statuses that are not replies' do it 'pushes statuses that are not replies' do
status = Fabricate(:status, text: 'Hello world', account: bob) status = Fabricate(:status, text: 'Hello world', account: bob)
expect(FeedManager.instance.push_to_list(list, status)).to be true expect(described_class.instance.push_to_list(list, status)).to be true
end end
it 'pushes statuses that are replies to list owner' do it 'pushes statuses that are replies to list owner' do
status = Fabricate(:status, text: 'Hello world', account: owner) status = Fabricate(:status, text: 'Hello world', account: owner)
reply = Fabricate(:status, text: 'Nay', thread: status, account: bob) reply = Fabricate(:status, text: 'Nay', thread: status, account: bob)
expect(FeedManager.instance.push_to_list(list, reply)).to be true expect(described_class.instance.push_to_list(list, reply)).to be true
end end
it 'pushes replies to another member of the list' do it 'pushes replies to another member of the list' do
status = Fabricate(:status, text: 'Hello world', account: alice) status = Fabricate(:status, text: 'Hello world', account: alice)
reply = Fabricate(:status, text: 'Nay', thread: status, account: bob) reply = Fabricate(:status, text: 'Nay', thread: status, account: bob)
expect(FeedManager.instance.push_to_list(list, reply)).to be true expect(described_class.instance.push_to_list(list, reply)).to be true
end end
it 'does not push replies to someone not a member of the list' do it 'does not push replies to someone not a member of the list' do
status = Fabricate(:status, text: 'Hello world', account: eve) status = Fabricate(:status, text: 'Hello world', account: eve)
reply = Fabricate(:status, text: 'Nay', thread: status, account: bob) reply = Fabricate(:status, text: 'Nay', thread: status, account: bob)
expect(FeedManager.instance.push_to_list(list, reply)).to be false expect(described_class.instance.push_to_list(list, reply)).to be false
end end
end end
@ -423,25 +423,25 @@ RSpec.describe FeedManager do
it 'pushes statuses that are not replies' do it 'pushes statuses that are not replies' do
status = Fabricate(:status, text: 'Hello world', account: bob) status = Fabricate(:status, text: 'Hello world', account: bob)
expect(FeedManager.instance.push_to_list(list, status)).to be true expect(described_class.instance.push_to_list(list, status)).to be true
end end
it 'pushes statuses that are replies to list owner' do it 'pushes statuses that are replies to list owner' do
status = Fabricate(:status, text: 'Hello world', account: owner) status = Fabricate(:status, text: 'Hello world', account: owner)
reply = Fabricate(:status, text: 'Nay', thread: status, account: bob) reply = Fabricate(:status, text: 'Nay', thread: status, account: bob)
expect(FeedManager.instance.push_to_list(list, reply)).to be true expect(described_class.instance.push_to_list(list, reply)).to be true
end end
it 'pushes replies to another member of the list' do it 'pushes replies to another member of the list' do
status = Fabricate(:status, text: 'Hello world', account: alice) status = Fabricate(:status, text: 'Hello world', account: alice)
reply = Fabricate(:status, text: 'Nay', thread: status, account: bob) reply = Fabricate(:status, text: 'Nay', thread: status, account: bob)
expect(FeedManager.instance.push_to_list(list, reply)).to be true expect(described_class.instance.push_to_list(list, reply)).to be true
end end
it 'pushes replies to someone not a member of the list' do it 'pushes replies to someone not a member of the list' do
status = Fabricate(:status, text: 'Hello world', account: eve) status = Fabricate(:status, text: 'Hello world', account: eve)
reply = Fabricate(:status, text: 'Nay', thread: status, account: bob) reply = Fabricate(:status, text: 'Nay', thread: status, account: bob)
expect(FeedManager.instance.push_to_list(list, reply)).to be true expect(described_class.instance.push_to_list(list, reply)).to be true
end end
end end
end end
@ -451,9 +451,9 @@ RSpec.describe FeedManager do
account = Fabricate(:account, id: 0) account = Fabricate(:account, id: 0)
reblog = Fabricate(:status) reblog = Fabricate(:status)
status = Fabricate(:status, reblog: reblog) status = Fabricate(:status, reblog: reblog)
FeedManager.instance.push_to_home(account, status) described_class.instance.push_to_home(account, status)
FeedManager.instance.merge_into_home(account, reblog.account) described_class.instance.merge_into_home(account, reblog.account)
expect(redis.zscore('feed:home:0', reblog.id)).to be_nil expect(redis.zscore('feed:home:0', reblog.id)).to be_nil
end end
@ -466,14 +466,14 @@ RSpec.describe FeedManager do
reblogged = Fabricate(:status) reblogged = Fabricate(:status)
status = Fabricate(:status, reblog: reblogged) status = Fabricate(:status, reblog: reblogged)
FeedManager.instance.push_to_home(receiver, reblogged) described_class.instance.push_to_home(receiver, reblogged)
FeedManager::REBLOG_FALLOFF.times { FeedManager.instance.push_to_home(receiver, Fabricate(:status)) } FeedManager::REBLOG_FALLOFF.times { described_class.instance.push_to_home(receiver, Fabricate(:status)) }
FeedManager.instance.push_to_home(receiver, status) described_class.instance.push_to_home(receiver, status)
# The reblogging status should show up under normal conditions. # The reblogging status should show up under normal conditions.
expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to include(status.id.to_s) expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to include(status.id.to_s)
FeedManager.instance.unpush_from_home(receiver, status) described_class.instance.unpush_from_home(receiver, status)
# Restore original status # Restore original status
expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to_not include(status.id.to_s) expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to_not include(status.id.to_s)
@ -484,12 +484,12 @@ RSpec.describe FeedManager do
reblogged = Fabricate(:status) reblogged = Fabricate(:status)
status = Fabricate(:status, reblog: reblogged) status = Fabricate(:status, reblog: reblogged)
FeedManager.instance.push_to_home(receiver, status) described_class.instance.push_to_home(receiver, status)
# The reblogging status should show up under normal conditions. # The reblogging status should show up under normal conditions.
expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to eq [status.id.to_s] expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to eq [status.id.to_s]
FeedManager.instance.unpush_from_home(receiver, status) described_class.instance.unpush_from_home(receiver, status)
expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to be_empty expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to be_empty
end end
@ -499,14 +499,14 @@ RSpec.describe FeedManager do
reblogs = Array.new(3) { Fabricate(:status, reblog: reblogged) } reblogs = Array.new(3) { Fabricate(:status, reblog: reblogged) }
reblogs.each do |reblog| reblogs.each do |reblog|
FeedManager.instance.push_to_home(receiver, reblog) described_class.instance.push_to_home(receiver, reblog)
end end
# The reblogging status should show up under normal conditions. # The reblogging status should show up under normal conditions.
expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to eq [reblogs.first.id.to_s] expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to eq [reblogs.first.id.to_s]
reblogs[0...-1].each do |reblog| reblogs[0...-1].each do |reblog|
FeedManager.instance.unpush_from_home(receiver, reblog) described_class.instance.unpush_from_home(receiver, reblog)
end end
expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to eq [reblogs.last.id.to_s] expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to eq [reblogs.last.id.to_s]
@ -515,10 +515,10 @@ RSpec.describe FeedManager do
it 'sends push updates' do it 'sends push updates' do
status = Fabricate(:status) status = Fabricate(:status)
FeedManager.instance.push_to_home(receiver, status) described_class.instance.push_to_home(receiver, status)
allow(redis).to receive_messages(publish: nil) allow(redis).to receive_messages(publish: nil)
FeedManager.instance.unpush_from_home(receiver, status) described_class.instance.unpush_from_home(receiver, status)
deletion = Oj.dump(event: :delete, payload: status.id.to_s) deletion = Oj.dump(event: :delete, payload: status.id.to_s)
expect(redis).to have_received(:publish).with("timeline:#{receiver.id}", deletion) expect(redis).to have_received(:publish).with("timeline:#{receiver.id}", deletion)
@ -544,7 +544,7 @@ RSpec.describe FeedManager do
end end
it 'correctly cleans the home timeline' do it 'correctly cleans the home timeline' do
FeedManager.instance.clear_from_home(account, target_account) described_class.instance.clear_from_home(account, target_account)
expect(redis.zrange("feed:home:#{account.id}", 0, -1)).to eq [status_1.id.to_s, status_7.id.to_s] expect(redis.zrange("feed:home:#{account.id}", 0, -1)).to eq [status_1.id.to_s, status_7.id.to_s]
end end

View file

@ -1248,4 +1248,117 @@ describe Mastodon::CLI::Accounts do
end end
end end
end end
describe '#reset_relationships' do
let(:target_account) { Fabricate(:account) }
let(:arguments) { [target_account.username] }
context 'when no option is given' do
it 'exits with an error message indicating that at least one option is required' do
expect { cli.invoke(:reset_relationships, arguments) }.to output(
a_string_including('Please specify either --follows or --followers, or both')
).to_stdout
.and raise_error(SystemExit)
end
end
context 'when the given username is not found' do
let(:arguments) { ['non_existent_username'] }
it 'exits with an error message indicating that there is no such account' do
expect { cli.invoke(:reset_relationships, arguments, follows: true) }.to output(
a_string_including('No such account')
).to_stdout
.and raise_error(SystemExit)
end
end
context 'when the given username is found' do
let(:total_relationships) { 10 }
let!(:accounts) { Fabricate.times(total_relationships, :account) }
context 'with --follows option' do
let(:options) { { follows: true } }
before do
accounts.each { |account| target_account.follow!(account) }
end
it 'resets all "following" relationships from the target account' do
cli.invoke(:reset_relationships, arguments, options)
expect(target_account.reload.following).to be_empty
end
it 'calls BootstrapTimelineWorker once to rebuild the timeline' do
allow(BootstrapTimelineWorker).to receive(:perform_async)
cli.invoke(:reset_relationships, arguments, options)
expect(BootstrapTimelineWorker).to have_received(:perform_async).with(target_account.id).once
end
it 'displays a successful message' do
expect { cli.invoke(:reset_relationships, arguments, options) }.to output(
a_string_including("Processed #{total_relationships} relationships")
).to_stdout
end
end
context 'with --followers option' do
let(:options) { { followers: true } }
before do
accounts.each { |account| account.follow!(target_account) }
end
it 'resets all "followers" relationships from the target account' do
cli.invoke(:reset_relationships, arguments, options)
expect(target_account.reload.followers).to be_empty
end
it 'displays a successful message' do
expect { cli.invoke(:reset_relationships, arguments, options) }.to output(
a_string_including("Processed #{total_relationships} relationships")
).to_stdout
end
end
context 'with --follows and --followers options' do
let(:options) { { followers: true, follows: true } }
before do
accounts.first(6).each { |account| account.follow!(target_account) }
accounts.last(4).each { |account| target_account.follow!(account) }
end
it 'resets all "followers" relationships from the target account' do
cli.invoke(:reset_relationships, arguments, options)
expect(target_account.reload.followers).to be_empty
end
it 'resets all "following" relationships from the target account' do
cli.invoke(:reset_relationships, arguments, options)
expect(target_account.reload.following).to be_empty
end
it 'calls BootstrapTimelineWorker once to rebuild the timeline' do
allow(BootstrapTimelineWorker).to receive(:perform_async)
cli.invoke(:reset_relationships, arguments, options)
expect(BootstrapTimelineWorker).to have_received(:perform_async).with(target_account.id).once
end
it 'displays a successful message' do
expect { cli.invoke(:reset_relationships, arguments, options) }.to output(
a_string_including("Processed #{total_relationships} relationships")
).to_stdout
end
end
end
end
end end

View file

@ -5,40 +5,40 @@ require 'rails_helper'
describe OStatus::TagManager do describe OStatus::TagManager do
describe '#unique_tag' do describe '#unique_tag' do
it 'returns a unique tag' do it 'returns a unique tag' do
expect(OStatus::TagManager.instance.unique_tag(Time.utc(2000), 12, 'Status')).to eq 'tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status' expect(described_class.instance.unique_tag(Time.utc(2000), 12, 'Status')).to eq 'tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status'
end end
end end
describe '#unique_tag_to_local_id' do describe '#unique_tag_to_local_id' do
it 'returns the ID part' do it 'returns the ID part' do
expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status', 'Status')).to eql '12' expect(described_class.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status', 'Status')).to eql '12'
end end
it 'returns nil if it is not local id' do it 'returns nil if it is not local id' do
expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:remote,2000-01-01:objectId=12:objectType=Status', 'Status')).to be_nil expect(described_class.instance.unique_tag_to_local_id('tag:remote,2000-01-01:objectId=12:objectType=Status', 'Status')).to be_nil
end end
it 'returns nil if it is not expected type' do it 'returns nil if it is not expected type' do
expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Block', 'Status')).to be_nil expect(described_class.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Block', 'Status')).to be_nil
end end
it 'returns nil if it does not have object ID' do it 'returns nil if it does not have object ID' do
expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectType=Status', 'Status')).to be_nil expect(described_class.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectType=Status', 'Status')).to be_nil
end end
end end
describe '#local_id?' do describe '#local_id?' do
it 'returns true for a local ID' do it 'returns true for a local ID' do
expect(OStatus::TagManager.instance.local_id?('tag:cb6e6126.ngrok.io;objectId=12:objectType=Status')).to be true expect(described_class.instance.local_id?('tag:cb6e6126.ngrok.io;objectId=12:objectType=Status')).to be true
end end
it 'returns false for a foreign ID' do it 'returns false for a foreign ID' do
expect(OStatus::TagManager.instance.local_id?('tag:foreign.tld;objectId=12:objectType=Status')).to be false expect(described_class.instance.local_id?('tag:foreign.tld;objectId=12:objectType=Status')).to be false
end end
end end
describe '#uri_for' do describe '#uri_for' do
subject { OStatus::TagManager.instance.uri_for(target) } subject { described_class.instance.uri_for(target) }
context 'with comment object' do context 'with comment object' do
let(:target) { Fabricate(:status, created_at: '2000-01-01T00:00:00Z', reply: true) } let(:target) { Fabricate(:status, created_at: '2000-01-01T00:00:00Z', reply: true) }

View file

@ -4,7 +4,7 @@ require 'rails_helper'
require 'securerandom' require 'securerandom'
describe Request do describe Request do
subject { Request.new(:get, 'http://example.com') } subject { described_class.new(:get, 'http://example.com') }
describe '#headers' do describe '#headers' do
it 'returns user agent' do it 'returns user agent' do

View file

@ -71,10 +71,8 @@ describe StatusReachFinder do
bob.statuses.create!(thread: status, text: 'Hoge') bob.statuses.create!(thread: status, text: 'Hoge')
end end
context do it 'includes the inbox of the replier' do
it 'includes the inbox of the replier' do expect(subject.inboxes).to include 'https://foo.bar/inbox'
expect(subject.inboxes).to include 'https://foo.bar/inbox'
end
end end
context 'when status is not public' do context 'when status is not public' do
@ -90,10 +88,8 @@ describe StatusReachFinder do
let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') } let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') }
let(:parent_status) { Fabricate(:status, account: bob) } let(:parent_status) { Fabricate(:status, account: bob) }
context do it 'includes the inbox of the replied-to account' do
it 'includes the inbox of the replied-to account' do expect(subject.inboxes).to include 'https://foo.bar/inbox'
expect(subject.inboxes).to include 'https://foo.bar/inbox'
end
end end
context 'when status is not public and replied-to account is not mentioned' do context 'when status is not public and replied-to account is not mentioned' do

View file

@ -16,15 +16,15 @@ RSpec.describe TagManager do
end end
it 'returns true for nil' do it 'returns true for nil' do
expect(TagManager.instance.local_domain?(nil)).to be true expect(described_class.instance.local_domain?(nil)).to be true
end end
it 'returns true if the slash-stripped string equals to local domain' do it 'returns true if the slash-stripped string equals to local domain' do
expect(TagManager.instance.local_domain?('DoMaIn.Example.com/')).to be true expect(described_class.instance.local_domain?('DoMaIn.Example.com/')).to be true
end end
it 'returns false for irrelevant string' do it 'returns false for irrelevant string' do
expect(TagManager.instance.local_domain?('DoMaIn.Example.com!')).to be false expect(described_class.instance.local_domain?('DoMaIn.Example.com!')).to be false
end end
end end
@ -41,25 +41,25 @@ RSpec.describe TagManager do
end end
it 'returns true for nil' do it 'returns true for nil' do
expect(TagManager.instance.web_domain?(nil)).to be true expect(described_class.instance.web_domain?(nil)).to be true
end end
it 'returns true if the slash-stripped string equals to web domain' do it 'returns true if the slash-stripped string equals to web domain' do
expect(TagManager.instance.web_domain?('DoMaIn.Example.com/')).to be true expect(described_class.instance.web_domain?('DoMaIn.Example.com/')).to be true
end end
it 'returns false for string with irrelevant characters' do it 'returns false for string with irrelevant characters' do
expect(TagManager.instance.web_domain?('DoMaIn.Example.com!')).to be false expect(described_class.instance.web_domain?('DoMaIn.Example.com!')).to be false
end end
end end
describe '#normalize_domain' do describe '#normalize_domain' do
it 'returns nil if the given parameter is nil' do it 'returns nil if the given parameter is nil' do
expect(TagManager.instance.normalize_domain(nil)).to be_nil expect(described_class.instance.normalize_domain(nil)).to be_nil
end end
it 'returns normalized domain' do it 'returns normalized domain' do
expect(TagManager.instance.normalize_domain('DoMaIn.Example.com/')).to eq 'domain.example.com' expect(described_class.instance.normalize_domain('DoMaIn.Example.com/')).to eq 'domain.example.com'
end end
end end
@ -72,17 +72,17 @@ RSpec.describe TagManager do
it 'returns true if the normalized string with port is local URL' do it 'returns true if the normalized string with port is local URL' do
Rails.configuration.x.web_domain = 'domain.example.com:42' Rails.configuration.x.web_domain = 'domain.example.com:42'
expect(TagManager.instance.local_url?('https://DoMaIn.Example.com:42/')).to be true expect(described_class.instance.local_url?('https://DoMaIn.Example.com:42/')).to be true
end end
it 'returns true if the normalized string without port is local URL' do it 'returns true if the normalized string without port is local URL' do
Rails.configuration.x.web_domain = 'domain.example.com' Rails.configuration.x.web_domain = 'domain.example.com'
expect(TagManager.instance.local_url?('https://DoMaIn.Example.com/')).to be true expect(described_class.instance.local_url?('https://DoMaIn.Example.com/')).to be true
end end
it 'returns false for string with irrelevant characters' do it 'returns false for string with irrelevant characters' do
Rails.configuration.x.web_domain = 'domain.example.com' Rails.configuration.x.web_domain = 'domain.example.com'
expect(TagManager.instance.local_url?('https://domain.example.net/')).to be false expect(described_class.instance.local_url?('https://domain.example.net/')).to be false
end end
end end
end end

View file

@ -17,7 +17,7 @@ describe WebfingerResource do
resource = 'https://example.com/users/alice/other' resource = 'https://example.com/users/alice/other'
expect do expect do
WebfingerResource.new(resource).username described_class.new(resource).username
end.to raise_error(ActiveRecord::RecordNotFound) end.to raise_error(ActiveRecord::RecordNotFound)
end end
@ -32,7 +32,7 @@ describe WebfingerResource do
expect(Rails.application.routes).to receive(:recognize_path).with(resource).and_return(recognized).at_least(:once) expect(Rails.application.routes).to receive(:recognize_path).with(resource).and_return(recognized).at_least(:once)
expect do expect do
WebfingerResource.new(resource).username described_class.new(resource).username
end.to raise_error(ActiveRecord::RecordNotFound) end.to raise_error(ActiveRecord::RecordNotFound)
end end
@ -40,28 +40,28 @@ describe WebfingerResource do
resource = 'website for http://example.com/users/alice/other' resource = 'website for http://example.com/users/alice/other'
expect do expect do
WebfingerResource.new(resource).username described_class.new(resource).username
end.to raise_error(WebfingerResource::InvalidRequest) end.to raise_error(WebfingerResource::InvalidRequest)
end end
it 'finds the username in a valid https route' do it 'finds the username in a valid https route' do
resource = 'https://example.com/users/alice' resource = 'https://example.com/users/alice'
result = WebfingerResource.new(resource).username result = described_class.new(resource).username
expect(result).to eq 'alice' expect(result).to eq 'alice'
end end
it 'finds the username in a mixed case http route' do it 'finds the username in a mixed case http route' do
resource = 'HTTp://exAMPLe.com/users/alice' resource = 'HTTp://exAMPLe.com/users/alice'
result = WebfingerResource.new(resource).username result = described_class.new(resource).username
expect(result).to eq 'alice' expect(result).to eq 'alice'
end end
it 'finds the username in a valid http route' do it 'finds the username in a valid http route' do
resource = 'http://example.com/users/alice' resource = 'http://example.com/users/alice'
result = WebfingerResource.new(resource).username result = described_class.new(resource).username
expect(result).to eq 'alice' expect(result).to eq 'alice'
end end
end end
@ -71,7 +71,7 @@ describe WebfingerResource do
resource = 'user@remote-host.com' resource = 'user@remote-host.com'
expect do expect do
WebfingerResource.new(resource).username described_class.new(resource).username
end.to raise_error(ActiveRecord::RecordNotFound) end.to raise_error(ActiveRecord::RecordNotFound)
end end
@ -79,7 +79,7 @@ describe WebfingerResource do
Rails.configuration.x.local_domain = 'example.com' Rails.configuration.x.local_domain = 'example.com'
resource = 'alice@example.com' resource = 'alice@example.com'
result = WebfingerResource.new(resource).username result = described_class.new(resource).username
expect(result).to eq 'alice' expect(result).to eq 'alice'
end end
@ -87,7 +87,7 @@ describe WebfingerResource do
Rails.configuration.x.web_domain = 'example.com' Rails.configuration.x.web_domain = 'example.com'
resource = 'alice@example.com' resource = 'alice@example.com'
result = WebfingerResource.new(resource).username result = described_class.new(resource).username
expect(result).to eq 'alice' expect(result).to eq 'alice'
end end
end end
@ -97,7 +97,7 @@ describe WebfingerResource do
resource = 'acct:user@remote-host.com' resource = 'acct:user@remote-host.com'
expect do expect do
WebfingerResource.new(resource).username described_class.new(resource).username
end.to raise_error(ActiveRecord::RecordNotFound) end.to raise_error(ActiveRecord::RecordNotFound)
end end
@ -105,7 +105,7 @@ describe WebfingerResource do
resource = 'acct:user@remote-host@remote-hostess.remote.local@remote' resource = 'acct:user@remote-host@remote-hostess.remote.local@remote'
expect do expect do
WebfingerResource.new(resource).username described_class.new(resource).username
end.to raise_error(ActiveRecord::RecordNotFound) end.to raise_error(ActiveRecord::RecordNotFound)
end end
@ -113,7 +113,7 @@ describe WebfingerResource do
Rails.configuration.x.local_domain = 'example.com' Rails.configuration.x.local_domain = 'example.com'
resource = 'acct:alice@example.com' resource = 'acct:alice@example.com'
result = WebfingerResource.new(resource).username result = described_class.new(resource).username
expect(result).to eq 'alice' expect(result).to eq 'alice'
end end
@ -121,7 +121,7 @@ describe WebfingerResource do
Rails.configuration.x.web_domain = 'example.com' Rails.configuration.x.web_domain = 'example.com'
resource = 'acct:alice@example.com' resource = 'acct:alice@example.com'
result = WebfingerResource.new(resource).username result = described_class.new(resource).username
expect(result).to eq 'alice' expect(result).to eq 'alice'
end end
end end
@ -131,7 +131,7 @@ describe WebfingerResource do
resource = 'df/:dfkj' resource = 'df/:dfkj'
expect do expect do
WebfingerResource.new(resource).username described_class.new(resource).username
end.to raise_error(WebfingerResource::InvalidRequest) end.to raise_error(WebfingerResource::InvalidRequest)
end end
end end

View file

@ -0,0 +1,30 @@
# frozen_string_literal: true
require 'rails_helper'
describe Webhooks::PayloadRenderer do
subject(:renderer) { described_class.new(json) }
let(:event) { Webhooks::EventPresenter.new(type, object) }
let(:payload) { ActiveModelSerializers::SerializableResource.new(event, serializer: REST::Admin::WebhookEventSerializer, scope: nil, scope_name: :current_user).as_json }
let(:json) { Oj.dump(payload) }
describe '#render' do
context 'when event is account.approved' do
let(:type) { 'account.approved' }
let(:object) { Fabricate(:account, display_name: 'Foo"') }
it 'renders event-related variables into template' do
expect(renderer.render('foo={{event}}')).to eq 'foo=account.approved'
end
it 'renders event-specific variables into template' do
expect(renderer.render('foo={{object.username}}')).to eq "foo=#{object.username}"
end
it 'escapes values for use in JSON' do
expect(renderer.render('foo={{object.account.display_name}}')).to eq 'foo=Foo\\"'
end
end
end
end

View file

@ -23,7 +23,7 @@ RSpec.describe NotificationMailer do
describe 'mention' do describe 'mention' do
let(:mention) { Mention.create!(account: receiver.account, status: foreign_status) } let(:mention) { Mention.create!(account: receiver.account, status: foreign_status) }
let(:mail) { NotificationMailer.mention(receiver.account, Notification.create!(account: receiver.account, activity: mention)) } let(:mail) { described_class.mention(receiver.account, Notification.create!(account: receiver.account, activity: mention)) }
include_examples 'localized subject', 'notification_mailer.mention.subject', name: 'bob' include_examples 'localized subject', 'notification_mailer.mention.subject', name: 'bob'
@ -40,7 +40,7 @@ RSpec.describe NotificationMailer do
describe 'follow' do describe 'follow' do
let(:follow) { sender.follow!(receiver.account) } let(:follow) { sender.follow!(receiver.account) }
let(:mail) { NotificationMailer.follow(receiver.account, Notification.create!(account: receiver.account, activity: follow)) } let(:mail) { described_class.follow(receiver.account, Notification.create!(account: receiver.account, activity: follow)) }
include_examples 'localized subject', 'notification_mailer.follow.subject', name: 'bob' include_examples 'localized subject', 'notification_mailer.follow.subject', name: 'bob'
@ -56,7 +56,7 @@ RSpec.describe NotificationMailer do
describe 'favourite' do describe 'favourite' do
let(:favourite) { Favourite.create!(account: sender, status: own_status) } let(:favourite) { Favourite.create!(account: sender, status: own_status) }
let(:mail) { NotificationMailer.favourite(own_status.account, Notification.create!(account: receiver.account, activity: favourite)) } let(:mail) { described_class.favourite(own_status.account, Notification.create!(account: receiver.account, activity: favourite)) }
include_examples 'localized subject', 'notification_mailer.favourite.subject', name: 'bob' include_examples 'localized subject', 'notification_mailer.favourite.subject', name: 'bob'
@ -73,7 +73,7 @@ RSpec.describe NotificationMailer do
describe 'reblog' do describe 'reblog' do
let(:reblog) { Status.create!(account: sender, reblog: own_status) } let(:reblog) { Status.create!(account: sender, reblog: own_status) }
let(:mail) { NotificationMailer.reblog(own_status.account, Notification.create!(account: receiver.account, activity: reblog)) } let(:mail) { described_class.reblog(own_status.account, Notification.create!(account: receiver.account, activity: reblog)) }
include_examples 'localized subject', 'notification_mailer.reblog.subject', name: 'bob' include_examples 'localized subject', 'notification_mailer.reblog.subject', name: 'bob'
@ -90,7 +90,7 @@ RSpec.describe NotificationMailer do
describe 'follow_request' do describe 'follow_request' do
let(:follow_request) { Fabricate(:follow_request, account: sender, target_account: receiver.account) } let(:follow_request) { Fabricate(:follow_request, account: sender, target_account: receiver.account) }
let(:mail) { NotificationMailer.follow_request(receiver.account, Notification.create!(account: receiver.account, activity: follow_request)) } let(:mail) { described_class.follow_request(receiver.account, Notification.create!(account: receiver.account, activity: follow_request)) }
include_examples 'localized subject', 'notification_mailer.follow_request.subject', name: 'bob' include_examples 'localized subject', 'notification_mailer.follow_request.subject', name: 'bob'

View file

@ -19,7 +19,7 @@ describe UserMailer do
end end
describe 'confirmation_instructions' do describe 'confirmation_instructions' do
let(:mail) { UserMailer.confirmation_instructions(receiver, 'spec') } let(:mail) { described_class.confirmation_instructions(receiver, 'spec') }
it 'renders confirmation instructions' do it 'renders confirmation instructions' do
receiver.update!(locale: nil) receiver.update!(locale: nil)
@ -34,7 +34,7 @@ describe UserMailer do
end end
describe 'reconfirmation_instructions' do describe 'reconfirmation_instructions' do
let(:mail) { UserMailer.confirmation_instructions(receiver, 'spec') } let(:mail) { described_class.confirmation_instructions(receiver, 'spec') }
it 'renders reconfirmation instructions' do it 'renders reconfirmation instructions' do
receiver.update!(email: 'new-email@example.com', locale: nil) receiver.update!(email: 'new-email@example.com', locale: nil)
@ -48,7 +48,7 @@ describe UserMailer do
end end
describe 'reset_password_instructions' do describe 'reset_password_instructions' do
let(:mail) { UserMailer.reset_password_instructions(receiver, 'spec') } let(:mail) { described_class.reset_password_instructions(receiver, 'spec') }
it 'renders reset password instructions' do it 'renders reset password instructions' do
receiver.update!(locale: nil) receiver.update!(locale: nil)
@ -61,7 +61,7 @@ describe UserMailer do
end end
describe 'password_change' do describe 'password_change' do
let(:mail) { UserMailer.password_change(receiver) } let(:mail) { described_class.password_change(receiver) }
it 'renders password change notification' do it 'renders password change notification' do
receiver.update!(locale: nil) receiver.update!(locale: nil)
@ -73,7 +73,7 @@ describe UserMailer do
end end
describe 'email_changed' do describe 'email_changed' do
let(:mail) { UserMailer.email_changed(receiver) } let(:mail) { described_class.email_changed(receiver) }
it 'renders email change notification' do it 'renders email change notification' do
receiver.update!(locale: nil) receiver.update!(locale: nil)
@ -86,7 +86,7 @@ describe UserMailer do
describe 'warning' do describe 'warning' do
let(:strike) { Fabricate(:account_warning, target_account: receiver.account, text: 'dont worry its just the testsuite', action: 'suspend') } let(:strike) { Fabricate(:account_warning, target_account: receiver.account, text: 'dont worry its just the testsuite', action: 'suspend') }
let(:mail) { UserMailer.warning(receiver, strike) } let(:mail) { described_class.warning(receiver, strike) }
it 'renders warning notification' do it 'renders warning notification' do
receiver.update!(locale: nil) receiver.update!(locale: nil)
@ -97,7 +97,7 @@ describe UserMailer do
describe 'webauthn_credential_deleted' do describe 'webauthn_credential_deleted' do
let(:credential) { Fabricate(:webauthn_credential, user_id: receiver.id) } let(:credential) { Fabricate(:webauthn_credential, user_id: receiver.id) }
let(:mail) { UserMailer.webauthn_credential_deleted(receiver, credential) } let(:mail) { described_class.webauthn_credential_deleted(receiver, credential) }
it 'renders webauthn credential deleted notification' do it 'renders webauthn credential deleted notification' do
receiver.update!(locale: nil) receiver.update!(locale: nil)
@ -112,7 +112,7 @@ describe UserMailer do
let(:ip) { '192.168.0.1' } let(:ip) { '192.168.0.1' }
let(:agent) { 'NCSA_Mosaic/2.0 (Windows 3.1)' } let(:agent) { 'NCSA_Mosaic/2.0 (Windows 3.1)' }
let(:timestamp) { Time.now.utc } let(:timestamp) { Time.now.utc }
let(:mail) { UserMailer.suspicious_sign_in(receiver, ip, agent, timestamp) } let(:mail) { described_class.suspicious_sign_in(receiver, ip, agent, timestamp) }
it 'renders suspicious sign in notification' do it 'renders suspicious sign in notification' do
receiver.update!(locale: nil) receiver.update!(locale: nil)
@ -125,7 +125,7 @@ describe UserMailer do
describe 'appeal_approved' do describe 'appeal_approved' do
let(:appeal) { Fabricate(:appeal, account: receiver.account, approved_at: Time.now.utc) } let(:appeal) { Fabricate(:appeal, account: receiver.account, approved_at: Time.now.utc) }
let(:mail) { UserMailer.appeal_approved(receiver, appeal) } let(:mail) { described_class.appeal_approved(receiver, appeal) }
it 'renders appeal_approved notification' do it 'renders appeal_approved notification' do
expect(mail.subject).to eq I18n.t('user_mailer.appeal_approved.subject', date: I18n.l(appeal.created_at)) expect(mail.subject).to eq I18n.t('user_mailer.appeal_approved.subject', date: I18n.l(appeal.created_at))
@ -135,7 +135,7 @@ describe UserMailer do
describe 'appeal_rejected' do describe 'appeal_rejected' do
let(:appeal) { Fabricate(:appeal, account: receiver.account, rejected_at: Time.now.utc) } let(:appeal) { Fabricate(:appeal, account: receiver.account, rejected_at: Time.now.utc) }
let(:mail) { UserMailer.appeal_rejected(receiver, appeal) } let(:mail) { described_class.appeal_rejected(receiver, appeal) }
it 'renders appeal_rejected notification' do it 'renders appeal_rejected notification' do
expect(mail.subject).to eq I18n.t('user_mailer.appeal_rejected.subject', date: I18n.l(appeal.created_at)) expect(mail.subject).to eq I18n.t('user_mailer.appeal_rejected.subject', date: I18n.l(appeal.created_at))

View file

@ -12,7 +12,7 @@ RSpec.describe AccountConversation do
status = Fabricate(:status, account: alice, visibility: :direct) status = Fabricate(:status, account: alice, visibility: :direct)
status.mentions.create(account: bob) status.mentions.create(account: bob)
conversation = AccountConversation.add_status(alice, status) conversation = described_class.add_status(alice, status)
expect(conversation.participant_accounts).to include(bob) expect(conversation.participant_accounts).to include(bob)
expect(conversation.last_status).to eq status expect(conversation.last_status).to eq status
@ -21,12 +21,12 @@ RSpec.describe AccountConversation do
it 'appends to old record when there is a match' do it 'appends to old record when there is a match' do
last_status = Fabricate(:status, account: alice, visibility: :direct) last_status = Fabricate(:status, account: alice, visibility: :direct)
conversation = AccountConversation.create!(account: alice, conversation: last_status.conversation, participant_account_ids: [bob.id], status_ids: [last_status.id]) conversation = described_class.create!(account: alice, conversation: last_status.conversation, participant_account_ids: [bob.id], status_ids: [last_status.id])
status = Fabricate(:status, account: bob, visibility: :direct, thread: last_status) status = Fabricate(:status, account: bob, visibility: :direct, thread: last_status)
status.mentions.create(account: alice) status.mentions.create(account: alice)
new_conversation = AccountConversation.add_status(alice, status) new_conversation = described_class.add_status(alice, status)
expect(new_conversation.id).to eq conversation.id expect(new_conversation.id).to eq conversation.id
expect(new_conversation.participant_accounts).to include(bob) expect(new_conversation.participant_accounts).to include(bob)
@ -36,13 +36,13 @@ RSpec.describe AccountConversation do
it 'creates new record when new participants are added' do it 'creates new record when new participants are added' do
last_status = Fabricate(:status, account: alice, visibility: :direct) last_status = Fabricate(:status, account: alice, visibility: :direct)
conversation = AccountConversation.create!(account: alice, conversation: last_status.conversation, participant_account_ids: [bob.id], status_ids: [last_status.id]) conversation = described_class.create!(account: alice, conversation: last_status.conversation, participant_account_ids: [bob.id], status_ids: [last_status.id])
status = Fabricate(:status, account: bob, visibility: :direct, thread: last_status) status = Fabricate(:status, account: bob, visibility: :direct, thread: last_status)
status.mentions.create(account: alice) status.mentions.create(account: alice)
status.mentions.create(account: mark) status.mentions.create(account: mark)
new_conversation = AccountConversation.add_status(alice, status) new_conversation = described_class.add_status(alice, status)
expect(new_conversation.id).to_not eq conversation.id expect(new_conversation.id).to_not eq conversation.id
expect(new_conversation.participant_accounts).to include(bob, mark) expect(new_conversation.participant_accounts).to include(bob, mark)
@ -55,7 +55,7 @@ RSpec.describe AccountConversation do
it 'updates last status to a previous value' do it 'updates last status to a previous value' do
last_status = Fabricate(:status, account: alice, visibility: :direct) last_status = Fabricate(:status, account: alice, visibility: :direct)
status = Fabricate(:status, account: alice, visibility: :direct) status = Fabricate(:status, account: alice, visibility: :direct)
conversation = AccountConversation.create!(account: alice, conversation: last_status.conversation, participant_account_ids: [bob.id], status_ids: [status.id, last_status.id]) conversation = described_class.create!(account: alice, conversation: last_status.conversation, participant_account_ids: [bob.id], status_ids: [status.id, last_status.id])
last_status.mentions.create(account: bob) last_status.mentions.create(account: bob)
last_status.destroy! last_status.destroy!
conversation.reload conversation.reload
@ -65,10 +65,10 @@ RSpec.describe AccountConversation do
it 'removes the record if no other statuses are referenced' do it 'removes the record if no other statuses are referenced' do
last_status = Fabricate(:status, account: alice, visibility: :direct) last_status = Fabricate(:status, account: alice, visibility: :direct)
conversation = AccountConversation.create!(account: alice, conversation: last_status.conversation, participant_account_ids: [bob.id], status_ids: [last_status.id]) conversation = described_class.create!(account: alice, conversation: last_status.conversation, participant_account_ids: [bob.id], status_ids: [last_status.id])
last_status.mentions.create(account: bob) last_status.mentions.create(account: bob)
last_status.destroy! last_status.destroy!
expect(AccountConversation.where(id: conversation.id).count).to eq 0 expect(described_class.where(id: conversation.id).count).to eq 0
end end
end end
end end

View file

@ -7,14 +7,14 @@ RSpec.describe AccountDomainBlock do
account = Fabricate(:account) account = Fabricate(:account)
Rails.cache.write("exclude_domains_for:#{account.id}", 'a.domain.already.blocked') Rails.cache.write("exclude_domains_for:#{account.id}", 'a.domain.already.blocked')
AccountDomainBlock.create!(account: account, domain: 'a.domain.blocked.later') described_class.create!(account: account, domain: 'a.domain.blocked.later')
expect(Rails.cache.exist?("exclude_domains_for:#{account.id}")).to be false expect(Rails.cache.exist?("exclude_domains_for:#{account.id}")).to be false
end end
it 'removes blocking cache after destruction' do it 'removes blocking cache after destruction' do
account = Fabricate(:account) account = Fabricate(:account)
block = AccountDomainBlock.create!(account: account, domain: 'domain') block = described_class.create!(account: account, domain: 'domain')
Rails.cache.write("exclude_domains_for:#{account.id}", 'domain') Rails.cache.write("exclude_domains_for:#{account.id}", 'domain')
block.destroy! block.destroy!

View file

@ -7,7 +7,7 @@ RSpec.describe AccountMigration do
let(:source_account) { Fabricate(:account) } let(:source_account) { Fabricate(:account) }
let(:target_acct) { target_account.acct } let(:target_acct) { target_account.acct }
let(:subject) { AccountMigration.new(account: source_account, acct: target_acct) } let(:subject) { described_class.new(account: source_account, acct: target_acct) }
context 'with valid properties' do context 'with valid properties' do
let(:target_account) { Fabricate(:account, username: 'target', domain: 'remote.org') } let(:target_account) { Fabricate(:account, username: 'target', domain: 'remote.org') }

View file

@ -3,7 +3,7 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe Account do RSpec.describe Account do
context do context 'with an account record' do
subject { Fabricate(:account) } subject { Fabricate(:account) }
let(:bob) { Fabricate(:account, username: 'bob') } let(:bob) { Fabricate(:account, username: 'bob') }
@ -362,7 +362,7 @@ RSpec.describe Account do
suspended: true suspended: true
) )
results = Account.search_for('username') results = described_class.search_for('username')
expect(results).to eq [] expect(results).to eq []
end end
@ -375,7 +375,7 @@ RSpec.describe Account do
match.user.update(approved: false) match.user.update(approved: false)
results = Account.search_for('username') results = described_class.search_for('username')
expect(results).to eq [] expect(results).to eq []
end end
@ -388,7 +388,7 @@ RSpec.describe Account do
match.user.update(confirmed_at: nil) match.user.update(confirmed_at: nil)
results = Account.search_for('username') results = described_class.search_for('username')
expect(results).to eq [] expect(results).to eq []
end end
@ -400,7 +400,7 @@ RSpec.describe Account do
domain: 'example.com' domain: 'example.com'
) )
results = Account.search_for('A?l\i:c e') results = described_class.search_for('A?l\i:c e')
expect(results).to eq [match] expect(results).to eq [match]
end end
@ -412,7 +412,7 @@ RSpec.describe Account do
domain: 'example.com' domain: 'example.com'
) )
results = Account.search_for('display') results = described_class.search_for('display')
expect(results).to eq [match] expect(results).to eq [match]
end end
@ -424,7 +424,7 @@ RSpec.describe Account do
domain: 'example.com' domain: 'example.com'
) )
results = Account.search_for('username') results = described_class.search_for('username')
expect(results).to eq [match] expect(results).to eq [match]
end end
@ -436,19 +436,19 @@ RSpec.describe Account do
domain: 'example.com' domain: 'example.com'
) )
results = Account.search_for('example') results = described_class.search_for('example')
expect(results).to eq [match] expect(results).to eq [match]
end end
it 'limits by 10 by default' do it 'limits by 10 by default' do
11.times.each { Fabricate(:account, display_name: 'Display Name') } 11.times.each { Fabricate(:account, display_name: 'Display Name') }
results = Account.search_for('display') results = described_class.search_for('display')
expect(results.size).to eq 10 expect(results.size).to eq 10
end end
it 'accepts arbitrary limits' do it 'accepts arbitrary limits' do
2.times.each { Fabricate(:account, display_name: 'Display Name') } 2.times.each { Fabricate(:account, display_name: 'Display Name') }
results = Account.search_for('display', limit: 1) results = described_class.search_for('display', limit: 1)
expect(results.size).to eq 1 expect(results.size).to eq 1
end end
@ -458,7 +458,7 @@ RSpec.describe Account do
{ display_name: 'Display Name', username: 'username', domain: 'example.com' }, { display_name: 'Display Name', username: 'username', domain: 'example.com' },
].map(&method(:Fabricate).curry(2).call(:account)) ].map(&method(:Fabricate).curry(2).call(:account))
results = Account.search_for('username') results = described_class.search_for('username')
expect(results).to eq matches expect(results).to eq matches
end end
end end
@ -476,7 +476,7 @@ RSpec.describe Account do
) )
account.follow!(match) account.follow!(match)
results = Account.advanced_search_for('A?l\i:c e', account, limit: 10, following: true) results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
expect(results).to eq [match] expect(results).to eq [match]
end end
@ -488,7 +488,7 @@ RSpec.describe Account do
domain: 'example.com' domain: 'example.com'
) )
results = Account.advanced_search_for('A?l\i:c e', account, limit: 10, following: true) results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
expect(results).to eq [] expect(results).to eq []
end end
@ -501,7 +501,7 @@ RSpec.describe Account do
suspended: true suspended: true
) )
results = Account.advanced_search_for('username', account, limit: 10, following: true) results = described_class.advanced_search_for('username', account, limit: 10, following: true)
expect(results).to eq [] expect(results).to eq []
end end
@ -514,7 +514,7 @@ RSpec.describe Account do
match.user.update(approved: false) match.user.update(approved: false)
results = Account.advanced_search_for('username', account, limit: 10, following: true) results = described_class.advanced_search_for('username', account, limit: 10, following: true)
expect(results).to eq [] expect(results).to eq []
end end
@ -527,7 +527,7 @@ RSpec.describe Account do
match.user.update(confirmed_at: nil) match.user.update(confirmed_at: nil)
results = Account.advanced_search_for('username', account, limit: 10, following: true) results = described_class.advanced_search_for('username', account, limit: 10, following: true)
expect(results).to eq [] expect(results).to eq []
end end
end end
@ -541,7 +541,7 @@ RSpec.describe Account do
suspended: true suspended: true
) )
results = Account.advanced_search_for('username', account) results = described_class.advanced_search_for('username', account)
expect(results).to eq [] expect(results).to eq []
end end
@ -554,7 +554,7 @@ RSpec.describe Account do
match.user.update(approved: false) match.user.update(approved: false)
results = Account.advanced_search_for('username', account) results = described_class.advanced_search_for('username', account)
expect(results).to eq [] expect(results).to eq []
end end
@ -567,7 +567,7 @@ RSpec.describe Account do
match.user.update(confirmed_at: nil) match.user.update(confirmed_at: nil)
results = Account.advanced_search_for('username', account) results = described_class.advanced_search_for('username', account)
expect(results).to eq [] expect(results).to eq []
end end
@ -579,19 +579,19 @@ RSpec.describe Account do
domain: 'example.com' domain: 'example.com'
) )
results = Account.advanced_search_for('A?l\i:c e', account) results = described_class.advanced_search_for('A?l\i:c e', account)
expect(results).to eq [match] expect(results).to eq [match]
end end
it 'limits by 10 by default' do it 'limits by 10 by default' do
11.times { Fabricate(:account, display_name: 'Display Name') } 11.times { Fabricate(:account, display_name: 'Display Name') }
results = Account.advanced_search_for('display', account) results = described_class.advanced_search_for('display', account)
expect(results.size).to eq 10 expect(results.size).to eq 10
end end
it 'accepts arbitrary limits' do it 'accepts arbitrary limits' do
2.times { Fabricate(:account, display_name: 'Display Name') } 2.times { Fabricate(:account, display_name: 'Display Name') }
results = Account.advanced_search_for('display', account, limit: 1) results = described_class.advanced_search_for('display', account, limit: 1)
expect(results.size).to eq 1 expect(results.size).to eq 1
end end
@ -600,7 +600,7 @@ RSpec.describe Account do
followed_match = Fabricate(:account, username: 'Matcher') followed_match = Fabricate(:account, username: 'Matcher')
Fabricate(:follow, account: account, target_account: followed_match) Fabricate(:follow, account: account, target_account: followed_match)
results = Account.advanced_search_for('match', account) results = described_class.advanced_search_for('match', account)
expect(results).to eq [followed_match, match] expect(results).to eq [followed_match, match]
expect(results.first.rank).to be > results.last.rank expect(results.first.rank).to be > results.last.rank
end end
@ -639,31 +639,31 @@ RSpec.describe Account do
describe '.following_map' do describe '.following_map' do
it 'returns an hash' do it 'returns an hash' do
expect(Account.following_map([], 1)).to be_a Hash expect(described_class.following_map([], 1)).to be_a Hash
end end
end end
describe '.followed_by_map' do describe '.followed_by_map' do
it 'returns an hash' do it 'returns an hash' do
expect(Account.followed_by_map([], 1)).to be_a Hash expect(described_class.followed_by_map([], 1)).to be_a Hash
end end
end end
describe '.blocking_map' do describe '.blocking_map' do
it 'returns an hash' do it 'returns an hash' do
expect(Account.blocking_map([], 1)).to be_a Hash expect(described_class.blocking_map([], 1)).to be_a Hash
end end
end end
describe '.requested_map' do describe '.requested_map' do
it 'returns an hash' do it 'returns an hash' do
expect(Account.requested_map([], 1)).to be_a Hash expect(described_class.requested_map([], 1)).to be_a Hash
end end
end end
describe '.requested_by_map' do describe '.requested_by_map' do
it 'returns an hash' do it 'returns an hash' do
expect(Account.requested_by_map([], 1)).to be_a Hash expect(described_class.requested_by_map([], 1)).to be_a Hash
end end
end end
@ -834,7 +834,7 @@ RSpec.describe Account do
{ username: 'b', domain: 'b' }, { username: 'b', domain: 'b' },
].map(&method(:Fabricate).curry(2).call(:account)) ].map(&method(:Fabricate).curry(2).call(:account))
expect(Account.where('id > 0').alphabetic).to eq matches expect(described_class.where('id > 0').alphabetic).to eq matches
end end
end end
@ -843,7 +843,7 @@ RSpec.describe Account do
match = Fabricate(:account, display_name: 'pattern and suffix') match = Fabricate(:account, display_name: 'pattern and suffix')
Fabricate(:account, display_name: 'prefix and pattern') Fabricate(:account, display_name: 'prefix and pattern')
expect(Account.matches_display_name('pattern')).to eq [match] expect(described_class.matches_display_name('pattern')).to eq [match]
end end
end end
@ -852,24 +852,24 @@ RSpec.describe Account do
match = Fabricate(:account, username: 'pattern_and_suffix') match = Fabricate(:account, username: 'pattern_and_suffix')
Fabricate(:account, username: 'prefix_and_pattern') Fabricate(:account, username: 'prefix_and_pattern')
expect(Account.matches_username('pattern')).to eq [match] expect(described_class.matches_username('pattern')).to eq [match]
end end
end end
describe 'by_domain_and_subdomains' do describe 'by_domain_and_subdomains' do
it 'returns exact domain matches' do it 'returns exact domain matches' do
account = Fabricate(:account, domain: 'example.com') account = Fabricate(:account, domain: 'example.com')
expect(Account.by_domain_and_subdomains('example.com')).to eq [account] expect(described_class.by_domain_and_subdomains('example.com')).to eq [account]
end end
it 'returns subdomains' do it 'returns subdomains' do
account = Fabricate(:account, domain: 'foo.example.com') account = Fabricate(:account, domain: 'foo.example.com')
expect(Account.by_domain_and_subdomains('example.com')).to eq [account] expect(described_class.by_domain_and_subdomains('example.com')).to eq [account]
end end
it 'does not return partially matching domains' do it 'does not return partially matching domains' do
account = Fabricate(:account, domain: 'grexample.com') account = Fabricate(:account, domain: 'grexample.com')
expect(Account.by_domain_and_subdomains('example.com')).to_not eq [account] expect(described_class.by_domain_and_subdomains('example.com')).to_not eq [account]
end end
end end
@ -877,7 +877,7 @@ RSpec.describe Account do
it 'returns an array of accounts who have a domain' do it 'returns an array of accounts who have a domain' do
account_1 = Fabricate(:account, domain: nil) account_1 = Fabricate(:account, domain: nil)
account_2 = Fabricate(:account, domain: 'example.com') account_2 = Fabricate(:account, domain: 'example.com')
expect(Account.remote).to contain_exactly(account_2) expect(described_class.remote).to contain_exactly(account_2)
end end
end end
@ -885,7 +885,7 @@ RSpec.describe Account do
it 'returns an array of accounts who do not have a domain' do it 'returns an array of accounts who do not have a domain' do
account_1 = Fabricate(:account, domain: nil) account_1 = Fabricate(:account, domain: nil)
account_2 = Fabricate(:account, domain: 'example.com') account_2 = Fabricate(:account, domain: 'example.com')
expect(Account.where('id > 0').local).to contain_exactly(account_1) expect(described_class.where('id > 0').local).to contain_exactly(account_1)
end end
end end
@ -896,14 +896,14 @@ RSpec.describe Account do
matches[index] = Fabricate(:account, domain: matches[index]) matches[index] = Fabricate(:account, domain: matches[index])
end end
expect(Account.where('id > 0').partitioned).to match_array(matches) expect(described_class.where('id > 0').partitioned).to match_array(matches)
end end
end end
describe 'recent' do describe 'recent' do
it 'returns a relation of accounts sorted by recent creation' do it 'returns a relation of accounts sorted by recent creation' do
matches = Array.new(2) { Fabricate(:account) } matches = Array.new(2) { Fabricate(:account) }
expect(Account.where('id > 0').recent).to match_array(matches) expect(described_class.where('id > 0').recent).to match_array(matches)
end end
end end
@ -911,7 +911,7 @@ RSpec.describe Account do
it 'returns an array of accounts who are silenced' do it 'returns an array of accounts who are silenced' do
account_1 = Fabricate(:account, silenced: true) account_1 = Fabricate(:account, silenced: true)
account_2 = Fabricate(:account, silenced: false) account_2 = Fabricate(:account, silenced: false)
expect(Account.silenced).to contain_exactly(account_1) expect(described_class.silenced).to contain_exactly(account_1)
end end
end end
@ -919,7 +919,7 @@ RSpec.describe Account do
it 'returns an array of accounts who are suspended' do it 'returns an array of accounts who are suspended' do
account_1 = Fabricate(:account, suspended: true) account_1 = Fabricate(:account, suspended: true)
account_2 = Fabricate(:account, suspended: false) account_2 = Fabricate(:account, suspended: false)
expect(Account.suspended).to contain_exactly(account_1) expect(described_class.suspended).to contain_exactly(account_1)
end end
end end
@ -941,18 +941,18 @@ RSpec.describe Account do
end end
it 'returns every usable non-suspended account' do it 'returns every usable non-suspended account' do
expect(Account.searchable).to contain_exactly(silenced_local, silenced_remote, local_account, remote_account) expect(described_class.searchable).to contain_exactly(silenced_local, silenced_remote, local_account, remote_account)
end end
it 'does not mess with previously-applied scopes' do it 'does not mess with previously-applied scopes' do
expect(Account.where.not(id: remote_account.id).searchable).to contain_exactly(silenced_local, silenced_remote, local_account) expect(described_class.where.not(id: remote_account.id).searchable).to contain_exactly(silenced_local, silenced_remote, local_account)
end end
end end
end end
context 'when is local' do context 'when is local' do
it 'generates keys' do it 'generates keys' do
account = Account.create!(domain: nil, username: Faker::Internet.user_name(separators: ['_'])) account = described_class.create!(domain: nil, username: Faker::Internet.user_name(separators: ['_']))
expect(account.keypair).to be_private expect(account.keypair).to be_private
expect(account.keypair).to be_public expect(account.keypair).to be_public
end end
@ -961,12 +961,12 @@ RSpec.describe Account do
context 'when is remote' do context 'when is remote' do
it 'does not generate keys' do it 'does not generate keys' do
key = OpenSSL::PKey::RSA.new(1024).public_key key = OpenSSL::PKey::RSA.new(1024).public_key
account = Account.create!(domain: 'remote', username: Faker::Internet.user_name(separators: ['_']), public_key: key.to_pem) account = described_class.create!(domain: 'remote', username: Faker::Internet.user_name(separators: ['_']), public_key: key.to_pem)
expect(account.keypair.params).to eq key.params expect(account.keypair.params).to eq key.params
end end
it 'normalizes domain' do it 'normalizes domain' do
account = Account.create!(domain: 'にゃん', username: Faker::Internet.user_name(separators: ['_'])) account = described_class.create!(domain: 'にゃん', username: Faker::Internet.user_name(separators: ['_']))
expect(account.domain).to eq 'xn--r9j5b5b' expect(account.domain).to eq 'xn--r9j5b5b'
end end
end end
@ -986,7 +986,7 @@ RSpec.describe Account do
threads = Array.new(increment_by) do threads = Array.new(increment_by) do
Thread.new do Thread.new do
true while wait_for_start true while wait_for_start
Account.find(subject.id).increment_count!(:followers_count) described_class.find(subject.id).increment_count!(:followers_count)
end end
end end

View file

@ -23,7 +23,7 @@ RSpec.describe Block do
Rails.cache.write("exclude_account_ids_for:#{account.id}", []) Rails.cache.write("exclude_account_ids_for:#{account.id}", [])
Rails.cache.write("exclude_account_ids_for:#{target_account.id}", []) Rails.cache.write("exclude_account_ids_for:#{target_account.id}", [])
Block.create!(account: account, target_account: target_account) described_class.create!(account: account, target_account: target_account)
expect(Rails.cache.exist?("exclude_account_ids_for:#{account.id}")).to be false expect(Rails.cache.exist?("exclude_account_ids_for:#{account.id}")).to be false
expect(Rails.cache.exist?("exclude_account_ids_for:#{target_account.id}")).to be false expect(Rails.cache.exist?("exclude_account_ids_for:#{target_account.id}")).to be false
@ -32,7 +32,7 @@ RSpec.describe Block do
it 'removes blocking cache after destruction' do it 'removes blocking cache after destruction' do
account = Fabricate(:account) account = Fabricate(:account)
target_account = Fabricate(:account) target_account = Fabricate(:account)
block = Block.create!(account: account, target_account: target_account) block = described_class.create!(account: account, target_account: target_account)
Rails.cache.write("exclude_account_ids_for:#{account.id}", [target_account.id]) Rails.cache.write("exclude_account_ids_for:#{account.id}", [target_account.id])
Rails.cache.write("exclude_account_ids_for:#{target_account.id}", [account.id]) Rails.cache.write("exclude_account_ids_for:#{target_account.id}", [account.id])

View file

@ -21,74 +21,92 @@ RSpec.describe DomainBlock do
describe '.blocked?' do describe '.blocked?' do
it 'returns true if the domain is suspended' do it 'returns true if the domain is suspended' do
Fabricate(:domain_block, domain: 'example.com', severity: :suspend) Fabricate(:domain_block, domain: 'example.com', severity: :suspend)
expect(DomainBlock.blocked?('example.com')).to be true expect(described_class.blocked?('example.com')).to be true
end end
it 'returns false even if the domain is silenced' do it 'returns false even if the domain is silenced' do
Fabricate(:domain_block, domain: 'example.com', severity: :silence) Fabricate(:domain_block, domain: 'example.com', severity: :silence)
expect(DomainBlock.blocked?('example.com')).to be false expect(described_class.blocked?('example.com')).to be false
end end
it 'returns false if the domain is not suspended nor silenced' do it 'returns false if the domain is not suspended nor silenced' do
expect(DomainBlock.blocked?('example.com')).to be false expect(described_class.blocked?('example.com')).to be false
end end
end end
describe '.rule_for' do describe '.rule_for' do
it 'returns rule matching a blocked domain' do it 'returns rule matching a blocked domain' do
block = Fabricate(:domain_block, domain: 'example.com') block = Fabricate(:domain_block, domain: 'example.com')
expect(DomainBlock.rule_for('example.com')).to eq block expect(described_class.rule_for('example.com')).to eq block
end end
it 'returns a rule matching a subdomain of a blocked domain' do it 'returns a rule matching a subdomain of a blocked domain' do
block = Fabricate(:domain_block, domain: 'example.com') block = Fabricate(:domain_block, domain: 'example.com')
expect(DomainBlock.rule_for('sub.example.com')).to eq block expect(described_class.rule_for('sub.example.com')).to eq block
end end
it 'returns a rule matching a blocked subdomain' do it 'returns a rule matching a blocked subdomain' do
block = Fabricate(:domain_block, domain: 'sub.example.com') block = Fabricate(:domain_block, domain: 'sub.example.com')
expect(DomainBlock.rule_for('sub.example.com')).to eq block expect(described_class.rule_for('sub.example.com')).to eq block
end end
it 'returns a rule matching a blocked TLD' do it 'returns a rule matching a blocked TLD' do
block = Fabricate(:domain_block, domain: 'google') block = Fabricate(:domain_block, domain: 'google')
expect(DomainBlock.rule_for('google')).to eq block expect(described_class.rule_for('google')).to eq block
end end
it 'returns a rule matching a subdomain of a blocked TLD' do it 'returns a rule matching a subdomain of a blocked TLD' do
block = Fabricate(:domain_block, domain: 'google') block = Fabricate(:domain_block, domain: 'google')
expect(DomainBlock.rule_for('maps.google')).to eq block expect(described_class.rule_for('maps.google')).to eq block
end end
end end
describe '#stricter_than?' do describe '#stricter_than?' do
it 'returns true if the new block has suspend severity while the old has lower severity' do it 'returns true if the new block has suspend severity while the old has lower severity' do
suspend = DomainBlock.new(domain: 'domain', severity: :suspend) suspend = described_class.new(domain: 'domain', severity: :suspend)
silence = DomainBlock.new(domain: 'domain', severity: :silence) silence = described_class.new(domain: 'domain', severity: :silence)
noop = DomainBlock.new(domain: 'domain', severity: :noop) noop = described_class.new(domain: 'domain', severity: :noop)
expect(suspend.stricter_than?(silence)).to be true expect(suspend.stricter_than?(silence)).to be true
expect(suspend.stricter_than?(noop)).to be true expect(suspend.stricter_than?(noop)).to be true
end end
it 'returns false if the new block has lower severity than the old one' do it 'returns false if the new block has lower severity than the old one' do
suspend = DomainBlock.new(domain: 'domain', severity: :suspend) suspend = described_class.new(domain: 'domain', severity: :suspend)
silence = DomainBlock.new(domain: 'domain', severity: :silence) silence = described_class.new(domain: 'domain', severity: :silence)
noop = DomainBlock.new(domain: 'domain', severity: :noop) noop = described_class.new(domain: 'domain', severity: :noop)
expect(silence.stricter_than?(suspend)).to be false expect(silence.stricter_than?(suspend)).to be false
expect(noop.stricter_than?(suspend)).to be false expect(noop.stricter_than?(suspend)).to be false
expect(noop.stricter_than?(silence)).to be false expect(noop.stricter_than?(silence)).to be false
end end
it 'returns false if the new block does is less strict regarding reports' do it 'returns false if the new block does is less strict regarding reports' do
older = DomainBlock.new(domain: 'domain', severity: :silence, reject_reports: true) older = described_class.new(domain: 'domain', severity: :silence, reject_reports: true)
newer = DomainBlock.new(domain: 'domain', severity: :silence, reject_reports: false) newer = described_class.new(domain: 'domain', severity: :silence, reject_reports: false)
expect(newer.stricter_than?(older)).to be false expect(newer.stricter_than?(older)).to be false
end end
it 'returns false if the new block does is less strict regarding media' do it 'returns false if the new block does is less strict regarding media' do
older = DomainBlock.new(domain: 'domain', severity: :silence, reject_media: true) older = described_class.new(domain: 'domain', severity: :silence, reject_media: true)
newer = DomainBlock.new(domain: 'domain', severity: :silence, reject_media: false) newer = described_class.new(domain: 'domain', severity: :silence, reject_media: false)
expect(newer.stricter_than?(older)).to be false expect(newer.stricter_than?(older)).to be false
end end
end end
describe '#public_domain' do
context 'with a domain block that is obfuscated' do
let(:domain_block) { Fabricate(:domain_block, domain: 'hostname.example.com', obfuscate: true) }
it 'garbles the domain' do
expect(domain_block.public_domain).to eq 'hostna**.******e.com'
end
end
context 'with a domain block that is not obfuscated' do
let(:domain_block) { Fabricate(:domain_block, domain: 'example.com', obfuscate: false) }
it 'returns the domain value' do
expect(domain_block.public_domain).to eq 'example.com'
end
end
end
end end

View file

@ -9,21 +9,21 @@ RSpec.describe EmailDomainBlock do
context 'when given an e-mail address' do context 'when given an e-mail address' do
let(:input) { "foo@#{domain}" } let(:input) { "foo@#{domain}" }
context do context 'with a top level domain' do
let(:domain) { 'example.com' } let(:domain) { 'example.com' }
it 'returns true if the domain is blocked' do it 'returns true if the domain is blocked' do
Fabricate(:email_domain_block, domain: 'example.com') Fabricate(:email_domain_block, domain: 'example.com')
expect(EmailDomainBlock.block?(input)).to be true expect(described_class.block?(input)).to be true
end end
it 'returns false if the domain is not blocked' do it 'returns false if the domain is not blocked' do
Fabricate(:email_domain_block, domain: 'other-example.com') Fabricate(:email_domain_block, domain: 'other-example.com')
expect(EmailDomainBlock.block?(input)).to be false expect(described_class.block?(input)).to be false
end end
end end
context do context 'with a subdomain' do
let(:domain) { 'mail.example.com' } let(:domain) { 'mail.example.com' }
it 'returns true if it is a subdomain of a blocked domain' do it 'returns true if it is a subdomain of a blocked domain' do
@ -38,7 +38,7 @@ RSpec.describe EmailDomainBlock do
it 'returns true if the domain is blocked' do it 'returns true if the domain is blocked' do
Fabricate(:email_domain_block, domain: 'mail.foo.com') Fabricate(:email_domain_block, domain: 'mail.foo.com')
expect(EmailDomainBlock.block?(input)).to be true expect(described_class.block?(input)).to be true
end end
end end
end end

View file

@ -12,7 +12,7 @@ describe Export do
it 'returns a csv of the blocked accounts' do it 'returns a csv of the blocked accounts' do
target_accounts.each { |target_account| account.block!(target_account) } target_accounts.each { |target_account| account.block!(target_account) }
export = Export.new(account).to_blocked_accounts_csv export = described_class.new(account).to_blocked_accounts_csv
results = export.strip.split results = export.strip.split
expect(results.size).to eq 2 expect(results.size).to eq 2
@ -22,7 +22,7 @@ describe Export do
it 'returns a csv of the muted accounts' do it 'returns a csv of the muted accounts' do
target_accounts.each { |target_account| account.mute!(target_account) } target_accounts.each { |target_account| account.mute!(target_account) }
export = Export.new(account).to_muted_accounts_csv export = described_class.new(account).to_muted_accounts_csv
results = export.strip.split("\n") results = export.strip.split("\n")
expect(results.size).to eq 3 expect(results.size).to eq 3
@ -33,7 +33,7 @@ describe Export do
it 'returns a csv of the following accounts' do it 'returns a csv of the following accounts' do
target_accounts.each { |target_account| account.follow!(target_account) } target_accounts.each { |target_account| account.follow!(target_account) }
export = Export.new(account).to_following_accounts_csv export = described_class.new(account).to_following_accounts_csv
results = export.strip.split("\n") results = export.strip.split("\n")
expect(results.size).to eq 3 expect(results.size).to eq 3
@ -45,24 +45,24 @@ describe Export do
describe 'total_storage' do describe 'total_storage' do
it 'returns the total size of the media attachments' do it 'returns the total size of the media attachments' do
media_attachment = Fabricate(:media_attachment, account: account) media_attachment = Fabricate(:media_attachment, account: account)
expect(Export.new(account).total_storage).to eq media_attachment.file_file_size || 0 expect(described_class.new(account).total_storage).to eq media_attachment.file_file_size || 0
end end
end end
describe 'total_follows' do describe 'total_follows' do
it 'returns the total number of the followed accounts' do it 'returns the total number of the followed accounts' do
target_accounts.each { |target_account| account.follow!(target_account) } target_accounts.each { |target_account| account.follow!(target_account) }
expect(Export.new(account.reload).total_follows).to eq 2 expect(described_class.new(account.reload).total_follows).to eq 2
end end
it 'returns the total number of the blocked accounts' do it 'returns the total number of the blocked accounts' do
target_accounts.each { |target_account| account.block!(target_account) } target_accounts.each { |target_account| account.block!(target_account) }
expect(Export.new(account.reload).total_blocks).to eq 2 expect(described_class.new(account.reload).total_blocks).to eq 2
end end
it 'returns the total number of the muted accounts' do it 'returns the total number of the muted accounts' do
target_accounts.each { |target_account| account.mute!(target_account) } target_accounts.each { |target_account| account.mute!(target_account) }
expect(Export.new(account.reload).total_mutes).to eq 2 expect(described_class.new(account.reload).total_mutes).to eq 2
end end
end end
end end

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