diff --git a/.github/workflows/build-nightly.yml b/.github/workflows/build-nightly.yml
index d2c3eea197..8adfcda841 100644
--- a/.github/workflows/build-nightly.yml
+++ b/.github/workflows/build-nightly.yml
@@ -11,6 +11,7 @@ permissions:
jobs:
compute-suffix:
runs-on: ubuntu-latest
+ if: github.repository == 'mastodon/mastodon'
steps:
- id: version_vars
env:
diff --git a/.github/workflows/crowdin-download.yml b/.github/workflows/crowdin-download.yml
index dc6fd874d1..dbd19a8d03 100644
--- a/.github/workflows/crowdin-download.yml
+++ b/.github/workflows/crowdin-download.yml
@@ -11,6 +11,7 @@ permissions:
jobs:
download-translations:
runs-on: ubuntu-latest
+ if: github.repository == 'mastodon/mastodon'
steps:
- name: Checkout
diff --git a/Gemfile b/Gemfile
index 449b0a9203..6cfdcfa5cd 100644
--- a/Gemfile
+++ b/Gemfile
@@ -106,6 +106,9 @@ group :test do
# Used to split testing into chunks in CI
gem 'rspec_chunked', '~> 0.6'
+ # Adds RSpec Error/Warning annotations to GitHub PRs on the Files tab
+ gem 'rspec-github', '~> 2.4', require: false
+
# RSpec progress bar formatter
gem 'fuubar', '~> 2.5'
diff --git a/Gemfile.lock b/Gemfile.lock
index ce21bc0a20..29cdc8aa4d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -513,7 +513,7 @@ GEM
premailer (~> 1.7, >= 1.7.9)
private_address_check (0.5.0)
public_suffix (5.0.3)
- puma (6.3.1)
+ puma (6.4.0)
nio4r (~> 2.0)
pundit (2.3.0)
activesupport (>= 3.0.0)
@@ -602,6 +602,8 @@ GEM
rspec-expectations (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
+ rspec-github (2.4.0)
+ rspec-core (~> 3.0)
rspec-mocks (3.12.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
@@ -634,11 +636,11 @@ GEM
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
- rubocop-capybara (2.18.0)
+ rubocop-capybara (2.19.0)
rubocop (~> 1.41)
rubocop-factory_bot (2.23.1)
rubocop (~> 1.33)
- rubocop-performance (1.19.0)
+ rubocop-performance (1.19.1)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-rails (2.20.2)
@@ -885,6 +887,7 @@ DEPENDENCIES
redis (~> 4.5)
redis-namespace (~> 1.10)
rqrcode (~> 2.2)
+ rspec-github (~> 2.4)
rspec-rails (~> 6.0)
rspec-sidekiq (~> 4.0)
rspec_chunked (~> 0.6)
diff --git a/app/javascript/mastodon/features/compose/components/search.jsx b/app/javascript/mastodon/features/compose/components/search.jsx
index dfbfa98f5e..2525df7793 100644
--- a/app/javascript/mastodon/features/compose/components/search.jsx
+++ b/app/javascript/mastodon/features/compose/components/search.jsx
@@ -64,7 +64,7 @@ class Search extends PureComponent {
{ label: <>before: >, action: e => { e.preventDefault(); this._insertText('before:'); } },
{ label: <>during: >, action: e => { e.preventDefault(); this._insertText('during:'); } },
{ label: <>after: >, action: e => { e.preventDefault(); this._insertText('after:'); } },
- { label: <>in: >, action: e => { e.preventDefault(); this._insertText('in:'); } }
+ { label: <>in: >, action: e => { e.preventDefault(); this._insertText('in:'); } }
];
setRef = c => {
diff --git a/app/lib/search_query_transformer.rb b/app/lib/search_query_transformer.rb
index a45ae3d09b..927495eace 100644
--- a/app/lib/search_query_transformer.rb
+++ b/app/lib/search_query_transformer.rb
@@ -58,6 +58,8 @@ class SearchQueryTransformer < Parslet::Transform
case @flags['in']
when 'library'
[StatusesIndex]
+ when 'public'
+ [PublicStatusesIndex]
else
[PublicStatusesIndex, StatusesIndex]
end
diff --git a/config/brakeman.ignore b/config/brakeman.ignore
index 02ce23a075..9f85ccb6a4 100644
--- a/config/brakeman.ignore
+++ b/config/brakeman.ignore
@@ -33,30 +33,6 @@
],
"note": ""
},
- {
- "warning_type": "Denial of Service",
- "warning_code": 76,
- "fingerprint": "7b6abba5699755348e7ee82a4694bfbf574b41c7cce2d0db0f7c11ae3f983c72",
- "check_name": "RegexDoS",
- "message": "Model attribute used in regular expression",
- "file": "lib/mastodon/cli/domains.rb",
- "line": 128,
- "link": "https://brakemanscanner.org/docs/warning_types/denial_of_service/",
- "code": "/\\.?(#{DomainBlock.where(:severity => 1).pluck(:domain).map do\n Regexp.escape(domain)\n end.join(\"|\")})$/",
- "render_path": null,
- "location": {
- "type": "method",
- "class": "Mastodon::CLI::Domains",
- "method": "crawl"
- },
- "user_input": "DomainBlock.where(:severity => 1).pluck(:domain)",
- "confidence": "Weak",
- "cwe_id": [
- 20,
- 185
- ],
- "note": ""
- },
{
"warning_type": "Cross-Site Scripting",
"warning_code": 4,
diff --git a/docker-compose.yml b/docker-compose.yml
index d19f278f75..bcfa4c85f1 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -111,7 +111,7 @@ services:
test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 6' || false"]
## Uncomment to enable federation with tor instances along with adding the following ENV variables
- ## http_proxy=http://privoxy:8118
+ ## http_hidden_proxy=http://privoxy:8118
## ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
# tor:
# image: sirboops/tor
diff --git a/lib/mastodon/cli/domains.rb b/lib/mastodon/cli/domains.rb
index d17b253681..329f171672 100644
--- a/lib/mastodon/cli/domains.rb
+++ b/lib/mastodon/cli/domains.rb
@@ -125,7 +125,7 @@ module Mastodon::CLI
failed = Concurrent::AtomicFixnum.new(0)
start_at = Time.now.to_f
seed = start ? [start] : Instance.pluck(:domain)
- blocked_domains = /\.?(#{DomainBlock.where(severity: 1).pluck(:domain).map { |domain| Regexp.escape(domain) }.join('|')})$/
+ blocked_domains = /\.?(#{Regexp.union(domain_block_suspended_domains).source})$/
progress = create_progress_bar
pool = Concurrent::ThreadPoolExecutor.new(min_threads: 0, max_threads: options[:concurrency], idletime: 10, auto_terminate: true, max_queue: 0)
@@ -189,6 +189,10 @@ module Mastodon::CLI
private
+ def domain_block_suspended_domains
+ DomainBlock.suspend.pluck(:domain)
+ end
+
def stats_to_summary(stats, processed, failed, start_at)
stats.compact!
diff --git a/spec/features/admin/accounts_spec.rb b/spec/features/admin/accounts_spec.rb
index 6d7bab1844..ad9c51485a 100644
--- a/spec/features/admin/accounts_spec.rb
+++ b/spec/features/admin/accounts_spec.rb
@@ -22,7 +22,7 @@ describe 'Admin::Accounts' do
context 'without selecting any accounts' do
it 'displays a notice about account selection' do
- click_on button_for_suspend
+ click_button button_for_suspend
expect(page).to have_content(selection_error_text)
end
@@ -32,7 +32,7 @@ describe 'Admin::Accounts' do
it 'suspends the account' do
batch_checkbox_for(approved_user_account).check
- click_on button_for_suspend
+ click_button button_for_suspend
expect(approved_user_account.reload).to be_suspended
end
@@ -42,7 +42,7 @@ describe 'Admin::Accounts' do
it 'approves the account user' do
batch_checkbox_for(unapproved_user_account).check
- click_on button_for_approve
+ click_button button_for_approve
expect(unapproved_user_account.reload.user).to be_approved
end
@@ -52,7 +52,7 @@ describe 'Admin::Accounts' do
it 'rejects and removes the account' do
batch_checkbox_for(unapproved_user_account).check
- click_on button_for_reject
+ click_button button_for_reject
expect { unapproved_user_account.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
diff --git a/spec/features/admin/custom_emojis_spec.rb b/spec/features/admin/custom_emojis_spec.rb
index 8a8b6efcd1..3fea8f06fe 100644
--- a/spec/features/admin/custom_emojis_spec.rb
+++ b/spec/features/admin/custom_emojis_spec.rb
@@ -16,7 +16,7 @@ describe 'Admin::CustomEmojis' do
context 'without selecting any records' do
it 'displays a notice about selection' do
- click_on button_for_enable
+ click_button button_for_enable
expect(page).to have_content(selection_error_text)
end
diff --git a/spec/features/admin/domain_blocks_spec.rb b/spec/features/admin/domain_blocks_spec.rb
index 4672c1e1a9..0d7b90c21c 100644
--- a/spec/features/admin/domain_blocks_spec.rb
+++ b/spec/features/admin/domain_blocks_spec.rb
@@ -13,7 +13,7 @@ describe 'blocking domains through the moderation interface' do
fill_in 'domain_block_domain', with: 'example.com'
select I18n.t('admin.domain_blocks.new.severity.silence'), from: 'domain_block_severity'
- click_on I18n.t('admin.domain_blocks.new.create')
+ click_button I18n.t('admin.domain_blocks.new.create')
expect(DomainBlock.exists?(domain: 'example.com', severity: 'silence')).to be true
end
@@ -25,13 +25,13 @@ describe 'blocking domains through the moderation interface' do
fill_in 'domain_block_domain', with: 'example.com'
select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity'
- click_on I18n.t('admin.domain_blocks.new.create')
+ click_button I18n.t('admin.domain_blocks.new.create')
# It presents a confirmation screen
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
# Confirming creates a block
- click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm')
+ click_button I18n.t('admin.domain_blocks.confirm_suspension.confirm')
expect(DomainBlock.exists?(domain: 'example.com', severity: 'suspend')).to be true
end
@@ -45,13 +45,13 @@ describe 'blocking domains through the moderation interface' do
fill_in 'domain_block_domain', with: 'example.com'
select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity'
- click_on I18n.t('admin.domain_blocks.new.create')
+ click_button I18n.t('admin.domain_blocks.new.create')
# It presents a confirmation screen
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
# Confirming updates the block
- click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm')
+ click_button I18n.t('admin.domain_blocks.confirm_suspension.confirm')
expect(domain_block.reload.severity).to eq 'suspend'
end
@@ -65,13 +65,13 @@ describe 'blocking domains through the moderation interface' do
fill_in 'domain_block_domain', with: 'subdomain.example.com'
select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity'
- click_on I18n.t('admin.domain_blocks.new.create')
+ click_button I18n.t('admin.domain_blocks.new.create')
# It presents a confirmation screen
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'subdomain.example.com'))
# Confirming creates the block
- click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm')
+ click_button I18n.t('admin.domain_blocks.confirm_suspension.confirm')
expect(DomainBlock.where(domain: 'subdomain.example.com', severity: 'suspend')).to exist
@@ -88,13 +88,13 @@ describe 'blocking domains through the moderation interface' do
visit edit_admin_domain_block_path(domain_block)
select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity'
- click_on I18n.t('generic.save_changes')
+ click_button I18n.t('generic.save_changes')
# It presents a confirmation screen
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
# Confirming updates the block
- click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm')
+ click_button I18n.t('admin.domain_blocks.confirm_suspension.confirm')
expect(domain_block.reload.severity).to eq 'suspend'
end
diff --git a/spec/features/admin/email_domain_blocks_spec.rb b/spec/features/admin/email_domain_blocks_spec.rb
index 14959cbe74..80efe72e95 100644
--- a/spec/features/admin/email_domain_blocks_spec.rb
+++ b/spec/features/admin/email_domain_blocks_spec.rb
@@ -16,7 +16,7 @@ describe 'Admin::EmailDomainBlocks' do
context 'without selecting any records' do
it 'displays a notice about selection' do
- click_on button_for_delete
+ click_button button_for_delete
expect(page).to have_content(selection_error_text)
end
diff --git a/spec/features/admin/ip_blocks_spec.rb b/spec/features/admin/ip_blocks_spec.rb
index c9b16f6f78..465c889190 100644
--- a/spec/features/admin/ip_blocks_spec.rb
+++ b/spec/features/admin/ip_blocks_spec.rb
@@ -16,7 +16,7 @@ describe 'Admin::IpBlocks' do
context 'without selecting any records' do
it 'displays a notice about selection' do
- click_on button_for_delete
+ click_button button_for_delete
expect(page).to have_content(selection_error_text)
end
diff --git a/spec/features/admin/software_updates_spec.rb b/spec/features/admin/software_updates_spec.rb
index 4a635d1a79..a2373d35a6 100644
--- a/spec/features/admin/software_updates_spec.rb
+++ b/spec/features/admin/software_updates_spec.rb
@@ -11,13 +11,13 @@ describe 'finding software updates through the admin interface' do
it 'shows a link to the software updates page, which links to release notes' do
visit settings_profile_path
- click_on I18n.t('admin.critical_update_pending')
+ click_link I18n.t('admin.critical_update_pending')
expect(page).to have_title(I18n.t('admin.software_updates.title'))
expect(page).to have_content('99.99.99')
- click_on I18n.t('admin.software_updates.release_notes')
+ click_link I18n.t('admin.software_updates.release_notes')
expect(page).to have_current_path('https://github.com/mastodon/mastodon/releases/v99', url: true)
end
end
diff --git a/spec/features/admin/statuses_spec.rb b/spec/features/admin/statuses_spec.rb
index 531d0de953..a21c901a92 100644
--- a/spec/features/admin/statuses_spec.rb
+++ b/spec/features/admin/statuses_spec.rb
@@ -17,7 +17,7 @@ describe 'Admin::Statuses' do
context 'without selecting any records' do
it 'displays a notice about selection' do
- click_on button_for_report
+ click_button button_for_report
expect(page).to have_content(selection_error_text)
end
diff --git a/spec/features/admin/trends/links/preview_card_providers_spec.rb b/spec/features/admin/trends/links/preview_card_providers_spec.rb
index dca89117b1..cf9796abf3 100644
--- a/spec/features/admin/trends/links/preview_card_providers_spec.rb
+++ b/spec/features/admin/trends/links/preview_card_providers_spec.rb
@@ -16,7 +16,7 @@ describe 'Admin::Trends::Links::PreviewCardProviders' do
context 'without selecting any records' do
it 'displays a notice about selection' do
- click_on button_for_allow
+ click_button button_for_allow
expect(page).to have_content(selection_error_text)
end
diff --git a/spec/features/admin/trends/links_spec.rb b/spec/features/admin/trends/links_spec.rb
index 99638bc069..8b1b991a5a 100644
--- a/spec/features/admin/trends/links_spec.rb
+++ b/spec/features/admin/trends/links_spec.rb
@@ -16,7 +16,7 @@ describe 'Admin::Trends::Links' do
context 'without selecting any records' do
it 'displays a notice about selection' do
- click_on button_for_allow
+ click_button button_for_allow
expect(page).to have_content(selection_error_text)
end
diff --git a/spec/features/admin/trends/statuses_spec.rb b/spec/features/admin/trends/statuses_spec.rb
index 779a15d38f..a578ab0559 100644
--- a/spec/features/admin/trends/statuses_spec.rb
+++ b/spec/features/admin/trends/statuses_spec.rb
@@ -16,7 +16,7 @@ describe 'Admin::Trends::Statuses' do
context 'without selecting any records' do
it 'displays a notice about selection' do
- click_on button_for_allow
+ click_button button_for_allow
expect(page).to have_content(selection_error_text)
end
diff --git a/spec/features/admin/trends/tags_spec.rb b/spec/features/admin/trends/tags_spec.rb
index 52e49c3a5d..7502bc8c6f 100644
--- a/spec/features/admin/trends/tags_spec.rb
+++ b/spec/features/admin/trends/tags_spec.rb
@@ -16,7 +16,7 @@ describe 'Admin::Trends::Tags' do
context 'without selecting any records' do
it 'displays a notice about selection' do
- click_on button_for_allow
+ click_button button_for_allow
expect(page).to have_content(selection_error_text)
end
diff --git a/spec/features/captcha_spec.rb b/spec/features/captcha_spec.rb
index db89ff3e61..6ccf066208 100644
--- a/spec/features/captcha_spec.rb
+++ b/spec/features/captcha_spec.rb
@@ -27,7 +27,7 @@ describe 'email confirmation flow when captcha is enabled' do
expect(user.reload.confirmed?).to be false
# It redirects to app and confirms user
- click_on I18n.t('challenge.confirm')
+ click_button I18n.t('challenge.confirm')
expect(user.reload.confirmed?).to be true
expect(page).to have_current_path(/\A#{client_app.confirmation_redirect_uri}/, url: true)
end
diff --git a/spec/features/log_in_spec.rb b/spec/features/log_in_spec.rb
index c64e19d2b7..7e5196aba9 100644
--- a/spec/features/log_in_spec.rb
+++ b/spec/features/log_in_spec.rb
@@ -19,7 +19,7 @@ describe 'Log in' do
it 'A valid email and password user is able to log in' do
fill_in 'user_email', with: email
fill_in 'user_password', with: password
- click_on I18n.t('auth.login')
+ click_button I18n.t('auth.login')
expect(subject).to have_css('div.app-holder')
end
@@ -27,7 +27,7 @@ describe 'Log in' do
it 'A invalid email and password user is not able to log in' do
fill_in 'user_email', with: 'invalid_email'
fill_in 'user_password', with: 'invalid_password'
- click_on I18n.t('auth.login')
+ click_button I18n.t('auth.login')
expect(subject).to have_css('.flash-message', text: failure_message('invalid'))
end
@@ -38,7 +38,7 @@ describe 'Log in' do
it 'A unconfirmed user is able to log in' do
fill_in 'user_email', with: email
fill_in 'user_password', with: password
- click_on I18n.t('auth.login')
+ click_button I18n.t('auth.login')
expect(subject).to have_css('div.admin-wrapper')
end
diff --git a/spec/features/oauth_spec.rb b/spec/features/oauth_spec.rb
index 967956cc8e..0e612b56a5 100644
--- a/spec/features/oauth_spec.rb
+++ b/spec/features/oauth_spec.rb
@@ -20,7 +20,7 @@ describe 'Using OAuth from an external app' do
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon authorizing, it redirects to the apps' callback URL
- click_on I18n.t('doorkeeper.authorizations.buttons.authorize')
+ click_button I18n.t('doorkeeper.authorizations.buttons.authorize')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
# It grants the app access to the account
@@ -35,7 +35,7 @@ describe 'Using OAuth from an external app' do
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.deny'))
# Upon denying, it redirects to the apps' callback URL
- click_on I18n.t('doorkeeper.authorizations.buttons.deny')
+ click_button I18n.t('doorkeeper.authorizations.buttons.deny')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
# It does not grant the app access to the account
@@ -63,17 +63,17 @@ describe 'Using OAuth from an external app' do
# Failing to log-in presents the form again
fill_in 'user_email', with: email
fill_in 'user_password', with: 'wrong password'
- click_on I18n.t('auth.login')
+ click_button I18n.t('auth.login')
expect(page).to have_content(I18n.t('auth.login'))
# Logging in redirects to an authorization page
fill_in 'user_email', with: email
fill_in 'user_password', with: password
- click_on I18n.t('auth.login')
+ click_button I18n.t('auth.login')
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon authorizing, it redirects to the apps' callback URL
- click_on I18n.t('doorkeeper.authorizations.buttons.authorize')
+ click_button I18n.t('doorkeeper.authorizations.buttons.authorize')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
# It grants the app access to the account
@@ -90,17 +90,17 @@ describe 'Using OAuth from an external app' do
# Failing to log-in presents the form again
fill_in 'user_email', with: email
fill_in 'user_password', with: 'wrong password'
- click_on I18n.t('auth.login')
+ click_button I18n.t('auth.login')
expect(page).to have_content(I18n.t('auth.login'))
# Logging in redirects to an authorization page
fill_in 'user_email', with: email
fill_in 'user_password', with: password
- click_on I18n.t('auth.login')
+ click_button I18n.t('auth.login')
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon denying, it redirects to the apps' callback URL
- click_on I18n.t('doorkeeper.authorizations.buttons.deny')
+ click_button I18n.t('doorkeeper.authorizations.buttons.deny')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
# It does not grant the app access to the account
@@ -120,27 +120,27 @@ describe 'Using OAuth from an external app' do
# Failing to log-in presents the form again
fill_in 'user_email', with: email
fill_in 'user_password', with: 'wrong password'
- click_on I18n.t('auth.login')
+ click_button I18n.t('auth.login')
expect(page).to have_content(I18n.t('auth.login'))
# Logging in redirects to a two-factor authentication page
fill_in 'user_email', with: email
fill_in 'user_password', with: password
- click_on I18n.t('auth.login')
+ click_button I18n.t('auth.login')
expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp'))
# Filling in an incorrect two-factor authentication code presents the form again
fill_in 'user_otp_attempt', with: 'wrong'
- click_on I18n.t('auth.login')
+ click_button I18n.t('auth.login')
expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp'))
# Filling in the correct TOTP code redirects to an app authorization page
fill_in 'user_otp_attempt', with: user.current_otp
- click_on I18n.t('auth.login')
+ click_button I18n.t('auth.login')
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon authorizing, it redirects to the apps' callback URL
- click_on I18n.t('doorkeeper.authorizations.buttons.authorize')
+ click_button I18n.t('doorkeeper.authorizations.buttons.authorize')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
# It grants the app access to the account
@@ -157,27 +157,27 @@ describe 'Using OAuth from an external app' do
# Failing to log-in presents the form again
fill_in 'user_email', with: email
fill_in 'user_password', with: 'wrong password'
- click_on I18n.t('auth.login')
+ click_button I18n.t('auth.login')
expect(page).to have_content(I18n.t('auth.login'))
# Logging in redirects to a two-factor authentication page
fill_in 'user_email', with: email
fill_in 'user_password', with: password
- click_on I18n.t('auth.login')
+ click_button I18n.t('auth.login')
expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp'))
# Filling in an incorrect two-factor authentication code presents the form again
fill_in 'user_otp_attempt', with: 'wrong'
- click_on I18n.t('auth.login')
+ click_button I18n.t('auth.login')
expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp'))
# Filling in the correct TOTP code redirects to an app authorization page
fill_in 'user_otp_attempt', with: user.current_otp
- click_on I18n.t('auth.login')
+ click_button I18n.t('auth.login')
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon denying, it redirects to the apps' callback URL
- click_on I18n.t('doorkeeper.authorizations.buttons.deny')
+ click_button I18n.t('doorkeeper.authorizations.buttons.deny')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
# It does not grant the app access to the account
diff --git a/spec/lib/mastodon/cli/accounts_spec.rb b/spec/lib/mastodon/cli/accounts_spec.rb
index a263d673de..5ecea5ea16 100644
--- a/spec/lib/mastodon/cli/accounts_spec.rb
+++ b/spec/lib/mastodon/cli/accounts_spec.rb
@@ -6,6 +6,24 @@ require 'mastodon/cli/accounts'
describe Mastodon::CLI::Accounts do
let(:cli) { described_class.new }
+ # `parallelize_with_progress` cannot run in transactions, so instead,
+ # stub it with an alternative implementation that runs sequentially
+ # and can run in transactions.
+ def stub_parallelize_with_progress!
+ allow(cli).to receive(:parallelize_with_progress) do |scope, &block|
+ aggregate = 0
+ total = 0
+
+ scope.reorder(nil).find_each do |record|
+ value = block.call(record)
+ aggregate += value if value.is_a?(Integer)
+ total += 1
+ end
+
+ [total, aggregate]
+ end
+ end
+
describe '.exit_on_failure?' do
it 'returns true' do
expect(described_class.exit_on_failure?).to be true
@@ -551,20 +569,15 @@ describe Mastodon::CLI::Accounts do
let!(:follower_rony) { Fabricate(:account, username: 'rony') }
let!(:follower_charles) { Fabricate(:account, username: 'charles') }
let(:follow_service) { instance_double(FollowService, call: nil) }
- let(:scope) { Account.local.without_suspended }
before do
- allow(cli).to receive(:parallelize_with_progress).and_yield(follower_bob)
- .and_yield(follower_rony)
- .and_yield(follower_charles)
- .and_return([3, nil])
allow(FollowService).to receive(:new).and_return(follow_service)
+ stub_parallelize_with_progress!
end
it 'makes all local accounts follow the target account' do
cli.follow(target_account.username)
- expect(cli).to have_received(:parallelize_with_progress).with(scope).once
expect(follow_service).to have_received(:call).with(follower_bob, target_account, any_args).once
expect(follow_service).to have_received(:call).with(follower_rony, target_account, any_args).once
expect(follow_service).to have_received(:call).with(follower_charles, target_account, any_args).once
@@ -572,7 +585,7 @@ describe Mastodon::CLI::Accounts do
it 'displays a successful message' do
expect { cli.follow(target_account.username) }.to output(
- a_string_including('OK, followed target from 3 accounts')
+ a_string_including("OK, followed target from #{Account.local.count} accounts")
).to_stdout
end
end
@@ -592,26 +605,21 @@ describe Mastodon::CLI::Accounts do
context 'when the given username is found' do
let!(:target_account) { Fabricate(:account) }
- let!(:follower_chris) { Fabricate(:account, username: 'chris') }
- let!(:follower_rambo) { Fabricate(:account, username: 'rambo') }
- let!(:follower_ana) { Fabricate(:account, username: 'ana') }
+ let!(:follower_chris) { Fabricate(:account, username: 'chris', domain: nil) }
+ let!(:follower_rambo) { Fabricate(:account, username: 'rambo', domain: nil) }
+ let!(:follower_ana) { Fabricate(:account, username: 'ana', domain: nil) }
let(:unfollow_service) { instance_double(UnfollowService, call: nil) }
- let(:scope) { target_account.followers.local }
before do
accounts = [follower_chris, follower_rambo, follower_ana]
- accounts.each { |account| target_account.follow!(account) }
- allow(cli).to receive(:parallelize_with_progress).and_yield(follower_chris)
- .and_yield(follower_rambo)
- .and_yield(follower_ana)
- .and_return([3, nil])
+ accounts.each { |account| account.follow!(target_account) }
allow(UnfollowService).to receive(:new).and_return(unfollow_service)
+ stub_parallelize_with_progress!
end
it 'makes all local accounts unfollow the target account' do
cli.unfollow(target_account.username)
- expect(cli).to have_received(:parallelize_with_progress).with(scope).once
expect(unfollow_service).to have_received(:call).with(follower_chris, target_account).once
expect(unfollow_service).to have_received(:call).with(follower_rambo, target_account).once
expect(unfollow_service).to have_received(:call).with(follower_ana, target_account).once
@@ -671,6 +679,8 @@ describe Mastodon::CLI::Accounts do
let(:scope) { Account.remote }
before do
+ # TODO: we should be using `stub_parallelize_with_progress!` but
+ # this makes the assertions harder to write
allow(cli).to receive(:parallelize_with_progress).and_yield(remote_account_example_com)
.and_yield(account_example_net)
.and_return([2, nil])
@@ -1112,26 +1122,19 @@ describe Mastodon::CLI::Accounts do
describe '#cull' do
let(:delete_account_service) { instance_double(DeleteAccountService, call: nil) }
- let!(:tom) { Fabricate(:account, updated_at: 30.days.ago, username: 'tom', uri: 'https://example.com/users/tom', domain: 'example.com') }
- let!(:bob) { Fabricate(:account, updated_at: 30.days.ago, last_webfingered_at: nil, username: 'bob', uri: 'https://example.org/users/bob', domain: 'example.org') }
- let!(:gon) { Fabricate(:account, updated_at: 15.days.ago, last_webfingered_at: 15.days.ago, username: 'gon', uri: 'https://example.net/users/gon', domain: 'example.net') }
- let!(:ana) { Fabricate(:account, username: 'ana', uri: 'https://example.com/users/ana', domain: 'example.com') }
- let!(:tales) { Fabricate(:account, updated_at: 10.days.ago, last_webfingered_at: nil, username: 'tales', uri: 'https://example.net/users/tales', domain: 'example.net') }
+ let!(:tom) { Fabricate(:account, updated_at: 30.days.ago, username: 'tom', uri: 'https://example.com/users/tom', domain: 'example.com', protocol: :activitypub) }
+ let!(:bob) { Fabricate(:account, updated_at: 30.days.ago, last_webfingered_at: nil, username: 'bob', uri: 'https://example.org/users/bob', domain: 'example.org', protocol: :activitypub) }
+ let!(:gon) { Fabricate(:account, updated_at: 15.days.ago, last_webfingered_at: 15.days.ago, username: 'gon', uri: 'https://example.net/users/gon', domain: 'example.net', protocol: :activitypub) }
+ let!(:ana) { Fabricate(:account, username: 'ana', uri: 'https://example.com/users/ana', domain: 'example.com', protocol: :activitypub) }
+ let!(:tales) { Fabricate(:account, updated_at: 10.days.ago, last_webfingered_at: nil, username: 'tales', uri: 'https://example.net/users/tales', domain: 'example.net', protocol: :activitypub) }
before do
allow(DeleteAccountService).to receive(:new).and_return(delete_account_service)
end
context 'when no domain is specified' do
- let(:scope) { Account.remote.where(protocol: :activitypub).partitioned }
-
before do
- allow(cli).to receive(:parallelize_with_progress).and_yield(tom)
- .and_yield(bob)
- .and_yield(gon)
- .and_yield(ana)
- .and_yield(tales)
- .and_return([5, 3])
+ stub_parallelize_with_progress!
stub_request(:head, 'https://example.org/users/bob').to_return(status: 404)
stub_request(:head, 'https://example.net/users/gon').to_return(status: 410)
stub_request(:head, 'https://example.net/users/tales').to_return(status: 200)
@@ -1140,7 +1143,6 @@ describe Mastodon::CLI::Accounts do
it 'deletes all inactive remote accounts that longer exist in the origin server' do
cli.cull
- expect(cli).to have_received(:parallelize_with_progress).with(scope).once
expect(delete_account_service).to have_received(:call).with(bob, reserve_username: false).once
expect(delete_account_service).to have_received(:call).with(gon, reserve_username: false).once
end
@@ -1148,35 +1150,27 @@ describe Mastodon::CLI::Accounts do
it 'does not delete any active remote account that still exists in the origin server' do
cli.cull
- expect(cli).to have_received(:parallelize_with_progress).with(scope).once
expect(delete_account_service).to_not have_received(:call).with(tom, reserve_username: false)
expect(delete_account_service).to_not have_received(:call).with(ana, reserve_username: false)
expect(delete_account_service).to_not have_received(:call).with(tales, reserve_username: false)
end
it 'touches inactive remote accounts that have not been deleted' do
- allow(tales).to receive(:touch)
-
- cli.cull
-
- expect(tales).to have_received(:touch).once
+ expect { cli.cull }.to(change { tales.reload.updated_at })
end
it 'displays the summary correctly' do
expect { cli.cull }.to output(
- a_string_including('Visited 5 accounts, removed 3')
+ a_string_including('Visited 5 accounts, removed 2')
).to_stdout
end
end
context 'when a domain is specified' do
let(:domain) { 'example.net' }
- let(:scope) { Account.remote.where(protocol: :activitypub, domain: domain).partitioned }
before do
- allow(cli).to receive(:parallelize_with_progress).and_yield(gon)
- .and_yield(tales)
- .and_return([2, 2])
+ stub_parallelize_with_progress!
stub_request(:head, 'https://example.net/users/gon').to_return(status: 410)
stub_request(:head, 'https://example.net/users/tales').to_return(status: 404)
end
@@ -1184,13 +1178,12 @@ describe Mastodon::CLI::Accounts do
it 'deletes inactive remote accounts that longer exist in the specified domain' do
cli.cull(domain)
- expect(cli).to have_received(:parallelize_with_progress).with(scope).once
expect(delete_account_service).to have_received(:call).with(gon, reserve_username: false).once
expect(delete_account_service).to have_received(:call).with(tales, reserve_username: false).once
end
it 'displays the summary correctly' do
- expect { cli.cull }.to output(
+ expect { cli.cull(domain) }.to output(
a_string_including('Visited 2 accounts, removed 2')
).to_stdout
end
@@ -1199,7 +1192,9 @@ describe Mastodon::CLI::Accounts do
context 'when a domain is unavailable' do
shared_examples 'an unavailable domain' do
before do
- allow(cli).to receive(:parallelize_with_progress).and_yield(tales).and_return([1, 0])
+ stub_parallelize_with_progress!
+ stub_request(:head, 'https://example.org/users/bob').to_return(status: 200)
+ stub_request(:head, 'https://example.net/users/gon').to_return(status: 200)
end
it 'skips accounts from the unavailable domain' do
@@ -1210,7 +1205,7 @@ describe Mastodon::CLI::Accounts do
it 'displays the summary correctly' do
expect { cli.cull }.to output(
- a_string_including("Visited 1 accounts, removed 0\nThe following domains were not available during the check:\n example.net")
+ a_string_including("Visited 5 accounts, removed 0\nThe following domains were not available during the check:\n example.net")
).to_stdout
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index b4c20545f5..4d3c234a0e 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -36,6 +36,12 @@ RSpec.configure do |config|
config.after :suite do
FileUtils.rm_rf(Dir[Rails.root.join('spec', 'test_files')])
end
+
+ # Use the GitHub Annotations formatter for CI
+ if ENV['GITHUB_ACTIONS'] == 'true'
+ require 'rspec/github'
+ config.add_formatter RSpec::Github::Formatter
+ end
end
def body_as_json
diff --git a/spec/support/stories/profile_stories.rb b/spec/support/stories/profile_stories.rb
index 2b345ddef1..82667ca080 100644
--- a/spec/support/stories/profile_stories.rb
+++ b/spec/support/stories/profile_stories.rb
@@ -18,7 +18,7 @@ module ProfileStories
visit new_user_session_path
fill_in 'user_email', with: email
fill_in 'user_password', with: password
- click_on I18n.t('auth.login')
+ click_button I18n.t('auth.login')
end
def with_alice_as_local_user
diff --git a/spec/system/new_statuses_spec.rb b/spec/system/new_statuses_spec.rb
index 6faed6c808..244101f4d4 100644
--- a/spec/system/new_statuses_spec.rb
+++ b/spec/system/new_statuses_spec.rb
@@ -24,10 +24,10 @@ describe 'NewStatuses' do
within('.compose-form') do
fill_in "What's on your mind?", with: status_text
- click_on 'Publish!'
+ click_button 'Publish!'
end
- expect(subject).to have_selector('.status__content__text', text: status_text)
+ expect(subject).to have_css('.status__content__text', text: status_text)
end
it 'can be posted again' do
@@ -37,9 +37,9 @@ describe 'NewStatuses' do
within('.compose-form') do
fill_in "What's on your mind?", with: status_text
- click_on 'Publish!'
+ click_button 'Publish!'
end
- expect(subject).to have_selector('.status__content__text', text: status_text)
+ expect(subject).to have_css('.status__content__text', text: status_text)
end
end