Merge
This commit is contained in:
parent
1d9c77063e
commit
84eae6fac9
32 changed files with 556 additions and 586 deletions
3
Gemfile
3
Gemfile
|
@ -106,6 +106,9 @@ group :test do
|
||||||
# Used to split testing into chunks in CI
|
# Used to split testing into chunks in CI
|
||||||
gem 'rspec_chunked', '~> 0.6'
|
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
|
# RSpec progress bar formatter
|
||||||
gem 'fuubar', '~> 2.5'
|
gem 'fuubar', '~> 2.5'
|
||||||
|
|
||||||
|
|
|
@ -513,7 +513,7 @@ GEM
|
||||||
premailer (~> 1.7, >= 1.7.9)
|
premailer (~> 1.7, >= 1.7.9)
|
||||||
private_address_check (0.5.0)
|
private_address_check (0.5.0)
|
||||||
public_suffix (5.0.3)
|
public_suffix (5.0.3)
|
||||||
puma (6.3.1)
|
puma (6.4.0)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
pundit (2.3.0)
|
pundit (2.3.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
|
@ -602,6 +602,8 @@ GEM
|
||||||
rspec-expectations (3.12.3)
|
rspec-expectations (3.12.3)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.12.0)
|
rspec-support (~> 3.12.0)
|
||||||
|
rspec-github (2.4.0)
|
||||||
|
rspec-core (~> 3.0)
|
||||||
rspec-mocks (3.12.5)
|
rspec-mocks (3.12.5)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.12.0)
|
rspec-support (~> 3.12.0)
|
||||||
|
@ -636,11 +638,11 @@ GEM
|
||||||
unicode-display_width (>= 2.4.0, < 3.0)
|
unicode-display_width (>= 2.4.0, < 3.0)
|
||||||
rubocop-ast (1.29.0)
|
rubocop-ast (1.29.0)
|
||||||
parser (>= 3.2.1.0)
|
parser (>= 3.2.1.0)
|
||||||
rubocop-capybara (2.18.0)
|
rubocop-capybara (2.19.0)
|
||||||
rubocop (~> 1.41)
|
rubocop (~> 1.41)
|
||||||
rubocop-factory_bot (2.23.1)
|
rubocop-factory_bot (2.23.1)
|
||||||
rubocop (~> 1.33)
|
rubocop (~> 1.33)
|
||||||
rubocop-performance (1.19.0)
|
rubocop-performance (1.19.1)
|
||||||
rubocop (>= 1.7.0, < 2.0)
|
rubocop (>= 1.7.0, < 2.0)
|
||||||
rubocop-ast (>= 0.4.0)
|
rubocop-ast (>= 0.4.0)
|
||||||
rubocop-rails (2.20.2)
|
rubocop-rails (2.20.2)
|
||||||
|
@ -887,6 +889,7 @@ DEPENDENCIES
|
||||||
redis (~> 4.5)
|
redis (~> 4.5)
|
||||||
redis-namespace (~> 1.10)
|
redis-namespace (~> 1.10)
|
||||||
rqrcode (~> 2.2)
|
rqrcode (~> 2.2)
|
||||||
|
rspec-github (~> 2.4)
|
||||||
rspec-rails (~> 6.0)
|
rspec-rails (~> 6.0)
|
||||||
rspec-retry (>= 0.6.2)
|
rspec-retry (>= 0.6.2)
|
||||||
rspec-sidekiq (~> 4.0)
|
rspec-sidekiq (~> 4.0)
|
||||||
|
|
|
@ -66,7 +66,7 @@ class Search extends PureComponent {
|
||||||
{ label: <><mark>before:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('before:'); } },
|
{ label: <><mark>before:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('before:'); } },
|
||||||
{ label: <><mark>during:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('during:'); } },
|
{ label: <><mark>during:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('during:'); } },
|
||||||
{ label: <><mark>after:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('after:'); } },
|
{ label: <><mark>after:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('after:'); } },
|
||||||
{ label: <><mark>in:</mark> <FormattedList type='disjunction' value={['all', 'library']} /></>, action: e => { e.preventDefault(); this._insertText('in:'); } },
|
{ label: <><mark>in:</mark> <FormattedList type='disjunction' value={['all', 'library', 'public']} /></>, action: e => { e.preventDefault(); this._insertText('in:'); } },
|
||||||
{ label: <><mark>order:</mark> <FormattedList type='disjunction' value={['desc', 'asc']} /></>, action: e => { e.preventDefault(); this._insertText('order:'); } },
|
{ label: <><mark>order:</mark> <FormattedList type='disjunction' value={['desc', 'asc']} /></>, action: e => { e.preventDefault(); this._insertText('order:'); } },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -198,7 +198,9 @@ class SwitchingColumnsArea extends PureComponent {
|
||||||
|
|
||||||
{singleColumn ? <Redirect from='/deck' to='/home' exact /> : null}
|
{singleColumn ? <Redirect from='/deck' to='/home' exact /> : null}
|
||||||
{singleColumn && pathName.startsWith('/deck/') ? <Redirect from={pathName} to={pathName.slice(5)} /> : null}
|
{singleColumn && pathName.startsWith('/deck/') ? <Redirect from={pathName} to={pathName.slice(5)} /> : null}
|
||||||
|
{/* Redirect old bookmarks (without /deck) with home-like routes to the advanced interface */}
|
||||||
{!singleColumn && pathName === '/getting-started' ? <Redirect from='/getting-started' to='/deck/getting-started' exact /> : null}
|
{!singleColumn && pathName === '/getting-started' ? <Redirect from='/getting-started' to='/deck/getting-started' exact /> : null}
|
||||||
|
{!singleColumn && pathName === '/home' ? <Redirect from='/home' to='/deck/getting-started' exact /> : null}
|
||||||
|
|
||||||
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
||||||
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
||||||
|
|
|
@ -113,6 +113,7 @@ const initialPath = document.querySelector("head meta[name=initialPath]")?.getAt
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
export const hasMultiColumnPath = initialPath === '/'
|
export const hasMultiColumnPath = initialPath === '/'
|
||||||
|| initialPath === '/getting-started'
|
|| initialPath === '/getting-started'
|
||||||
|
|| initialPath === '/home'
|
||||||
|| initialPath.startsWith('/deck');
|
|| initialPath.startsWith('/deck');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -79,6 +79,8 @@ class SearchQueryTransformer < Parslet::Transform
|
||||||
case @flags['in']
|
case @flags['in']
|
||||||
when 'library'
|
when 'library'
|
||||||
[StatusesIndex]
|
[StatusesIndex]
|
||||||
|
when 'public'
|
||||||
|
[PublicStatusesIndex]
|
||||||
else
|
else
|
||||||
@options[:current_account].user&.setting_use_public_index ? [PublicStatusesIndex, StatusesIndex] : [StatusesIndex]
|
@options[:current_account].user&.setting_use_public_index ? [PublicStatusesIndex, StatusesIndex] : [StatusesIndex]
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,16 +11,31 @@ class UnreservedUsernameValidator < ActiveModel::Validator
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def pam_controlled?
|
def reserved_username?
|
||||||
return false unless Devise.pam_authentication && Devise.pam_controlled_service
|
pam_username_reserved? || settings_username_reserved?
|
||||||
|
|
||||||
Rpam2.account(Devise.pam_controlled_service, @username).present?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def reserved_username?
|
def pam_username_reserved?
|
||||||
return true if pam_controlled?
|
pam_controlled? && pam_reserves_username?
|
||||||
return false unless Setting.reserved_usernames
|
end
|
||||||
|
|
||||||
|
def pam_controlled?
|
||||||
|
Devise.pam_authentication && Devise.pam_controlled_service
|
||||||
|
end
|
||||||
|
|
||||||
|
def pam_reserves_username?
|
||||||
|
Rpam2.account(Devise.pam_controlled_service, @username)
|
||||||
|
end
|
||||||
|
|
||||||
|
def settings_username_reserved?
|
||||||
|
settings_has_reserved_usernames? && settings_reserves_username?
|
||||||
|
end
|
||||||
|
|
||||||
|
def settings_has_reserved_usernames?
|
||||||
|
Setting.reserved_usernames.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def settings_reserves_username?
|
||||||
Setting.reserved_usernames.include?(@username.downcase)
|
Setting.reserved_usernames.include?(@username.downcase)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,76 +33,6 @@
|
||||||
],
|
],
|
||||||
"note": ""
|
"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": "Mass Assignment",
|
|
||||||
"warning_code": 105,
|
|
||||||
"fingerprint": "874be88fedf4c680926845e9a588d3197765a6ccbfdd76466b44cc00151c612e",
|
|
||||||
"check_name": "PermitAttributes",
|
|
||||||
"message": "Potentially dangerous key allowed for mass assignment",
|
|
||||||
"file": "app/controllers/api/v1/admin/reports_controller.rb",
|
|
||||||
"line": 88,
|
|
||||||
"link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
|
|
||||||
"code": "params.permit(:resolved, :account_id, :target_account_id)",
|
|
||||||
"render_path": null,
|
|
||||||
"location": {
|
|
||||||
"type": "method",
|
|
||||||
"class": "Api::V1::Admin::ReportsController",
|
|
||||||
"method": "filter_params"
|
|
||||||
},
|
|
||||||
"user_input": ":account_id",
|
|
||||||
"confidence": "High",
|
|
||||||
"cwe_id": [
|
|
||||||
915
|
|
||||||
],
|
|
||||||
"note": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"warning_type": "Mass Assignment",
|
|
||||||
"warning_code": 105,
|
|
||||||
"fingerprint": "ab5035dd1a9f8c3a8d92fb2c37e8fe86fede4f87c91b71aa32e89c9eede602fc",
|
|
||||||
"check_name": "PermitAttributes",
|
|
||||||
"message": "Potentially dangerous key allowed for mass assignment",
|
|
||||||
"file": "app/controllers/api/v1/notifications_controller.rb",
|
|
||||||
"line": 81,
|
|
||||||
"link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
|
|
||||||
"code": "params.permit(:account_id, :types => ([]), :exclude_types => ([]))",
|
|
||||||
"render_path": null,
|
|
||||||
"location": {
|
|
||||||
"type": "method",
|
|
||||||
"class": "Api::V1::NotificationsController",
|
|
||||||
"method": "browserable_params"
|
|
||||||
},
|
|
||||||
"user_input": ":account_id",
|
|
||||||
"confidence": "High",
|
|
||||||
"cwe_id": [
|
|
||||||
915
|
|
||||||
],
|
|
||||||
"note": ""
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"warning_type": "Cross-Site Scripting",
|
"warning_type": "Cross-Site Scripting",
|
||||||
"warning_code": 4,
|
"warning_code": 4,
|
||||||
|
|
|
@ -111,7 +111,7 @@ services:
|
||||||
test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 6' || false"]
|
test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 6' || false"]
|
||||||
|
|
||||||
## Uncomment to enable federation with tor instances along with adding the following ENV variables
|
## 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
|
## ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
|
||||||
# tor:
|
# tor:
|
||||||
# image: sirboops/tor
|
# image: sirboops/tor
|
||||||
|
|
|
@ -125,7 +125,7 @@ module Mastodon::CLI
|
||||||
failed = Concurrent::AtomicFixnum.new(0)
|
failed = Concurrent::AtomicFixnum.new(0)
|
||||||
start_at = Time.now.to_f
|
start_at = Time.now.to_f
|
||||||
seed = start ? [start] : Instance.pluck(:domain)
|
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
|
progress = create_progress_bar
|
||||||
|
|
||||||
pool = Concurrent::ThreadPoolExecutor.new(min_threads: 0, max_threads: options[:concurrency], idletime: 10, auto_terminate: true, max_queue: 0)
|
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
|
private
|
||||||
|
|
||||||
|
def domain_block_suspended_domains
|
||||||
|
DomainBlock.suspend.pluck(:domain)
|
||||||
|
end
|
||||||
|
|
||||||
def stats_to_summary(stats, processed, failed, start_at)
|
def stats_to_summary(stats, processed, failed, start_at)
|
||||||
stats.compact!
|
stats.compact!
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,67 @@ RSpec.describe AccountsController do
|
||||||
|
|
||||||
let(:account) { Fabricate(:account) }
|
let(:account) { Fabricate(:account) }
|
||||||
|
|
||||||
|
shared_examples 'unapproved account check' do
|
||||||
|
before { account.user.update(approved: false) }
|
||||||
|
|
||||||
|
it 'returns http not found' do
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
|
||||||
|
expect(response).to have_http_status(404)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples 'permanently suspended account check' do
|
||||||
|
before do
|
||||||
|
account.suspend!
|
||||||
|
account.deletion_request.destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns http gone' do
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
|
||||||
|
expect(response).to have_http_status(410)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples 'temporarily suspended account check' do |code: 403|
|
||||||
|
before { account.suspend! }
|
||||||
|
|
||||||
|
it 'returns appropriate http response code' do
|
||||||
|
get :show, params: { username: account.username, format: format }
|
||||||
|
|
||||||
|
expect(response).to have_http_status(code)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'GET #show' do
|
describe 'GET #show' do
|
||||||
|
context 'with basic account status checks' do
|
||||||
|
context 'with HTML' do
|
||||||
let(:format) { 'html' }
|
let(:format) { 'html' }
|
||||||
|
|
||||||
|
it_behaves_like 'unapproved account check'
|
||||||
|
it_behaves_like 'permanently suspended account check'
|
||||||
|
it_behaves_like 'temporarily suspended account check'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with JSON' do
|
||||||
|
let(:format) { 'json' }
|
||||||
|
|
||||||
|
it_behaves_like 'unapproved account check'
|
||||||
|
it_behaves_like 'permanently suspended account check'
|
||||||
|
it_behaves_like 'temporarily suspended account check', code: 200
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with RSS' do
|
||||||
|
let(:format) { 'rss' }
|
||||||
|
|
||||||
|
it_behaves_like 'unapproved account check'
|
||||||
|
it_behaves_like 'permanently suspended account check'
|
||||||
|
it_behaves_like 'temporarily suspended account check'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with existing statuses' do
|
||||||
let!(:status) { Fabricate(:status, account: account) }
|
let!(:status) { Fabricate(:status, account: account) }
|
||||||
let!(:status_reply) { Fabricate(:status, account: account, thread: Fabricate(:status)) }
|
let!(:status_reply) { Fabricate(:status, account: account, thread: Fabricate(:status)) }
|
||||||
let!(:status_self_reply) { Fabricate(:status, account: account, thread: status) }
|
let!(:status_self_reply) { Fabricate(:status, account: account, thread: status) }
|
||||||
|
@ -25,57 +83,15 @@ RSpec.describe AccountsController do
|
||||||
account.pinned_statuses << status_private
|
account.pinned_statuses << status_private
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'preliminary checks' do
|
|
||||||
context 'when account is not approved' do
|
|
||||||
before do
|
|
||||||
account.user.update(approved: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns http not found' do
|
|
||||||
get :show, params: { username: account.username, format: format }
|
|
||||||
expect(response).to have_http_status(404)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with HTML' do
|
context 'with HTML' do
|
||||||
let(:format) { 'html' }
|
let(:format) { 'html' }
|
||||||
|
|
||||||
it_behaves_like 'preliminary checks'
|
shared_examples 'common HTML response' do
|
||||||
|
it 'returns a standard HTML response', :aggregate_failures do
|
||||||
context 'when account is permanently suspended' do
|
|
||||||
before do
|
|
||||||
account.suspend!
|
|
||||||
account.deletion_request.destroy
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns http gone' do
|
|
||||||
get :show, params: { username: account.username, format: format }
|
|
||||||
expect(response).to have_http_status(410)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when account is temporarily suspended' do
|
|
||||||
before do
|
|
||||||
account.suspend!
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns http forbidden' do
|
|
||||||
get :show, params: { username: account.username, format: format }
|
|
||||||
expect(response).to have_http_status(403)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples 'common response characteristics' do
|
|
||||||
it 'returns http success' do
|
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns Link header' do
|
|
||||||
expect(response.headers['Link'].to_s).to include ActivityPub::TagManager.instance.uri_for(account)
|
expect(response.headers['Link'].to_s).to include ActivityPub::TagManager.instance.uri_for(account)
|
||||||
end
|
|
||||||
|
|
||||||
it 'renders show template' do
|
|
||||||
expect(response).to render_template(:show)
|
expect(response).to render_template(:show)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -85,7 +101,7 @@ RSpec.describe AccountsController do
|
||||||
get :show, params: { username: account.username, format: format }
|
get :show, params: { username: account.username, format: format }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'common response characteristics'
|
it_behaves_like 'common HTML response'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with replies' do
|
context 'with replies' do
|
||||||
|
@ -94,7 +110,7 @@ RSpec.describe AccountsController do
|
||||||
get :show, params: { username: account.username, format: format }
|
get :show, params: { username: account.username, format: format }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'common response characteristics'
|
it_behaves_like 'common HTML response'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with media' do
|
context 'with media' do
|
||||||
|
@ -103,7 +119,7 @@ RSpec.describe AccountsController do
|
||||||
get :show, params: { username: account.username, format: format }
|
get :show, params: { username: account.username, format: format }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'common response characteristics'
|
it_behaves_like 'common HTML response'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with tag' do
|
context 'with tag' do
|
||||||
|
@ -117,7 +133,7 @@ RSpec.describe AccountsController do
|
||||||
get :show, params: { username: account.username, format: format, tag: tag.to_param }
|
get :show, params: { username: account.username, format: format, tag: tag.to_param }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'common response characteristics'
|
it_behaves_like 'common HTML response'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -129,51 +145,21 @@ RSpec.describe AccountsController do
|
||||||
allow(controller).to receive(:authorized_fetch_mode?).and_return(authorized_fetch_mode)
|
allow(controller).to receive(:authorized_fetch_mode?).and_return(authorized_fetch_mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'preliminary checks'
|
|
||||||
|
|
||||||
context 'when account is suspended permanently' do
|
|
||||||
before do
|
|
||||||
account.suspend!
|
|
||||||
account.deletion_request.destroy
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns http gone' do
|
|
||||||
get :show, params: { username: account.username, format: format }
|
|
||||||
expect(response).to have_http_status(410)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when account is suspended temporarily' do
|
|
||||||
before do
|
|
||||||
account.suspend!
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns http success' do
|
|
||||||
get :show, params: { username: account.username, format: format }
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with a normal account in a JSON request' 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
|
||||||
|
|
||||||
it 'returns http success' do
|
it 'returns a JSON version of the account', :aggregate_failures do
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns application/activity+json' do
|
|
||||||
expect(response.media_type).to eq 'application/activity+json'
|
expect(response.media_type).to eq 'application/activity+json'
|
||||||
|
|
||||||
|
expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
|
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
|
||||||
|
|
||||||
it 'renders account' do
|
|
||||||
json = body_as_json
|
|
||||||
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with authorized fetch mode' do
|
context 'with authorized fetch mode' do
|
||||||
let(:authorized_fetch_mode) { true }
|
let(:authorized_fetch_mode) { true }
|
||||||
|
|
||||||
|
@ -191,21 +177,14 @@ RSpec.describe AccountsController do
|
||||||
get :show, params: { username: account.username, format: format }
|
get :show, params: { username: account.username, format: format }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns http success' do
|
it 'returns a private JSON version of the account', :aggregate_failures do
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns application/activity+json' do
|
|
||||||
expect(response.media_type).to eq 'application/activity+json'
|
expect(response.media_type).to eq 'application/activity+json'
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns private Cache-Control header' do
|
|
||||||
expect(response.headers['Cache-Control']).to include 'private'
|
expect(response.headers['Cache-Control']).to include 'private'
|
||||||
end
|
|
||||||
|
|
||||||
it 'renders account' do
|
expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||||
json = body_as_json
|
|
||||||
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -217,43 +196,29 @@ RSpec.describe AccountsController do
|
||||||
get :show, params: { username: account.username, format: format }
|
get :show, params: { username: account.username, format: format }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns http success' do
|
it 'returns a JSON version of the account', :aggregate_failures do
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns application/activity+json' do
|
|
||||||
expect(response.media_type).to eq 'application/activity+json'
|
expect(response.media_type).to eq 'application/activity+json'
|
||||||
|
|
||||||
|
expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
|
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
|
||||||
|
|
||||||
it 'renders account' do
|
|
||||||
json = body_as_json
|
|
||||||
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with authorized fetch mode' do
|
context 'with authorized fetch mode' do
|
||||||
let(:authorized_fetch_mode) { true }
|
let(:authorized_fetch_mode) { true }
|
||||||
|
|
||||||
it 'returns http success' do
|
it 'returns a private signature JSON version of the account', :aggregate_failures do
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns application/activity+json' do
|
|
||||||
expect(response.media_type).to eq 'application/activity+json'
|
expect(response.media_type).to eq 'application/activity+json'
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns private Cache-Control header' do
|
|
||||||
expect(response.headers['Cache-Control']).to include 'private'
|
expect(response.headers['Cache-Control']).to include 'private'
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns Vary header with Signature' do
|
|
||||||
expect(response.headers['Vary']).to include 'Signature'
|
expect(response.headers['Vary']).to include 'Signature'
|
||||||
end
|
|
||||||
|
|
||||||
it 'renders account' do
|
expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
||||||
json = body_as_json
|
|
||||||
expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -262,32 +227,7 @@ RSpec.describe AccountsController do
|
||||||
context 'with RSS' do
|
context 'with RSS' do
|
||||||
let(:format) { 'rss' }
|
let(:format) { 'rss' }
|
||||||
|
|
||||||
it_behaves_like 'preliminary checks'
|
shared_examples 'common RSS response' do
|
||||||
|
|
||||||
context 'when account is permanently suspended' do
|
|
||||||
before do
|
|
||||||
account.suspend!
|
|
||||||
account.deletion_request.destroy
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns http gone' do
|
|
||||||
get :show, params: { username: account.username, format: format }
|
|
||||||
expect(response).to have_http_status(410)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when account is temporarily suspended' do
|
|
||||||
before do
|
|
||||||
account.suspend!
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns http forbidden' do
|
|
||||||
get :show, params: { username: account.username, format: format }
|
|
||||||
expect(response).to have_http_status(403)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples 'common response characteristics' do
|
|
||||||
it 'returns http success' do
|
it 'returns http success' do
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
end
|
end
|
||||||
|
@ -300,34 +240,16 @@ RSpec.describe AccountsController do
|
||||||
get :show, params: { username: account.username, format: format }
|
get :show, params: { username: account.username, format: format }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'common response characteristics'
|
it_behaves_like 'common RSS response'
|
||||||
|
|
||||||
it 'renders public status' do
|
it 'responds with correct statuses', :aggregate_failures do
|
||||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
|
expect(response.body).to include_status_tag(status_media)
|
||||||
end
|
expect(response.body).to include_status_tag(status_self_reply)
|
||||||
|
expect(response.body).to include_status_tag(status)
|
||||||
it 'renders self-reply' do
|
expect(response.body).to_not include_status_tag(status_direct)
|
||||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
expect(response.body).to_not include_status_tag(status_private)
|
||||||
end
|
expect(response.body).to_not include_status_tag(status_reblog.reblog)
|
||||||
|
expect(response.body).to_not include_status_tag(status_reply)
|
||||||
it 'renders status with media' do
|
|
||||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not render reblog' do
|
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not render private status' do
|
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not render direct status' do
|
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not render reply to someone else' do
|
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -337,34 +259,16 @@ RSpec.describe AccountsController do
|
||||||
get :show, params: { username: account.username, format: format }
|
get :show, params: { username: account.username, format: format }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'common response characteristics'
|
it_behaves_like 'common RSS response'
|
||||||
|
|
||||||
it 'renders public status' do
|
it 'responds with correct statuses with replies', :aggregate_failures do
|
||||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
|
expect(response.body).to include_status_tag(status_media)
|
||||||
end
|
expect(response.body).to include_status_tag(status_reply)
|
||||||
|
expect(response.body).to include_status_tag(status_self_reply)
|
||||||
it 'renders self-reply' do
|
expect(response.body).to include_status_tag(status)
|
||||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
expect(response.body).to_not include_status_tag(status_direct)
|
||||||
end
|
expect(response.body).to_not include_status_tag(status_private)
|
||||||
|
expect(response.body).to_not include_status_tag(status_reblog.reblog)
|
||||||
it 'renders status with media' do
|
|
||||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not render reblog' do
|
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not render private status' do
|
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not render direct status' do
|
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'renders reply to someone else' do
|
|
||||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reply))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -374,34 +278,16 @@ RSpec.describe AccountsController do
|
||||||
get :show, params: { username: account.username, format: format }
|
get :show, params: { username: account.username, format: format }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'common response characteristics'
|
it_behaves_like 'common RSS response'
|
||||||
|
|
||||||
it 'does not render public status' do
|
it 'responds with correct statuses with media', :aggregate_failures do
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
|
expect(response.body).to include_status_tag(status_media)
|
||||||
end
|
expect(response.body).to_not include_status_tag(status_direct)
|
||||||
|
expect(response.body).to_not include_status_tag(status_private)
|
||||||
it 'does not render self-reply' do
|
expect(response.body).to_not include_status_tag(status_reblog.reblog)
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
expect(response.body).to_not include_status_tag(status_reply)
|
||||||
end
|
expect(response.body).to_not include_status_tag(status_self_reply)
|
||||||
|
expect(response.body).to_not include_status_tag(status)
|
||||||
it 'renders status with media' do
|
|
||||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not render reblog' do
|
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not render private status' do
|
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not render direct status' do
|
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not render reply to someone else' do
|
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -416,40 +302,24 @@ RSpec.describe AccountsController do
|
||||||
get :show, params: { username: account.username, format: format, tag: tag.to_param }
|
get :show, params: { username: account.username, format: format, tag: tag.to_param }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'common response characteristics'
|
it_behaves_like 'common RSS response'
|
||||||
|
|
||||||
it 'does not render public status' do
|
it 'responds with correct statuses with a tag', :aggregate_failures do
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
|
expect(response.body).to include_status_tag(status_tag)
|
||||||
|
expect(response.body).to_not include_status_tag(status_direct)
|
||||||
|
expect(response.body).to_not include_status_tag(status_media)
|
||||||
|
expect(response.body).to_not include_status_tag(status_private)
|
||||||
|
expect(response.body).to_not include_status_tag(status_reblog.reblog)
|
||||||
|
expect(response.body).to_not include_status_tag(status_reply)
|
||||||
|
expect(response.body).to_not include_status_tag(status_self_reply)
|
||||||
|
expect(response.body).to_not include_status_tag(status)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not render self-reply' do
|
def include_status_tag(status)
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
|
include ActivityPub::TagManager.instance.url_for(status)
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not render status with media' do
|
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not render reblog' do
|
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not render private status' do
|
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not render direct status' do
|
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not render reply to someone else' do
|
|
||||||
expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'renders status with tag' do
|
|
||||||
expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_tag))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,7 +22,7 @@ describe 'Admin::Accounts' do
|
||||||
|
|
||||||
context 'without selecting any accounts' do
|
context 'without selecting any accounts' do
|
||||||
it 'displays a notice about account selection' 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)
|
expect(page).to have_content(selection_error_text)
|
||||||
end
|
end
|
||||||
|
@ -32,7 +32,7 @@ describe 'Admin::Accounts' do
|
||||||
it 'suspends the account' do
|
it 'suspends the account' do
|
||||||
batch_checkbox_for(approved_user_account).check
|
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
|
expect(approved_user_account.reload).to be_suspended
|
||||||
end
|
end
|
||||||
|
@ -42,7 +42,7 @@ describe 'Admin::Accounts' do
|
||||||
it 'approves the account user' do
|
it 'approves the account user' do
|
||||||
batch_checkbox_for(unapproved_user_account).check
|
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
|
expect(unapproved_user_account.reload.user).to be_approved
|
||||||
end
|
end
|
||||||
|
@ -52,7 +52,7 @@ describe 'Admin::Accounts' do
|
||||||
it 'rejects and removes the account' do
|
it 'rejects and removes the account' do
|
||||||
batch_checkbox_for(unapproved_user_account).check
|
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)
|
expect { unapproved_user_account.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,7 +16,7 @@ describe 'Admin::CustomEmojis' do
|
||||||
|
|
||||||
context 'without selecting any records' do
|
context 'without selecting any records' do
|
||||||
it 'displays a notice about selection' 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)
|
expect(page).to have_content(selection_error_text)
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,7 @@ describe 'blocking domains through the moderation interface' do
|
||||||
|
|
||||||
fill_in 'domain_block_domain', with: 'example.com'
|
fill_in 'domain_block_domain', with: 'example.com'
|
||||||
select I18n.t('admin.domain_blocks.new.severity.silence'), from: 'domain_block_severity'
|
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
|
expect(DomainBlock.exists?(domain: 'example.com', severity: 'silence')).to be true
|
||||||
end
|
end
|
||||||
|
@ -25,13 +25,13 @@ describe 'blocking domains through the moderation interface' do
|
||||||
|
|
||||||
fill_in 'domain_block_domain', with: 'example.com'
|
fill_in 'domain_block_domain', with: 'example.com'
|
||||||
select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity'
|
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
|
# It presents a confirmation screen
|
||||||
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
|
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
|
||||||
|
|
||||||
# Confirming creates a block
|
# 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
|
expect(DomainBlock.exists?(domain: 'example.com', severity: 'suspend')).to be true
|
||||||
end
|
end
|
||||||
|
@ -45,13 +45,13 @@ describe 'blocking domains through the moderation interface' do
|
||||||
|
|
||||||
fill_in 'domain_block_domain', with: 'example.com'
|
fill_in 'domain_block_domain', with: 'example.com'
|
||||||
select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity'
|
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
|
# It presents a confirmation screen
|
||||||
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
|
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
|
||||||
|
|
||||||
# Confirming updates the block
|
# 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'
|
expect(domain_block.reload.severity).to eq 'suspend'
|
||||||
end
|
end
|
||||||
|
@ -65,13 +65,13 @@ describe 'blocking domains through the moderation interface' do
|
||||||
|
|
||||||
fill_in 'domain_block_domain', with: 'subdomain.example.com'
|
fill_in 'domain_block_domain', with: 'subdomain.example.com'
|
||||||
select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity'
|
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
|
# It presents a confirmation screen
|
||||||
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'subdomain.example.com'))
|
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'subdomain.example.com'))
|
||||||
|
|
||||||
# Confirming creates the block
|
# 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
|
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)
|
visit edit_admin_domain_block_path(domain_block)
|
||||||
|
|
||||||
select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity'
|
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
|
# It presents a confirmation screen
|
||||||
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
|
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
|
||||||
|
|
||||||
# Confirming updates the block
|
# 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'
|
expect(domain_block.reload.severity).to eq 'suspend'
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,7 +16,7 @@ describe 'Admin::EmailDomainBlocks' do
|
||||||
|
|
||||||
context 'without selecting any records' do
|
context 'without selecting any records' do
|
||||||
it 'displays a notice about selection' 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)
|
expect(page).to have_content(selection_error_text)
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,7 +16,7 @@ describe 'Admin::IpBlocks' do
|
||||||
|
|
||||||
context 'without selecting any records' do
|
context 'without selecting any records' do
|
||||||
it 'displays a notice about selection' 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)
|
expect(page).to have_content(selection_error_text)
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
it 'shows a link to the software updates page, which links to release notes' do
|
||||||
visit settings_profile_path
|
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_title(I18n.t('admin.software_updates.title'))
|
||||||
|
|
||||||
expect(page).to have_content('99.99.99')
|
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)
|
expect(page).to have_current_path('https://github.com/mastodon/mastodon/releases/v99', url: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,7 +17,7 @@ describe 'Admin::Statuses' do
|
||||||
|
|
||||||
context 'without selecting any records' do
|
context 'without selecting any records' do
|
||||||
it 'displays a notice about selection' 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)
|
expect(page).to have_content(selection_error_text)
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,7 +16,7 @@ describe 'Admin::Trends::Links::PreviewCardProviders' do
|
||||||
|
|
||||||
context 'without selecting any records' do
|
context 'without selecting any records' do
|
||||||
it 'displays a notice about selection' 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)
|
expect(page).to have_content(selection_error_text)
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,7 +16,7 @@ describe 'Admin::Trends::Links' do
|
||||||
|
|
||||||
context 'without selecting any records' do
|
context 'without selecting any records' do
|
||||||
it 'displays a notice about selection' 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)
|
expect(page).to have_content(selection_error_text)
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,7 +16,7 @@ describe 'Admin::Trends::Statuses' do
|
||||||
|
|
||||||
context 'without selecting any records' do
|
context 'without selecting any records' do
|
||||||
it 'displays a notice about selection' 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)
|
expect(page).to have_content(selection_error_text)
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,7 +16,7 @@ describe 'Admin::Trends::Tags' do
|
||||||
|
|
||||||
context 'without selecting any records' do
|
context 'without selecting any records' do
|
||||||
it 'displays a notice about selection' 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)
|
expect(page).to have_content(selection_error_text)
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,7 +27,7 @@ describe 'email confirmation flow when captcha is enabled' do
|
||||||
expect(user.reload.confirmed?).to be false
|
expect(user.reload.confirmed?).to be false
|
||||||
|
|
||||||
# It redirects to app and confirms user
|
# 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(user.reload.confirmed?).to be true
|
||||||
expect(page).to have_current_path(/\A#{client_app.confirmation_redirect_uri}/, url: true)
|
expect(page).to have_current_path(/\A#{client_app.confirmation_redirect_uri}/, url: true)
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,7 +19,7 @@ describe 'Log in' do
|
||||||
it 'A valid email and password user is able to 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_email', with: email
|
||||||
fill_in 'user_password', with: password
|
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')
|
expect(subject).to have_css('div.app-holder')
|
||||||
end
|
end
|
||||||
|
@ -27,7 +27,7 @@ describe 'Log in' do
|
||||||
it 'A invalid email and password user is not able to 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_email', with: 'invalid_email'
|
||||||
fill_in 'user_password', with: 'invalid_password'
|
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'))
|
expect(subject).to have_css('.flash-message', text: failure_message('invalid'))
|
||||||
end
|
end
|
||||||
|
@ -38,7 +38,7 @@ describe 'Log in' do
|
||||||
it 'A unconfirmed user is able to log in' do
|
it 'A unconfirmed user is able to log in' do
|
||||||
fill_in 'user_email', with: email
|
fill_in 'user_email', with: email
|
||||||
fill_in 'user_password', with: password
|
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')
|
expect(subject).to have_css('div.admin-wrapper')
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,7 +20,7 @@ describe 'Using OAuth from an external app' do
|
||||||
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
|
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
|
||||||
|
|
||||||
# Upon authorizing, it redirects to the apps' callback URL
|
# 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)
|
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
|
||||||
|
|
||||||
# It grants the app access to the account
|
# 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'))
|
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.deny'))
|
||||||
|
|
||||||
# Upon denying, it redirects to the apps' callback URL
|
# 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)
|
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
|
||||||
|
|
||||||
# It does not grant the app access to the account
|
# 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
|
# Failing to log-in presents the form again
|
||||||
fill_in 'user_email', with: email
|
fill_in 'user_email', with: email
|
||||||
fill_in 'user_password', with: 'wrong password'
|
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'))
|
expect(page).to have_content(I18n.t('auth.login'))
|
||||||
|
|
||||||
# Logging in redirects to an authorization page
|
# Logging in redirects to an authorization page
|
||||||
fill_in 'user_email', with: email
|
fill_in 'user_email', with: email
|
||||||
fill_in 'user_password', with: password
|
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'))
|
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
|
||||||
|
|
||||||
# Upon authorizing, it redirects to the apps' callback URL
|
# 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)
|
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
|
||||||
|
|
||||||
# It grants the app access to the account
|
# 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
|
# Failing to log-in presents the form again
|
||||||
fill_in 'user_email', with: email
|
fill_in 'user_email', with: email
|
||||||
fill_in 'user_password', with: 'wrong password'
|
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'))
|
expect(page).to have_content(I18n.t('auth.login'))
|
||||||
|
|
||||||
# Logging in redirects to an authorization page
|
# Logging in redirects to an authorization page
|
||||||
fill_in 'user_email', with: email
|
fill_in 'user_email', with: email
|
||||||
fill_in 'user_password', with: password
|
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'))
|
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
|
||||||
|
|
||||||
# Upon denying, it redirects to the apps' callback URL
|
# 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)
|
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
|
||||||
|
|
||||||
# It does not grant the app access to the account
|
# 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
|
# Failing to log-in presents the form again
|
||||||
fill_in 'user_email', with: email
|
fill_in 'user_email', with: email
|
||||||
fill_in 'user_password', with: 'wrong password'
|
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'))
|
expect(page).to have_content(I18n.t('auth.login'))
|
||||||
|
|
||||||
# Logging in redirects to a two-factor authentication page
|
# Logging in redirects to a two-factor authentication page
|
||||||
fill_in 'user_email', with: email
|
fill_in 'user_email', with: email
|
||||||
fill_in 'user_password', with: password
|
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'))
|
expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp'))
|
||||||
|
|
||||||
# Filling in an incorrect two-factor authentication code presents the form again
|
# Filling in an incorrect two-factor authentication code presents the form again
|
||||||
fill_in 'user_otp_attempt', with: 'wrong'
|
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'))
|
expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp'))
|
||||||
|
|
||||||
# Filling in the correct TOTP code redirects to an app authorization page
|
# Filling in the correct TOTP code redirects to an app authorization page
|
||||||
fill_in 'user_otp_attempt', with: user.current_otp
|
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'))
|
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
|
||||||
|
|
||||||
# Upon authorizing, it redirects to the apps' callback URL
|
# 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)
|
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
|
||||||
|
|
||||||
# It grants the app access to the account
|
# 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
|
# Failing to log-in presents the form again
|
||||||
fill_in 'user_email', with: email
|
fill_in 'user_email', with: email
|
||||||
fill_in 'user_password', with: 'wrong password'
|
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'))
|
expect(page).to have_content(I18n.t('auth.login'))
|
||||||
|
|
||||||
# Logging in redirects to a two-factor authentication page
|
# Logging in redirects to a two-factor authentication page
|
||||||
fill_in 'user_email', with: email
|
fill_in 'user_email', with: email
|
||||||
fill_in 'user_password', with: password
|
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'))
|
expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp'))
|
||||||
|
|
||||||
# Filling in an incorrect two-factor authentication code presents the form again
|
# Filling in an incorrect two-factor authentication code presents the form again
|
||||||
fill_in 'user_otp_attempt', with: 'wrong'
|
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'))
|
expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp'))
|
||||||
|
|
||||||
# Filling in the correct TOTP code redirects to an app authorization page
|
# Filling in the correct TOTP code redirects to an app authorization page
|
||||||
fill_in 'user_otp_attempt', with: user.current_otp
|
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'))
|
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
|
||||||
|
|
||||||
# Upon denying, it redirects to the apps' callback URL
|
# 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)
|
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
|
||||||
|
|
||||||
# It does not grant the app access to the account
|
# It does not grant the app access to the account
|
||||||
|
|
|
@ -6,6 +6,24 @@ require 'mastodon/cli/accounts'
|
||||||
describe Mastodon::CLI::Accounts do
|
describe Mastodon::CLI::Accounts do
|
||||||
let(:cli) { described_class.new }
|
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
|
describe '.exit_on_failure?' do
|
||||||
it 'returns true' do
|
it 'returns true' do
|
||||||
expect(described_class.exit_on_failure?).to be true
|
expect(described_class.exit_on_failure?).to be true
|
||||||
|
@ -551,20 +569,15 @@ describe Mastodon::CLI::Accounts do
|
||||||
let!(:follower_rony) { Fabricate(:account, username: 'rony') }
|
let!(:follower_rony) { Fabricate(:account, username: 'rony') }
|
||||||
let!(:follower_charles) { Fabricate(:account, username: 'charles') }
|
let!(:follower_charles) { Fabricate(:account, username: 'charles') }
|
||||||
let(:follow_service) { instance_double(FollowService, call: nil) }
|
let(:follow_service) { instance_double(FollowService, call: nil) }
|
||||||
let(:scope) { Account.local.without_suspended }
|
|
||||||
|
|
||||||
before do
|
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)
|
allow(FollowService).to receive(:new).and_return(follow_service)
|
||||||
|
stub_parallelize_with_progress!
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'makes all local accounts follow the target account' do
|
it 'makes all local accounts follow the target account' do
|
||||||
cli.follow(target_account.username)
|
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_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_rony, target_account, any_args).once
|
||||||
expect(follow_service).to have_received(:call).with(follower_charles, 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
|
it 'displays a successful message' do
|
||||||
expect { cli.follow(target_account.username) }.to output(
|
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
|
).to_stdout
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -592,26 +605,21 @@ describe Mastodon::CLI::Accounts do
|
||||||
|
|
||||||
context 'when the given username is found' do
|
context 'when the given username is found' do
|
||||||
let!(:target_account) { Fabricate(:account) }
|
let!(:target_account) { Fabricate(:account) }
|
||||||
let!(:follower_chris) { Fabricate(:account, username: 'chris') }
|
let!(:follower_chris) { Fabricate(:account, username: 'chris', domain: nil) }
|
||||||
let!(:follower_rambo) { Fabricate(:account, username: 'rambo') }
|
let!(:follower_rambo) { Fabricate(:account, username: 'rambo', domain: nil) }
|
||||||
let!(:follower_ana) { Fabricate(:account, username: 'ana') }
|
let!(:follower_ana) { Fabricate(:account, username: 'ana', domain: nil) }
|
||||||
let(:unfollow_service) { instance_double(UnfollowService, call: nil) }
|
let(:unfollow_service) { instance_double(UnfollowService, call: nil) }
|
||||||
let(:scope) { target_account.followers.local }
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
accounts = [follower_chris, follower_rambo, follower_ana]
|
accounts = [follower_chris, follower_rambo, follower_ana]
|
||||||
accounts.each { |account| target_account.follow!(account) }
|
accounts.each { |account| account.follow!(target_account) }
|
||||||
allow(cli).to receive(:parallelize_with_progress).and_yield(follower_chris)
|
|
||||||
.and_yield(follower_rambo)
|
|
||||||
.and_yield(follower_ana)
|
|
||||||
.and_return([3, nil])
|
|
||||||
allow(UnfollowService).to receive(:new).and_return(unfollow_service)
|
allow(UnfollowService).to receive(:new).and_return(unfollow_service)
|
||||||
|
stub_parallelize_with_progress!
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'makes all local accounts unfollow the target account' do
|
it 'makes all local accounts unfollow the target account' do
|
||||||
cli.unfollow(target_account.username)
|
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_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_rambo, target_account).once
|
||||||
expect(unfollow_service).to have_received(:call).with(follower_ana, 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 }
|
let(:scope) { Account.remote }
|
||||||
|
|
||||||
before do
|
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)
|
allow(cli).to receive(:parallelize_with_progress).and_yield(remote_account_example_com)
|
||||||
.and_yield(account_example_net)
|
.and_yield(account_example_net)
|
||||||
.and_return([2, nil])
|
.and_return([2, nil])
|
||||||
|
@ -1112,26 +1122,19 @@ describe Mastodon::CLI::Accounts do
|
||||||
|
|
||||||
describe '#cull' do
|
describe '#cull' do
|
||||||
let(:delete_account_service) { instance_double(DeleteAccountService, call: nil) }
|
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!(: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') }
|
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') }
|
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') }
|
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') }
|
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
|
before do
|
||||||
allow(DeleteAccountService).to receive(:new).and_return(delete_account_service)
|
allow(DeleteAccountService).to receive(:new).and_return(delete_account_service)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when no domain is specified' do
|
context 'when no domain is specified' do
|
||||||
let(:scope) { Account.remote.where(protocol: :activitypub).partitioned }
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(cli).to receive(:parallelize_with_progress).and_yield(tom)
|
stub_parallelize_with_progress!
|
||||||
.and_yield(bob)
|
|
||||||
.and_yield(gon)
|
|
||||||
.and_yield(ana)
|
|
||||||
.and_yield(tales)
|
|
||||||
.and_return([5, 3])
|
|
||||||
stub_request(:head, 'https://example.org/users/bob').to_return(status: 404)
|
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/gon').to_return(status: 410)
|
||||||
stub_request(:head, 'https://example.net/users/tales').to_return(status: 200)
|
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
|
it 'deletes all inactive remote accounts that longer exist in the origin server' do
|
||||||
cli.cull
|
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(bob, reserve_username: false).once
|
||||||
expect(delete_account_service).to have_received(:call).with(gon, reserve_username: false).once
|
expect(delete_account_service).to have_received(:call).with(gon, reserve_username: false).once
|
||||||
end
|
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
|
it 'does not delete any active remote account that still exists in the origin server' do
|
||||||
cli.cull
|
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(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(ana, reserve_username: false)
|
||||||
expect(delete_account_service).to_not have_received(:call).with(tales, reserve_username: false)
|
expect(delete_account_service).to_not have_received(:call).with(tales, reserve_username: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'touches inactive remote accounts that have not been deleted' do
|
it 'touches inactive remote accounts that have not been deleted' do
|
||||||
allow(tales).to receive(:touch)
|
expect { cli.cull }.to(change { tales.reload.updated_at })
|
||||||
|
|
||||||
cli.cull
|
|
||||||
|
|
||||||
expect(tales).to have_received(:touch).once
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'displays the summary correctly' do
|
it 'displays the summary correctly' do
|
||||||
expect { cli.cull }.to output(
|
expect { cli.cull }.to output(
|
||||||
a_string_including('Visited 5 accounts, removed 3')
|
a_string_including('Visited 5 accounts, removed 2')
|
||||||
).to_stdout
|
).to_stdout
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when a domain is specified' do
|
context 'when a domain is specified' do
|
||||||
let(:domain) { 'example.net' }
|
let(:domain) { 'example.net' }
|
||||||
let(:scope) { Account.remote.where(protocol: :activitypub, domain: domain).partitioned }
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(cli).to receive(:parallelize_with_progress).and_yield(gon)
|
stub_parallelize_with_progress!
|
||||||
.and_yield(tales)
|
|
||||||
.and_return([2, 2])
|
|
||||||
stub_request(:head, 'https://example.net/users/gon').to_return(status: 410)
|
stub_request(:head, 'https://example.net/users/gon').to_return(status: 410)
|
||||||
stub_request(:head, 'https://example.net/users/tales').to_return(status: 404)
|
stub_request(:head, 'https://example.net/users/tales').to_return(status: 404)
|
||||||
end
|
end
|
||||||
|
@ -1184,13 +1178,12 @@ describe Mastodon::CLI::Accounts do
|
||||||
it 'deletes inactive remote accounts that longer exist in the specified domain' do
|
it 'deletes inactive remote accounts that longer exist in the specified domain' do
|
||||||
cli.cull(domain)
|
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(gon, reserve_username: false).once
|
||||||
expect(delete_account_service).to have_received(:call).with(tales, reserve_username: false).once
|
expect(delete_account_service).to have_received(:call).with(tales, reserve_username: false).once
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'displays the summary correctly' do
|
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')
|
a_string_including('Visited 2 accounts, removed 2')
|
||||||
).to_stdout
|
).to_stdout
|
||||||
end
|
end
|
||||||
|
@ -1199,7 +1192,9 @@ describe Mastodon::CLI::Accounts do
|
||||||
context 'when a domain is unavailable' do
|
context 'when a domain is unavailable' do
|
||||||
shared_examples 'an unavailable domain' do
|
shared_examples 'an unavailable domain' do
|
||||||
before 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
|
end
|
||||||
|
|
||||||
it 'skips accounts from the unavailable domain' do
|
it 'skips accounts from the unavailable domain' do
|
||||||
|
@ -1210,7 +1205,7 @@ describe Mastodon::CLI::Accounts do
|
||||||
|
|
||||||
it 'displays the summary correctly' do
|
it 'displays the summary correctly' do
|
||||||
expect { cli.cull }.to output(
|
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
|
).to_stdout
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,9 +4,52 @@ require 'rails_helper'
|
||||||
require 'mastodon/cli/preview_cards'
|
require 'mastodon/cli/preview_cards'
|
||||||
|
|
||||||
describe Mastodon::CLI::PreviewCards do
|
describe Mastodon::CLI::PreviewCards do
|
||||||
|
let(:cli) { described_class.new }
|
||||||
|
|
||||||
describe '.exit_on_failure?' do
|
describe '.exit_on_failure?' do
|
||||||
it 'returns true' do
|
it 'returns true' do
|
||||||
expect(described_class.exit_on_failure?).to be true
|
expect(described_class.exit_on_failure?).to be true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#remove' do
|
||||||
|
context 'with relevant preview cards' do
|
||||||
|
before do
|
||||||
|
Fabricate(:preview_card, updated_at: 10.years.ago, type: :link)
|
||||||
|
Fabricate(:preview_card, updated_at: 10.months.ago, type: :photo)
|
||||||
|
Fabricate(:preview_card, updated_at: 10.days.ago, type: :photo)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with no arguments' do
|
||||||
|
it 'deletes thumbnails for local preview cards' do
|
||||||
|
expect { cli.invoke(:remove) }.to output(
|
||||||
|
a_string_including('Removed 2 preview cards')
|
||||||
|
.and(a_string_including('approx. 119 KB'))
|
||||||
|
).to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with the --link option' do
|
||||||
|
let(:options) { { link: true } }
|
||||||
|
|
||||||
|
it 'deletes thumbnails for local preview cards' do
|
||||||
|
expect { cli.invoke(:remove, [], options) }.to output(
|
||||||
|
a_string_including('Removed 1 link-type preview cards')
|
||||||
|
.and(a_string_including('approx. 59.6 KB'))
|
||||||
|
).to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with the --days option' do
|
||||||
|
let(:options) { { days: 365 } }
|
||||||
|
|
||||||
|
it 'deletes thumbnails for local preview cards' do
|
||||||
|
expect { cli.invoke(:remove, [], options) }.to output(
|
||||||
|
a_string_including('Removed 1 preview cards')
|
||||||
|
.and(a_string_including('approx. 59.6 KB'))
|
||||||
|
).to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,4 +29,23 @@ describe Poll do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'validations' do
|
||||||
|
context 'when valid' do
|
||||||
|
let(:poll) { Fabricate.build(:poll) }
|
||||||
|
|
||||||
|
it 'is valid with valid attributes' do
|
||||||
|
expect(poll).to be_valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when not valid' do
|
||||||
|
let(:poll) { Fabricate.build(:poll, expires_at: nil) }
|
||||||
|
|
||||||
|
it 'is invalid without an expire date' do
|
||||||
|
poll.valid?
|
||||||
|
expect(poll).to model_have_error_on_field(:expires_at)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -42,6 +42,12 @@ RSpec.configure do |config|
|
||||||
# for RSpec::Retry
|
# for RSpec::Retry
|
||||||
config.verbose_retry = true
|
config.verbose_retry = true
|
||||||
config.display_try_failure_messages = true
|
config.display_try_failure_messages = true
|
||||||
|
|
||||||
|
# Use the GitHub Annotations formatter for CI
|
||||||
|
if ENV['GITHUB_ACTIONS'] == 'true'
|
||||||
|
require 'rspec/github'
|
||||||
|
config.add_formatter RSpec::Github::Formatter
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def body_as_json
|
def body_as_json
|
||||||
|
|
|
@ -18,7 +18,7 @@ module ProfileStories
|
||||||
visit new_user_session_path
|
visit new_user_session_path
|
||||||
fill_in 'user_email', with: email
|
fill_in 'user_email', with: email
|
||||||
fill_in 'user_password', with: password
|
fill_in 'user_password', with: password
|
||||||
click_on I18n.t('auth.login')
|
click_button I18n.t('auth.login')
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_alice_as_local_user
|
def with_alice_as_local_user
|
||||||
|
|
|
@ -24,10 +24,10 @@ describe 'NewStatuses' do
|
||||||
|
|
||||||
within('.compose-form') do
|
within('.compose-form') do
|
||||||
fill_in "What's on your mind?", with: status_text
|
fill_in "What's on your mind?", with: status_text
|
||||||
click_on 'Publish!'
|
click_button 'Publish!'
|
||||||
end
|
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
|
||||||
|
|
||||||
it 'can be posted again' do
|
it 'can be posted again' do
|
||||||
|
@ -37,9 +37,9 @@ describe 'NewStatuses' do
|
||||||
|
|
||||||
within('.compose-form') do
|
within('.compose-form') do
|
||||||
fill_in "What's on your mind?", with: status_text
|
fill_in "What's on your mind?", with: status_text
|
||||||
click_on 'Publish!'
|
click_button 'Publish!'
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,41 +2,118 @@
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe UnreservedUsernameValidator, type: :validator do
|
describe UnreservedUsernameValidator do
|
||||||
|
let(:record_class) do
|
||||||
|
Class.new do
|
||||||
|
include ActiveModel::Validations
|
||||||
|
attr_accessor :username
|
||||||
|
|
||||||
|
validates_with UnreservedUsernameValidator
|
||||||
|
end
|
||||||
|
end
|
||||||
|
let(:record) { record_class.new }
|
||||||
|
|
||||||
describe '#validate' do
|
describe '#validate' do
|
||||||
|
context 'when username is nil' do
|
||||||
|
it 'does not add errors' do
|
||||||
|
record.username = nil
|
||||||
|
|
||||||
|
expect(record).to be_valid
|
||||||
|
expect(record.errors).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when PAM is enabled' do
|
||||||
before do
|
before do
|
||||||
allow(validator).to receive(:reserved_username?) { reserved_username }
|
allow(Devise).to receive(:pam_authentication).and_return(true)
|
||||||
validator.validate(account)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:validator) { described_class.new }
|
context 'with a pam service available' do
|
||||||
let(:account) { instance_double(Account, username: username, errors: errors) }
|
let(:service) { double }
|
||||||
let(:errors) { instance_double(ActiveModel::Errors, add: nil) }
|
let(:pam_class) do
|
||||||
|
Class.new do
|
||||||
context 'when @username is blank?' do
|
def self.account(service, username); end
|
||||||
let(:username) { nil }
|
|
||||||
|
|
||||||
it 'not calls errors.add' do
|
|
||||||
expect(errors).to_not have_received(:add).with(:username, any_args)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when @username is not blank?' do
|
before do
|
||||||
let(:username) { 'f' }
|
stub_const('Rpam2', pam_class)
|
||||||
|
allow(Devise).to receive(:pam_controlled_service).and_return(service)
|
||||||
|
end
|
||||||
|
|
||||||
context 'with reserved_username?' do
|
context 'when the account exists' do
|
||||||
let(:reserved_username) { true }
|
before do
|
||||||
|
allow(Rpam2).to receive(:account).with(service, 'username').and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
it 'calls errors.add' do
|
it 'adds errors to the record' do
|
||||||
expect(errors).to have_received(:add).with(:username, :reserved)
|
record.username = 'username'
|
||||||
|
|
||||||
|
expect(record).to_not be_valid
|
||||||
|
expect(record.errors.first.attribute).to eq(:username)
|
||||||
|
expect(record.errors.first.type).to eq(:reserved)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when username is not reserved' do
|
context 'when the account does not exist' do
|
||||||
let(:reserved_username) { false }
|
before do
|
||||||
|
allow(Rpam2).to receive(:account).with(service, 'username').and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
it 'not calls errors.add' do
|
it 'does not add errors to the record' do
|
||||||
expect(errors).to_not have_received(:add).with(:username, any_args)
|
record.username = 'username'
|
||||||
|
|
||||||
|
expect(record).to be_valid
|
||||||
|
expect(record.errors).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without a pam service' do
|
||||||
|
before do
|
||||||
|
allow(Devise).to receive(:pam_controlled_service).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there are not any reserved usernames' do
|
||||||
|
before do
|
||||||
|
stub_reserved_usernames(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not add errors to the record' do
|
||||||
|
record.username = 'username'
|
||||||
|
|
||||||
|
expect(record).to be_valid
|
||||||
|
expect(record.errors).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there are reserved usernames' do
|
||||||
|
before do
|
||||||
|
stub_reserved_usernames(%w(alice bob))
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the username is reserved' do
|
||||||
|
it 'adds errors to the record' do
|
||||||
|
record.username = 'alice'
|
||||||
|
|
||||||
|
expect(record).to_not be_valid
|
||||||
|
expect(record.errors.first.attribute).to eq(:username)
|
||||||
|
expect(record.errors.first.type).to eq(:reserved)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the username is not reserved' do
|
||||||
|
it 'does not add errors to the record' do
|
||||||
|
record.username = 'chris'
|
||||||
|
|
||||||
|
expect(record).to be_valid
|
||||||
|
expect(record.errors).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def stub_reserved_usernames(value)
|
||||||
|
allow(Setting).to receive(:[]).with('reserved_usernames').and_return(value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue