diff --git a/spec/lib/mastodon/cli/accounts_spec.rb b/spec/lib/mastodon/cli/accounts_spec.rb index 3216d0d1bd9..06860c2ff86 100644 --- a/spec/lib/mastodon/cli/accounts_spec.rb +++ b/spec/lib/mastodon/cli/accounts_spec.rb @@ -4,7 +4,11 @@ require 'rails_helper' require 'mastodon/cli/accounts' describe Mastodon::CLI::Accounts do + subject { cli.invoke(action, arguments, options) } + let(:cli) { described_class.new } + let(:arguments) { [] } + let(:options) { {} } it_behaves_like 'CLI Command' @@ -27,15 +31,17 @@ describe Mastodon::CLI::Accounts do end describe '#create' do + let(:action) { :create } + shared_examples 'a new user with given email address and username' do it 'creates a new user with the specified email address' do - cli.invoke(:create, arguments, options) + subject expect(User.find_by(email: options[:email])).to be_present end it 'creates a new local account with the specified username' do - cli.invoke(:create, arguments, options) + subject expect(Account.find_local('tootctl_username')).to be_present end @@ -43,9 +49,8 @@ describe Mastodon::CLI::Accounts do it 'returns "OK" and newly generated password' do allow(SecureRandom).to receive(:hex).and_return('test_password') - expect { cli.invoke(:create, arguments, options) }.to output( - a_string_including("OK\nNew password: test_password") - ).to_stdout + expect { subject } + .to output_results("OK\nNew password: test_password") end end @@ -61,9 +66,8 @@ describe Mastodon::CLI::Accounts do let(:options) { { email: 'invalid' } } it 'exits with an error message' do - expect { cli.invoke(:create, arguments, options) }.to output( - a_string_including('Failure/Error: email') - ).to_stdout + expect { subject } + .to output_results('Failure/Error: email') .and raise_error(SystemExit) end end @@ -75,7 +79,7 @@ describe Mastodon::CLI::Accounts do it_behaves_like 'a new user with given email address and username' it 'creates a new user with confirmed status' do - cli.invoke(:create, arguments, options) + subject user = User.find_by(email: options[:email]) @@ -93,7 +97,7 @@ describe Mastodon::CLI::Accounts do it_behaves_like 'a new user with given email address and username' it 'creates a new user with approved status' do - cli.invoke(:create, arguments, options) + subject user = User.find_by(email: options[:email]) @@ -109,7 +113,7 @@ describe Mastodon::CLI::Accounts do it_behaves_like 'a new user with given email address and username' it 'creates a new user and assigns the specified role' do - cli.invoke(:create, arguments, options) + subject role = User.find_by(email: options[:email])&.role @@ -121,9 +125,8 @@ describe Mastodon::CLI::Accounts do let(:options) { { email: 'tootctl@example.com', role: '404' } } it 'exits with an error message indicating the role name was not found' do - expect { cli.invoke(:create, arguments, options) }.to output( - a_string_including('Cannot find user role with that name') - ).to_stdout + expect { subject } + .to output_results('Cannot find user role with that name') .and raise_error(SystemExit) end end @@ -139,16 +142,15 @@ describe Mastodon::CLI::Accounts do end it 'returns an error message indicating the username is already taken' do - expect { cli.invoke(:create, arguments, options) }.to output( - a_string_including("The chosen username is currently in use\nUse --force to reattach it anyway and delete the other user") - ).to_stdout + expect { subject } + .to output_results("The chosen username is currently in use\nUse --force to reattach it anyway and delete the other user") end context 'with --force option' do let(:options) { { email: 'tootctl_new@example.com', reattach: true, force: true } } it 'reattaches the account to the new user and deletes the previous user' do - cli.invoke(:create, arguments, options) + subject user = Account.find_local('tootctl_username')&.user @@ -173,20 +175,21 @@ describe Mastodon::CLI::Accounts do let(:arguments) { ['tootctl_username'] } it 'raises a required argument missing error (Thor::RequiredArgumentMissingError)' do - expect { cli.invoke(:create, arguments) } + expect { subject } .to raise_error(Thor::RequiredArgumentMissingError) end end end describe '#modify' do + let(:action) { :modify } + context 'when the given username is not found' do let(:arguments) { ['non_existent_username'] } it 'exits with an error message indicating the user was not found' do - expect { cli.invoke(:modify, arguments) }.to output( - a_string_including('No user with such username') - ).to_stdout + expect { subject } + .to output_results('No user with such username') .and raise_error(SystemExit) end end @@ -197,13 +200,12 @@ describe Mastodon::CLI::Accounts do context 'when no option is provided' do it 'returns a successful message' do - expect { cli.invoke(:modify, arguments) }.to output( - a_string_including('OK') - ).to_stdout + expect { subject } + .to output_results('OK') end it 'does not modify the user' do - cli.invoke(:modify, arguments) + subject expect(user).to eq(user.reload) end @@ -214,9 +216,8 @@ describe Mastodon::CLI::Accounts do let(:options) { { role: '404' } } it 'exits with an error message indicating the role was not found' do - expect { cli.invoke(:modify, arguments, options) }.to output( - a_string_including('Cannot find user role with that name') - ).to_stdout + expect { subject } + .to output_results('Cannot find user role with that name') .and raise_error(SystemExit) end end @@ -226,7 +227,7 @@ describe Mastodon::CLI::Accounts do let(:options) { { role: default_role.name } } it "updates the user's role to the specified role" do - cli.invoke(:modify, arguments, options) + subject role = user.reload.role @@ -241,7 +242,7 @@ describe Mastodon::CLI::Accounts do let(:user) { Fabricate(:user, role: role) } it "removes the user's role successfully" do - cli.invoke(:modify, arguments, options) + subject role = user.reload.role @@ -254,13 +255,13 @@ describe Mastodon::CLI::Accounts do let(:options) { { email: 'new_email@email.com' } } it "sets the user's unconfirmed email to the provided email address" do - cli.invoke(:modify, arguments, options) + subject expect(user.reload.unconfirmed_email).to eq(options[:email]) end it "does not update the user's original email address" do - cli.invoke(:modify, arguments, options) + subject expect(user.reload.email).to eq('old_email@email.com') end @@ -270,13 +271,13 @@ describe Mastodon::CLI::Accounts do let(:options) { { email: 'new_email@email.com', confirm: true } } it "updates the user's email address to the provided email" do - cli.invoke(:modify, arguments, options) + subject expect(user.reload.email).to eq(options[:email]) end it "sets the user's email address as confirmed" do - cli.invoke(:modify, arguments, options) + subject expect(user.reload.confirmed?).to be(true) end @@ -288,7 +289,7 @@ describe Mastodon::CLI::Accounts do let(:options) { { confirm: true } } it "confirms the user's email address" do - cli.invoke(:modify, arguments, options) + subject expect(user.reload.confirmed?).to be(true) end @@ -303,7 +304,7 @@ describe Mastodon::CLI::Accounts do end it 'approves the user' do - expect { cli.invoke(:modify, arguments, options) }.to change { user.reload.approved }.from(false).to(true) + expect { subject }.to change { user.reload.approved }.from(false).to(true) end end @@ -312,7 +313,7 @@ describe Mastodon::CLI::Accounts do let(:options) { { disable: true } } it 'disables the user' do - expect { cli.invoke(:modify, arguments, options) }.to change { user.reload.disabled }.from(false).to(true) + expect { subject }.to change { user.reload.disabled }.from(false).to(true) end end @@ -321,7 +322,7 @@ describe Mastodon::CLI::Accounts do let(:options) { { enable: true } } it 'enables the user' do - expect { cli.invoke(:modify, arguments, options) }.to change { user.reload.disabled }.from(true).to(false) + expect { subject }.to change { user.reload.disabled }.from(true).to(false) end end @@ -331,9 +332,8 @@ describe Mastodon::CLI::Accounts do it 'returns a new password for the user' do allow(SecureRandom).to receive(:hex).and_return('new_password') - expect { cli.invoke(:modify, arguments, options) }.to output( - a_string_including('new_password') - ).to_stdout + expect { subject } + .to output_results('new_password') end end @@ -342,7 +342,7 @@ describe Mastodon::CLI::Accounts do let(:options) { { disable_2fa: true } } it 'disables the two-factor authentication for the user' do - expect { cli.invoke(:modify, arguments, options) }.to change { user.reload.otp_required_for_login }.from(true).to(false) + expect { subject }.to change { user.reload.otp_required_for_login }.from(true).to(false) end end @@ -351,9 +351,8 @@ describe Mastodon::CLI::Accounts do let(:options) { { email: 'invalid' } } it 'exits with an error message' do - expect { cli.invoke(:modify, arguments, options) }.to output( - a_string_including('Failure/Error: email') - ).to_stdout + expect { subject } + .to output_results('Failure/Error: email') .and raise_error(SystemExit) end end @@ -361,9 +360,8 @@ describe Mastodon::CLI::Accounts do end describe '#delete' do + let(:action) { :delete } let(:account) { Fabricate(:account) } - let(:arguments) { [account.username] } - let(:options) { { email: account.user.email } } let(:delete_account_service) { instance_double(DeleteAccountService) } before do @@ -372,26 +370,29 @@ describe Mastodon::CLI::Accounts do end context 'when both username and --email are provided' do + let(:arguments) { [account.username] } + let(:options) { { email: account.user.email } } + it 'exits with an error message indicating that only one should be used' do - expect { cli.invoke(:delete, arguments, options) }.to output( - a_string_including('Use username or --email, not both') - ).to_stdout + expect { subject } + .to output_results('Use username or --email, not both') .and raise_error(SystemExit) end end context 'when neither username nor --email are provided' do it 'exits with an error message indicating that no username was provided' do - expect { cli.invoke(:delete) }.to output( - a_string_including('No username provided') - ).to_stdout + expect { subject } + .to output_results('No username provided') .and raise_error(SystemExit) end end context 'when username is provided' do + let(:arguments) { [account.username] } + it 'deletes the specified user successfully' do - cli.invoke(:delete, arguments) + subject expect(delete_account_service).to have_received(:call).with(account, reserve_email: false).once end @@ -400,15 +401,14 @@ describe Mastodon::CLI::Accounts do let(:options) { { dry_run: true } } it 'does not delete the specified user' do - cli.invoke(:delete, arguments, options) + subject expect(delete_account_service).to_not have_received(:call).with(account, reserve_email: false) end it 'outputs a successful message in dry run mode' do - expect { cli.invoke(:delete, arguments, options) }.to output( - a_string_including('OK (DRY RUN)') - ).to_stdout + expect { subject } + .to output_results('OK (DRY RUN)') end end @@ -416,17 +416,18 @@ describe Mastodon::CLI::Accounts do let(:arguments) { ['non_existent_username'] } it 'exits with an error message indicating that no user was found' do - expect { cli.invoke(:delete, arguments) }.to output( - a_string_including('No user with such username') - ).to_stdout + expect { subject } + .to output_results('No user with such username') .and raise_error(SystemExit) end end end context 'when --email is provided' do + let(:options) { { email: account.user.email } } + it 'deletes the specified user successfully' do - cli.invoke(:delete, nil, options) + subject expect(delete_account_service).to have_received(:call).with(account, reserve_email: false).once end @@ -435,15 +436,14 @@ describe Mastodon::CLI::Accounts do let(:options) { { email: account.user.email, dry_run: true } } it 'does not delete the user' do - cli.invoke(:delete, nil, options) + subject expect(delete_account_service).to_not have_received(:call).with(account, reserve_email: false) end it 'outputs a successful message in dry run mode' do - expect { cli.invoke(:delete, nil, options) }.to output( - a_string_including('OK (DRY RUN)') - ).to_stdout + expect { subject } + .to output_results('OK (DRY RUN)') end end @@ -451,9 +451,8 @@ describe Mastodon::CLI::Accounts do let(:options) { { email: '404@example.com' } } it 'exits with an error message indicating that no user was found' do - expect { cli.invoke(:delete, nil, options) }.to output( - a_string_including('No user with such email') - ).to_stdout + expect { subject } + .to output_results('No user with such email') .and raise_error(SystemExit) end end @@ -461,6 +460,7 @@ describe Mastodon::CLI::Accounts do end describe '#approve' do + let(:action) { :approve } let(:total_users) { 4 } before do @@ -469,8 +469,10 @@ describe Mastodon::CLI::Accounts do end context 'with --all option' do + let(:options) { { all: true } } + it 'approves all pending registrations' do - cli.invoke(:approve, nil, all: true) + subject expect(User.pluck(:approved).all?(true)).to be(true) end @@ -481,7 +483,7 @@ describe Mastodon::CLI::Accounts do let(:options) { { number: 2 } } it 'approves the earliest n pending registrations' do - cli.invoke(:approve, nil, options) + subject n_earliest_pending_registrations = User.order(created_at: :asc).first(options[:number]) @@ -489,7 +491,7 @@ describe Mastodon::CLI::Accounts do end it 'does not approve the remaining pending registrations' do - cli.invoke(:approve, nil, options) + subject pending_registrations = User.order(created_at: :asc).last(total_users - options[:number]) @@ -498,10 +500,11 @@ describe Mastodon::CLI::Accounts do end context 'when the number is negative' do + let(:options) { { number: -1 } } + it 'exits with an error message indicating that the number must be positive' do - expect { cli.invoke(:approve, nil, number: -1) }.to output( - a_string_including('Number must be positive') - ).to_stdout + expect { subject } + .to output_results('Number must be positive') .and raise_error(SystemExit) end end @@ -510,13 +513,13 @@ describe Mastodon::CLI::Accounts do let(:options) { { number: total_users * 2 } } it 'approves all users' do - cli.invoke(:approve, nil, options) + subject expect(User.pluck(:approved).all?(true)).to be(true) end it 'does not raise any error' do - expect { cli.invoke(:approve, nil, options) } + expect { subject } .to_not raise_error end end @@ -528,7 +531,7 @@ describe Mastodon::CLI::Accounts do let(:arguments) { [user.account.username] } it 'approves the specified user successfully' do - cli.invoke(:approve, arguments) + subject expect(user.reload.approved?).to be(true) end @@ -538,9 +541,8 @@ describe Mastodon::CLI::Accounts do let(:arguments) { ['non_existent_username'] } it 'exits with an error message indicating that no such account was found' do - expect { cli.invoke(:approve, arguments) }.to output( - a_string_including('No such account') - ).to_stdout + expect { subject } + .to output_results('No such account') .and raise_error(SystemExit) end end @@ -548,13 +550,14 @@ describe Mastodon::CLI::Accounts do end describe '#follow' do + let(:action) { :follow } + context 'when the given username is not found' do let(:arguments) { ['non_existent_username'] } it 'exits with an error message indicating that no account with the given username was found' do - expect { cli.invoke(:follow, arguments) }.to output( - a_string_including('No such account') - ).to_stdout + expect { subject } + .to output_results('No such account') .and raise_error(SystemExit) end end @@ -565,6 +568,7 @@ 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(:arguments) { [target_account.username] } before do allow(FollowService).to receive(:new).and_return(follow_service) @@ -572,7 +576,7 @@ describe Mastodon::CLI::Accounts do end it 'makes all local accounts follow the target account' do - cli.follow(target_account.username) + subject 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 @@ -580,21 +584,21 @@ describe Mastodon::CLI::Accounts do end it 'displays a successful message' do - expect { cli.follow(target_account.username) }.to output( - a_string_including("OK, followed target from #{Account.local.count} accounts") - ).to_stdout + expect { subject } + .to output_results("OK, followed target from #{Account.local.count} accounts") end end end describe '#unfollow' do + let(:action) { :unfollow } + context 'when the given username is not found' do let(:arguments) { ['non_existent_username'] } it 'exits with an error message indicating that no account with the given username was found' do - expect { cli.invoke(:unfollow, arguments) }.to output( - a_string_including('No such account') - ).to_stdout + expect { subject } + .to output_results('No such account') .and raise_error(SystemExit) end end @@ -605,6 +609,7 @@ describe Mastodon::CLI::Accounts do 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(:arguments) { [target_account.username] } before do accounts = [follower_chris, follower_rambo, follower_ana] @@ -614,7 +619,7 @@ describe Mastodon::CLI::Accounts do end it 'makes all local accounts unfollow the target account' do - cli.unfollow(target_account.username) + subject 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 @@ -622,21 +627,21 @@ describe Mastodon::CLI::Accounts do end it 'displays a successful message' do - expect { cli.unfollow(target_account.username) }.to output( - a_string_including('OK, unfollowed target from 3 accounts') - ).to_stdout + expect { subject } + .to output_results('OK, unfollowed target from 3 accounts') end end end describe '#backup' do + let(:action) { :backup } + context 'when the given username is not found' do let(:arguments) { ['non_existent_username'] } it 'exits with an error message indicating that there is no such account' do - expect { cli.invoke(:backup, arguments) }.to output( - a_string_including('No user with such username') - ).to_stdout + expect { subject } + .to output_results('No user with such username') .and raise_error(SystemExit) end end @@ -647,22 +652,21 @@ describe Mastodon::CLI::Accounts do let(:arguments) { [account.username] } it 'creates a new backup for the specified user' do - expect { cli.invoke(:backup, arguments) }.to change { user.backups.count }.by(1) + expect { subject }.to change { user.backups.count }.by(1) end it 'creates a backup job' do allow(BackupWorker).to receive(:perform_async) - cli.invoke(:backup, arguments) + subject latest_backup = user.backups.last expect(BackupWorker).to have_received(:perform_async).with(latest_backup.id).once end it 'displays a successful message' do - expect { cli.invoke(:backup, arguments) }.to output( - a_string_including('OK') - ).to_stdout + expect { subject } + .to output_results('OK') end end end @@ -724,9 +728,8 @@ describe Mastodon::CLI::Accounts do end it 'displays a successful message' do - expect { cli.refresh }.to output( - a_string_including('Refreshed 2 accounts') - ).to_stdout + expect { cli.refresh } + .to output_results('Refreshed 2 accounts') end context 'with --dry-run option' do @@ -761,9 +764,8 @@ describe Mastodon::CLI::Accounts do end it 'displays a successful message with (DRY RUN)' do - expect { cli.refresh }.to output( - a_string_including('Refreshed 2 accounts (DRY RUN)') - ).to_stdout + expect { cli.refresh } + .to output_results('Refreshed 2 accounts (DRY RUN)') end end end @@ -823,9 +825,7 @@ describe Mastodon::CLI::Accounts do allow(account_example_com_a).to receive(:reset_avatar!).and_raise(Mastodon::UnexpectedResponseError) expect { cli.refresh(*arguments) } - .to output( - a_string_including("Account failed: #{account_example_com_a.username}@#{account_example_com_a.domain}") - ).to_stdout + .to output_results("Account failed: #{account_example_com_a.username}@#{account_example_com_a.domain}") end end @@ -833,9 +833,8 @@ describe Mastodon::CLI::Accounts do it 'exits with an error message' do allow(Account).to receive(:find_remote).with(account_example_com_b.username, account_example_com_b.domain).and_return(nil) - expect { cli.refresh(*arguments) }.to output( - a_string_including('No such account') - ).to_stdout + expect { cli.refresh(*arguments) } + .to output_results('No such account') .and raise_error(SystemExit) end end @@ -878,7 +877,6 @@ describe Mastodon::CLI::Accounts do allow(cli).to receive(:parallelize_with_progress).and_yield(account_example_com_a) .and_yield(account_example_com_b) .and_return([2, nil]) - cli.options = { domain: domain } end @@ -925,32 +923,33 @@ describe Mastodon::CLI::Accounts do context 'when neither a list of accts nor options are provided' do it 'exits with an error message' do - expect { cli.refresh }.to output( - a_string_including('No account(s) given') - ).to_stdout + expect { cli.refresh } + .to output_results('No account(s) given') .and raise_error(SystemExit) end end end describe '#rotate' do + let(:action) { :rotate } + context 'when neither username nor --all option are given' do it 'exits with an error message' do - expect { cli.rotate }.to output( - a_string_including('No account(s) given') - ).to_stdout + expect { subject } + .to output_results('No account(s) given') .and raise_error(SystemExit) end end context 'when a username is given' do let(:account) { Fabricate(:account) } + let(:arguments) { [account.username] } it 'correctly rotates keys for the specified account' do old_private_key = account.private_key old_public_key = account.public_key - cli.rotate(account.username) + subject account.reload expect(account.private_key).to_not eq(old_private_key) @@ -960,16 +959,17 @@ describe Mastodon::CLI::Accounts do it 'broadcasts the new keys for the specified account' do allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_in) - cli.rotate(account.username) + subject expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_in).with(anything, account.id, anything).once end context 'when the given username is not found' do + let(:arguments) { ['non_existent_username'] } + it 'exits with an error message when the specified username is not found' do - expect { cli.rotate('non_existent_username') }.to output( - a_string_including('No such account') - ).to_stdout + expect { subject } + .to output_results('No such account') .and raise_error(SystemExit) end end @@ -977,17 +977,13 @@ describe Mastodon::CLI::Accounts do context 'when --all option is provided' do let!(:accounts) { Fabricate.times(2, :account) } - let(:options) { { all: true } } - - before do - cli.options = { all: true } - end + let(:options) { { all: true } } it 'correctly rotates keys for all local accounts' do old_private_keys = accounts.map(&:private_key) old_public_keys = accounts.map(&:public_key) - cli.rotate + subject accounts.each(&:reload) expect(accounts.map(&:private_key)).to_not eq(old_private_keys) @@ -997,7 +993,7 @@ describe Mastodon::CLI::Accounts do it 'broadcasts the new keys for each account' do allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_in) - cli.rotate + subject accounts.each do |account| expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_in).with(anything, account.id, anything).once @@ -1007,11 +1003,12 @@ describe Mastodon::CLI::Accounts do end describe '#merge' do + let(:action) { :merge } + shared_examples 'an account not found' do |acct| it 'exits with an error message indicating that there is no such account' do - expect { cli.invoke(:merge, arguments) }.to output( - a_string_including("No such account (#{acct})") - ).to_stdout + expect { subject } + .to output_results("No such account (#{acct})") .and raise_error(SystemExit) end end @@ -1061,9 +1058,8 @@ describe Mastodon::CLI::Accounts do end it 'exits with an error message indicating that the accounts do not have the same pub key' do - expect { cli.invoke(:merge, arguments) }.to output( - a_string_including("Accounts don't have the same public key, might not be duplicates!\nOverride with --force") - ).to_stdout + expect { subject } + .to output_results("Accounts don't have the same public key, might not be duplicates!\nOverride with --force") .and raise_error(SystemExit) end @@ -1076,13 +1072,13 @@ describe Mastodon::CLI::Accounts do end it 'merges "from_account" into "to_account"' do - cli.invoke(:merge, arguments, options) + subject expect(to_account).to have_received(:merge_with!).with(from_account).once end it 'deletes "from_account"' do - cli.invoke(:merge, arguments, options) + subject expect(from_account).to have_received(:destroy).once end @@ -1104,13 +1100,13 @@ describe Mastodon::CLI::Accounts do end it 'merges "from_account" into "to_account"' do - cli.invoke(:merge, arguments) + subject expect(to_account).to have_received(:merge_with!).with(from_account).once end it 'deletes "from_account"' do - cli.invoke(:merge, arguments) + subject expect(from_account).to have_received(:destroy) end @@ -1118,6 +1114,7 @@ describe Mastodon::CLI::Accounts do end describe '#cull' do + let(:action) { :cull } 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', 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) } @@ -1138,14 +1135,14 @@ describe Mastodon::CLI::Accounts do end it 'deletes all inactive remote accounts that longer exist in the origin server' do - cli.cull + subject 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 it 'does not delete any active remote account that still exists in the origin server' do - cli.cull + subject 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) @@ -1153,18 +1150,17 @@ describe Mastodon::CLI::Accounts do end it 'touches inactive remote accounts that have not been deleted' do - expect { cli.cull }.to(change { tales.reload.updated_at }) + expect { subject }.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 2') - ).to_stdout + expect { subject } + .to output_results('Visited 5 accounts, removed 2') end end context 'when a domain is specified' do - let(:domain) { 'example.net' } + let(:arguments) { ['example.net'] } before do stub_parallelize_with_progress! @@ -1173,16 +1169,15 @@ describe Mastodon::CLI::Accounts do end it 'deletes inactive remote accounts that longer exist in the specified domain' do - cli.cull(domain) + subject 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(domain) }.to output( - a_string_including('Visited 2 accounts, removed 2') - ).to_stdout + expect { subject } + .to output_results('Visited 2 accounts, removed 2') end end @@ -1195,15 +1190,14 @@ describe Mastodon::CLI::Accounts do end it 'skips accounts from the unavailable domain' do - cli.cull + subject expect(delete_account_service).to_not have_received(:call).with(tales, reserve_username: false) end it 'displays the summary correctly' do - expect { cli.cull }.to output( - a_string_including("Visited 5 accounts, removed 0\nThe following domains were not available during the check:\n example.net") - ).to_stdout + expect { subject } + .to output_results("Visited 5 accounts, removed 0\nThe following domains were not available during the check:\n example.net") end end @@ -1242,25 +1236,25 @@ describe Mastodon::CLI::Accounts do end describe '#reset_relationships' do + let(:action) { :reset_relationships } let(:target_account) { Fabricate(:account) } let(:arguments) { [target_account.username] } context 'when no option is given' do it 'exits with an error message indicating that at least one option is required' do - expect { cli.invoke(:reset_relationships, arguments) }.to output( - a_string_including('Please specify either --follows or --followers, or both') - ).to_stdout + expect { subject } + .to output_results('Please specify either --follows or --followers, or both') .and raise_error(SystemExit) end end context 'when the given username is not found' do let(:arguments) { ['non_existent_username'] } + let(:options) { { follows: true } } it 'exits with an error message indicating that there is no such account' do - expect { cli.invoke(:reset_relationships, arguments, follows: true) }.to output( - a_string_including('No such account') - ).to_stdout + expect { subject } + .to output_results('No such account') .and raise_error(SystemExit) end end @@ -1277,7 +1271,7 @@ describe Mastodon::CLI::Accounts do end it 'resets all "following" relationships from the target account' do - cli.invoke(:reset_relationships, arguments, options) + subject expect(target_account.reload.following).to be_empty end @@ -1285,15 +1279,14 @@ describe Mastodon::CLI::Accounts do it 'calls BootstrapTimelineWorker once to rebuild the timeline' do allow(BootstrapTimelineWorker).to receive(:perform_async) - cli.invoke(:reset_relationships, arguments, options) + subject expect(BootstrapTimelineWorker).to have_received(:perform_async).with(target_account.id).once end it 'displays a successful message' do - expect { cli.invoke(:reset_relationships, arguments, options) }.to output( - a_string_including("Processed #{total_relationships} relationships") - ).to_stdout + expect { subject } + .to output_results("Processed #{total_relationships} relationships") end end @@ -1305,15 +1298,14 @@ describe Mastodon::CLI::Accounts do end it 'resets all "followers" relationships from the target account' do - cli.invoke(:reset_relationships, arguments, options) + subject expect(target_account.reload.followers).to be_empty end it 'displays a successful message' do - expect { cli.invoke(:reset_relationships, arguments, options) }.to output( - a_string_including("Processed #{total_relationships} relationships") - ).to_stdout + expect { subject } + .to output_results("Processed #{total_relationships} relationships") end end @@ -1326,13 +1318,13 @@ describe Mastodon::CLI::Accounts do end it 'resets all "followers" relationships from the target account' do - cli.invoke(:reset_relationships, arguments, options) + subject expect(target_account.reload.followers).to be_empty end it 'resets all "following" relationships from the target account' do - cli.invoke(:reset_relationships, arguments, options) + subject expect(target_account.reload.following).to be_empty end @@ -1340,21 +1332,21 @@ describe Mastodon::CLI::Accounts do it 'calls BootstrapTimelineWorker once to rebuild the timeline' do allow(BootstrapTimelineWorker).to receive(:perform_async) - cli.invoke(:reset_relationships, arguments, options) + subject expect(BootstrapTimelineWorker).to have_received(:perform_async).with(target_account.id).once end it 'displays a successful message' do - expect { cli.invoke(:reset_relationships, arguments, options) }.to output( - a_string_including("Processed #{total_relationships} relationships") - ).to_stdout + expect { subject } + .to output_results("Processed #{total_relationships} relationships") end end end end describe '#prune' do + let(:action) { :prune } let!(:local_account) { Fabricate(:account) } let!(:bot_account) { Fabricate(:account, bot: true, domain: 'example.com') } let!(:group_account) { Fabricate(:account, actor_type: 'Group', domain: 'example.com') } @@ -1369,7 +1361,7 @@ describe Mastodon::CLI::Accounts do end it 'prunes all remote accounts with no interactions with local users' do - cli.prune + subject prunable_account_ids = prunable_accounts.pluck(:id) @@ -1377,42 +1369,39 @@ describe Mastodon::CLI::Accounts do end it 'displays a successful message' do - expect { cli.prune }.to output( - a_string_including("OK, pruned #{prunable_accounts.size} accounts") - ).to_stdout + expect { subject } + .to output_results("OK, pruned #{prunable_accounts.size} accounts") end it 'does not prune local accounts' do - cli.prune + subject expect(Account.exists?(id: local_account.id)).to be(true) end it 'does not prune bot accounts' do - cli.prune + subject expect(Account.exists?(id: bot_account.id)).to be(true) end it 'does not prune group accounts' do - cli.prune + subject expect(Account.exists?(id: group_account.id)).to be(true) end it 'does not prune accounts that have been mentioned' do - cli.prune + subject expect(Account.exists?(id: mentioned_account.id)).to be true end context 'with --dry-run option' do - before do - cli.options = { dry_run: true } - end + let(:options) { { dry_run: true } } it 'does not prune any account' do - cli.prune + subject prunable_account_ids = prunable_accounts.pluck(:id) @@ -1420,14 +1409,14 @@ describe Mastodon::CLI::Accounts do end it 'displays a successful message with (DRY RUN)' do - expect { cli.prune }.to output( - a_string_including("OK, pruned #{prunable_accounts.size} accounts (DRY RUN)") - ).to_stdout + expect { subject } + .to output_results("OK, pruned #{prunable_accounts.size} accounts (DRY RUN)") end end end describe '#migrate' do + let(:action) { :migrate } let!(:source_account) { Fabricate(:account) } let!(:target_account) { Fabricate(:account, domain: 'example.com') } let(:arguments) { [source_account.username] } @@ -1441,7 +1430,7 @@ describe Mastodon::CLI::Accounts do shared_examples 'a successful migration' do it 'calls the MoveService for the last migration' do - cli.invoke(:migrate, arguments, options) + subject last_migration = source_account.migrations.last @@ -1449,9 +1438,8 @@ describe Mastodon::CLI::Accounts do end it 'displays a successful message' do - expect { cli.invoke(:migrate, arguments, options) }.to output( - a_string_including("OK, migrated #{source_account.acct} to #{target_account.acct}") - ).to_stdout + expect { subject } + .to output_results("OK, migrated #{source_account.acct} to #{target_account.acct}") end end @@ -1459,29 +1447,27 @@ describe Mastodon::CLI::Accounts do let(:options) { { replay: true, target: "#{target_account.username}@example.com" } } it 'exits with an error message indicating that using both options is not possible' do - expect { cli.invoke(:migrate, arguments, options) }.to output( - a_string_including('Use --replay or --target, not both') - ).to_stdout + expect { subject } + .to output_results('Use --replay or --target, not both') .and raise_error(SystemExit) end end context 'when no option is given' do it 'exits with an error message indicating that at least one option must be used' do - expect { cli.invoke(:migrate, arguments, {}) }.to output( - a_string_including('Use either --replay or --target') - ).to_stdout + expect { subject } + .to output_results('Use either --replay or --target') .and raise_error(SystemExit) end end context 'when the given username is not found' do let(:arguments) { ['non_existent_username'] } + let(:options) { { replay: true } } it 'exits with an error message indicating that there is no such account' do - expect { cli.invoke(:migrate, arguments, replay: true) }.to output( - a_string_including("No such account: #{arguments.first}") - ).to_stdout + expect { subject } + .to output_results("No such account: #{arguments.first}") .and raise_error(SystemExit) end end @@ -1491,9 +1477,8 @@ describe Mastodon::CLI::Accounts do context 'when the specified account has no previous migrations' do it 'exits with an error message indicating that the given account has no previous migrations' do - expect { cli.invoke(:migrate, arguments, options) }.to output( - a_string_including('The specified account has not performed any migration') - ).to_stdout + expect { subject } + .to output_results('The specified account has not performed any migration') .and raise_error(SystemExit) end end @@ -1515,9 +1500,8 @@ describe Mastodon::CLI::Accounts do end it 'exits with an error message' do - expect { cli.invoke(:migrate, arguments, options) }.to output( - a_string_including('The specified account is not redirecting to its last migration target. Use --force if you want to replay the migration anyway') - ).to_stdout + expect { subject } + .to output_results('The specified account is not redirecting to its last migration target. Use --force if you want to replay the migration anyway') .and raise_error(SystemExit) end end @@ -1544,9 +1528,8 @@ describe Mastodon::CLI::Accounts do end it 'exits with an error message indicating that there is no such account' do - expect { cli.invoke(:migrate, arguments, options) }.to output( - a_string_including("The specified target account could not be found: #{options[:target]}") - ).to_stdout + expect { subject } + .to output_results("The specified target account could not be found: #{options[:target]}") .and raise_error(SystemExit) end end @@ -1557,7 +1540,7 @@ describe Mastodon::CLI::Accounts do end it 'creates a migration for the specified account with the target account' do - cli.invoke(:migrate, arguments, options) + subject last_migration = source_account.migrations.last @@ -1569,9 +1552,8 @@ describe Mastodon::CLI::Accounts do context 'when the migration record is invalid' do it 'exits with an error indicating that the validation failed' do - expect { cli.invoke(:migrate, arguments, options) }.to output( - a_string_including('Error: Validation failed') - ).to_stdout + expect { subject } + .to output_results('Error: Validation failed') .and raise_error(SystemExit) end end @@ -1582,9 +1564,8 @@ describe Mastodon::CLI::Accounts do end it 'exits with an error message' do - expect { cli.invoke(:migrate, arguments, options) }.to output( - a_string_including('The specified account is redirecting to a different target account. Use --force if you want to change the migration target') - ).to_stdout + expect { subject } + .to output_results('The specified account is redirecting to a different target account. Use --force if you want to change the migration target') .and raise_error(SystemExit) end end diff --git a/spec/lib/mastodon/cli/cache_spec.rb b/spec/lib/mastodon/cli/cache_spec.rb index c1ce04710c7..b1515801eb5 100644 --- a/spec/lib/mastodon/cli/cache_spec.rb +++ b/spec/lib/mastodon/cli/cache_spec.rb @@ -4,22 +4,29 @@ require 'rails_helper' require 'mastodon/cli/cache' describe Mastodon::CLI::Cache do + subject { cli.invoke(action, arguments, options) } + let(:cli) { described_class.new } + let(:arguments) { [] } + let(:options) { {} } it_behaves_like 'CLI Command' describe '#clear' do + let(:action) { :clear } + before { allow(Rails.cache).to receive(:clear) } it 'clears the Rails cache' do - expect { cli.invoke(:clear) }.to output( - a_string_including('OK') - ).to_stdout + expect { subject } + .to output_results('OK') expect(Rails.cache).to have_received(:clear) end end describe '#recount' do + let(:action) { :recount } + context 'with the `accounts` argument' do let(:arguments) { ['accounts'] } let(:account_stat) { Fabricate(:account_stat) } @@ -29,9 +36,8 @@ describe Mastodon::CLI::Cache do end it 're-calculates account records in the cache' do - expect { cli.invoke(:recount, arguments) }.to output( - a_string_including('OK') - ).to_stdout + expect { subject } + .to output_results('OK') expect(account_stat.reload.statuses_count).to be_zero end @@ -46,9 +52,8 @@ describe Mastodon::CLI::Cache do end it 're-calculates account records in the cache' do - expect { cli.invoke(:recount, arguments) }.to output( - a_string_including('OK') - ).to_stdout + expect { subject } + .to output_results('OK') expect(status_stat.reload.replies_count).to be_zero end @@ -58,9 +63,9 @@ describe Mastodon::CLI::Cache do let(:arguments) { ['other-type'] } it 'Exits with an error message' do - expect { cli.invoke(:recount, arguments) }.to output( - a_string_including('Unknown') - ).to_stdout.and raise_error(SystemExit) + expect { subject } + .to output_results('Unknown') + .and raise_error(SystemExit) end end end diff --git a/spec/lib/mastodon/cli/canonical_email_blocks_spec.rb b/spec/lib/mastodon/cli/canonical_email_blocks_spec.rb index 6e4675748e1..1745ea01bf3 100644 --- a/spec/lib/mastodon/cli/canonical_email_blocks_spec.rb +++ b/spec/lib/mastodon/cli/canonical_email_blocks_spec.rb @@ -4,42 +4,45 @@ require 'rails_helper' require 'mastodon/cli/canonical_email_blocks' describe Mastodon::CLI::CanonicalEmailBlocks do + subject { cli.invoke(action, arguments, options) } + let(:cli) { described_class.new } + let(:arguments) { [] } + let(:options) { {} } it_behaves_like 'CLI Command' describe '#find' do + let(:action) { :find } let(:arguments) { ['user@example.com'] } context 'when a block is present' do before { Fabricate(:canonical_email_block, email: 'user@example.com') } it 'announces the presence of the block' do - expect { cli.invoke(:find, arguments) }.to output( - a_string_including('user@example.com is blocked') - ).to_stdout + expect { subject } + .to output_results('user@example.com is blocked') end end context 'when a block is not present' do it 'announces the absence of the block' do - expect { cli.invoke(:find, arguments) }.to output( - a_string_including('user@example.com is not blocked') - ).to_stdout + expect { subject } + .to output_results('user@example.com is not blocked') end end end describe '#remove' do + let(:action) { :remove } let(:arguments) { ['user@example.com'] } context 'when a block is present' do before { Fabricate(:canonical_email_block, email: 'user@example.com') } it 'removes the block' do - expect { cli.invoke(:remove, arguments) }.to output( - a_string_including('Unblocked user@example.com') - ).to_stdout + expect { subject } + .to output_results('Unblocked user@example.com') expect(CanonicalEmailBlock.matching_email('user@example.com')).to be_empty end @@ -47,9 +50,8 @@ describe Mastodon::CLI::CanonicalEmailBlocks do context 'when a block is not present' do it 'announces the absence of the block' do - expect { cli.invoke(:remove, arguments) }.to output( - a_string_including('user@example.com is not blocked') - ).to_stdout + expect { subject } + .to output_results('user@example.com is not blocked') end end end diff --git a/spec/lib/mastodon/cli/domains_spec.rb b/spec/lib/mastodon/cli/domains_spec.rb index add754159ca..a10907f76e3 100644 --- a/spec/lib/mastodon/cli/domains_spec.rb +++ b/spec/lib/mastodon/cli/domains_spec.rb @@ -4,20 +4,26 @@ require 'rails_helper' require 'mastodon/cli/domains' describe Mastodon::CLI::Domains do + subject { cli.invoke(action, arguments, options) } + let(:cli) { described_class.new } + let(:arguments) { [] } + let(:options) { {} } it_behaves_like 'CLI Command' describe '#purge' do + let(:action) { :purge } + context 'with accounts from the domain' do - let(:options) { {} } let(:domain) { 'host.example' } let!(:account) { Fabricate(:account, domain: domain) } + let(:arguments) { [domain] } it 'removes the account' do - expect { cli.invoke(:purge, [domain], options) }.to output( - a_string_including('Removed 1 accounts') - ).to_stdout + expect { subject } + .to output_results('Removed 1 accounts') + expect { account.reload }.to raise_error(ActiveRecord::RecordNotFound) end end diff --git a/spec/lib/mastodon/cli/email_domain_blocks_spec.rb b/spec/lib/mastodon/cli/email_domain_blocks_spec.rb index f5cb6c332b7..13deb05b6cf 100644 --- a/spec/lib/mastodon/cli/email_domain_blocks_spec.rb +++ b/spec/lib/mastodon/cli/email_domain_blocks_spec.rb @@ -4,96 +4,99 @@ require 'rails_helper' require 'mastodon/cli/email_domain_blocks' describe Mastodon::CLI::EmailDomainBlocks do + subject { cli.invoke(action, arguments, options) } + let(:cli) { described_class.new } + let(:arguments) { [] } + let(:options) { {} } it_behaves_like 'CLI Command' describe '#list' do + let(:action) { :list } + context 'with email domain block records' do let!(:parent_block) { Fabricate(:email_domain_block) } let!(:child_block) { Fabricate(:email_domain_block, parent: parent_block) } - let(:options) { {} } it 'lists the blocks' do - expect { cli.invoke(:list, [], options) }.to output( - a_string_including(parent_block.domain) - .and(a_string_including(child_block.domain)) - ).to_stdout + expect { subject } + .to output_results( + parent_block.domain, + child_block.domain + ) end end end describe '#add' do - context 'without any options' do - let(:options) { {} } + let(:action) { :add } + context 'without any options' do it 'warns about usage and exits' do - expect { cli.invoke(:add, [], options) }.to output( - a_string_including('No domain(s) given') - ).to_stdout.and raise_error(SystemExit) + expect { subject } + .to output_results('No domain(s) given') + .and raise_error(SystemExit) end end context 'when blocks exist' do let(:options) { {} } let(:domain) { 'host.example' } + let(:arguments) { [domain] } before { Fabricate(:email_domain_block, domain: domain) } it 'does not add a new block' do - expect { cli.invoke(:add, [domain], options) }.to output( - a_string_including('is already blocked') - ).to_stdout + expect { subject } + .to output_results('is already blocked') .and(not_change(EmailDomainBlock, :count)) end end context 'when no blocks exist' do - let(:options) { {} } let(:domain) { 'host.example' } + let(:arguments) { [domain] } it 'adds a new block' do - expect { cli.invoke(:add, [domain], options) }.to output( - a_string_including('Added 1') - ).to_stdout + expect { subject } + .to output_results('Added 1') .and(change(EmailDomainBlock, :count).by(1)) end end end describe '#remove' do - context 'without any options' do - let(:options) { {} } + let(:action) { :remove } + context 'without any options' do it 'warns about usage and exits' do - expect { cli.invoke(:remove, [], options) }.to output( - a_string_including('No domain(s) given') - ).to_stdout.and raise_error(SystemExit) + expect { subject } + .to output_results('No domain(s) given') + .and raise_error(SystemExit) end end context 'when blocks exist' do - let(:options) { {} } let(:domain) { 'host.example' } + let(:arguments) { [domain] } before { Fabricate(:email_domain_block, domain: domain) } it 'removes the block' do - expect { cli.invoke(:remove, [domain], options) }.to output( - a_string_including('Removed 1') - ).to_stdout + expect { subject } + .to output_results('Removed 1') .and(change(EmailDomainBlock, :count).by(-1)) end end context 'when no blocks exist' do - let(:options) { {} } let(:domain) { 'host.example' } + let(:arguments) { [domain] } it 'does not remove a block' do - expect { cli.invoke(:remove, [domain], options) }.to output( - a_string_including('is not yet blocked') - ).to_stdout + expect { subject } + .to output_results('is not yet blocked') .and(not_change(EmailDomainBlock, :count)) end end diff --git a/spec/lib/mastodon/cli/emoji_spec.rb b/spec/lib/mastodon/cli/emoji_spec.rb index 3441413b90e..d05e972e77c 100644 --- a/spec/lib/mastodon/cli/emoji_spec.rb +++ b/spec/lib/mastodon/cli/emoji_spec.rb @@ -4,10 +4,10 @@ require 'rails_helper' require 'mastodon/cli/emoji' describe Mastodon::CLI::Emoji do - subject { cli.invoke(action, args, options) } + subject { cli.invoke(action, arguments, options) } let(:cli) { described_class.new } - let(:args) { [] } + let(:arguments) { [] } let(:options) { {} } it_behaves_like 'CLI Command' @@ -29,7 +29,7 @@ describe Mastodon::CLI::Emoji do context 'with existing custom emoji' do let(:import_path) { Rails.root.join('spec', 'fixtures', 'files', 'elite-assets.tar.gz') } let(:action) { :import } - let(:args) { [import_path] } + let(:arguments) { [import_path] } it 'reports about imported emoji' do expect { subject } @@ -51,7 +51,7 @@ describe Mastodon::CLI::Emoji do after { FileUtils.rm_rf(export_path.dirname) } let(:export_path) { Rails.root.join('tmp', 'cli-tests', 'export.tar.gz') } - let(:args) { [export_path.dirname.to_s] } + let(:arguments) { [export_path.dirname.to_s] } let(:action) { :export } it 'reports about exported emoji' do @@ -61,8 +61,4 @@ describe Mastodon::CLI::Emoji do end end end - - def output_results(string) - output(a_string_including(string)).to_stdout - end end diff --git a/spec/lib/mastodon/cli/feeds_spec.rb b/spec/lib/mastodon/cli/feeds_spec.rb index e16113c8546..19979805270 100644 --- a/spec/lib/mastodon/cli/feeds_spec.rb +++ b/spec/lib/mastodon/cli/feeds_spec.rb @@ -4,20 +4,25 @@ require 'rails_helper' require 'mastodon/cli/feeds' describe Mastodon::CLI::Feeds do + subject { cli.invoke(action, arguments, options) } + let(:cli) { described_class.new } + let(:arguments) { [] } + let(:options) { {} } it_behaves_like 'CLI Command' describe '#build' do + let(:action) { :build } + before { Fabricate(:account) } context 'with --all option' do let(:options) { { all: true } } it 'regenerates feeds for all accounts' do - expect { cli.invoke(:build, [], options) }.to output( - a_string_including('Regenerated feeds') - ).to_stdout + expect { subject } + .to output_results('Regenerated feeds') end end @@ -27,9 +32,8 @@ describe Mastodon::CLI::Feeds do let(:arguments) { ['alice'] } it 'regenerates feeds for the account' do - expect { cli.invoke(:build, arguments) }.to output( - a_string_including('OK') - ).to_stdout + expect { subject } + .to output_results('OK') end end @@ -37,22 +41,23 @@ describe Mastodon::CLI::Feeds do let(:arguments) { ['invalid-username'] } it 'displays an error and exits' do - expect { cli.invoke(:build, arguments) }.to output( - a_string_including('No such account') - ).to_stdout.and raise_error(SystemExit) + expect { subject } + .to output_results('No such account') + .and raise_error(SystemExit) end end end describe '#clear' do + let(:action) { :clear } + before do allow(redis).to receive(:del).with(key_namespace) end it 'clears the redis `feed:*` namespace' do - expect { cli.invoke(:clear) }.to output( - a_string_including('OK') - ).to_stdout + expect { subject } + .to output_results('OK') expect(redis).to have_received(:del).with(key_namespace).once end diff --git a/spec/lib/mastodon/cli/ip_blocks_spec.rb b/spec/lib/mastodon/cli/ip_blocks_spec.rb index 684314dc7ad..dc967a69c97 100644 --- a/spec/lib/mastodon/cli/ip_blocks_spec.rb +++ b/spec/lib/mastodon/cli/ip_blocks_spec.rb @@ -4,11 +4,16 @@ require 'rails_helper' require 'mastodon/cli/ip_blocks' describe Mastodon::CLI::IpBlocks do + subject { cli.invoke(action, arguments, options) } + let(:cli) { described_class.new } + let(:arguments) { [] } + let(:options) { {} } it_behaves_like 'CLI Command' describe '#add' do + let(:action) { :add } let(:ip_list) do [ '192.0.2.1', @@ -25,10 +30,11 @@ describe Mastodon::CLI::IpBlocks do ] end let(:options) { { severity: 'no_access' } } + let(:arguments) { ip_list } shared_examples 'ip address blocking' do it 'blocks all specified IP addresses' do - cli.invoke(:add, ip_list, options) + subject blocked_ip_addresses = IpBlock.where(ip: ip_list).pluck(:ip) expected_ip_addresses = ip_list.map { |ip| IPAddr.new(ip) } @@ -37,7 +43,7 @@ describe Mastodon::CLI::IpBlocks do end it 'sets the severity for all blocked IP addresses' do - cli.invoke(:add, ip_list, options) + subject blocked_ips_severity = IpBlock.where(ip: ip_list).pluck(:severity).all?(options[:severity]) @@ -45,9 +51,8 @@ describe Mastodon::CLI::IpBlocks do end it 'displays a success message with a summary' do - expect { cli.invoke(:add, ip_list, options) }.to output( - a_string_including("Added #{ip_list.size}, skipped 0, failed 0") - ).to_stdout + expect { subject } + .to output_results("Added #{ip_list.size}, skipped 0, failed 0") end end @@ -57,19 +62,19 @@ describe Mastodon::CLI::IpBlocks do context 'when a specified IP address is already blocked' do let!(:blocked_ip) { IpBlock.create(ip: ip_list.last, severity: options[:severity]) } + let(:arguments) { ip_list } it 'skips the already blocked IP address' do allow(IpBlock).to receive(:new).and_call_original - cli.invoke(:add, ip_list, options) + subject expect(IpBlock).to_not have_received(:new).with(ip: ip_list.last) end it 'displays the correct summary' do - expect { cli.invoke(:add, ip_list, options) }.to output( - a_string_including("#{ip_list.last} is already blocked\nAdded #{ip_list.size - 1}, skipped 1, failed 0") - ).to_stdout + expect { subject } + .to output_results("#{ip_list.last} is already blocked\nAdded #{ip_list.size - 1}, skipped 1, failed 0") end context 'with --force option' do @@ -77,7 +82,7 @@ describe Mastodon::CLI::IpBlocks do let(:options) { { severity: 'sign_up_requires_approval', force: true } } it 'overwrites the existing IP block record' do - expect { cli.invoke(:add, ip_list, options) } + expect { subject } .to change { blocked_ip.reload.severity } .from('no_access') .to('sign_up_requires_approval') @@ -89,11 +94,11 @@ describe Mastodon::CLI::IpBlocks do context 'when a specified IP address is invalid' do let(:ip_list) { ['320.15.175.0', '9.5.105.255', '0.0.0.0'] } + let(:arguments) { ip_list } it 'displays the correct summary' do - expect { cli.invoke(:add, ip_list, options) }.to output( - a_string_including("#{ip_list.first} is invalid\nAdded #{ip_list.size - 1}, skipped 0, failed 1") - ).to_stdout + expect { subject } + .to output_results("#{ip_list.first} is invalid\nAdded #{ip_list.size - 1}, skipped 0, failed 1") end end @@ -124,6 +129,7 @@ describe Mastodon::CLI::IpBlocks do context 'when a specified IP address fails to be blocked' do let(:ip_address) { '127.0.0.1' } let(:ip_block) { instance_double(IpBlock, ip: ip_address, save: false) } + let(:arguments) { [ip_address] } before do allow(IpBlock).to receive(:new).and_return(ip_block) @@ -132,24 +138,25 @@ describe Mastodon::CLI::IpBlocks do end it 'displays an error message' do - expect { cli.invoke(:add, [ip_address], options) } - .to output( - a_string_including("#{ip_address} could not be saved") - ).to_stdout + expect { subject } + .to output_results("#{ip_address} could not be saved") end end context 'when no IP address is provided' do + let(:arguments) { [] } + it 'exits with an error message' do - expect { cli.add }.to output( - a_string_including('No IP(s) given') - ).to_stdout + expect { subject } + .to output_results('No IP(s) given') .and raise_error(SystemExit) end end end describe '#remove' do + let(:action) { :remove } + context 'when removing exact matches' do let(:ip_list) do [ @@ -166,21 +173,21 @@ describe Mastodon::CLI::IpBlocks do '::/128', ] end + let(:arguments) { ip_list } before do ip_list.each { |ip| IpBlock.create(ip: ip, severity: :no_access) } end it 'removes exact IP blocks' do - cli.invoke(:remove, ip_list) + subject expect(IpBlock.where(ip: ip_list)).to_not exist end it 'displays success message with a summary' do - expect { cli.invoke(:remove, ip_list) }.to output( - a_string_including("Removed #{ip_list.size}, skipped 0") - ).to_stdout + expect { subject } + .to output_results("Removed #{ip_list.size}, skipped 0") end end @@ -192,13 +199,13 @@ describe Mastodon::CLI::IpBlocks do let(:options) { { force: true } } it 'removes blocks for IP ranges that cover given IP(s)' do - cli.invoke(:remove, arguments, options) + subject expect(IpBlock.where(id: [first_ip_range_block.id, second_ip_range_block.id])).to_not exist end it 'does not remove other IP ranges' do - cli.invoke(:remove, arguments, options) + subject expect(IpBlock.where(id: third_ip_range_block.id)).to exist end @@ -206,47 +213,46 @@ describe Mastodon::CLI::IpBlocks do context 'when a specified IP address is not blocked' do let(:unblocked_ip) { '192.0.2.1' } + let(:arguments) { [unblocked_ip] } it 'skips the IP address' do - expect { cli.invoke(:remove, [unblocked_ip]) }.to output( - a_string_including("#{unblocked_ip} is not yet blocked") - ).to_stdout + expect { subject } + .to output_results("#{unblocked_ip} is not yet blocked") end it 'displays the summary correctly' do - expect { cli.invoke(:remove, [unblocked_ip]) }.to output( - a_string_including('Removed 0, skipped 1') - ).to_stdout + expect { subject } + .to output_results('Removed 0, skipped 1') end end context 'when a specified IP address is invalid' do let(:invalid_ip) { '320.15.175.0' } + let(:arguments) { [invalid_ip] } it 'skips the invalid IP address' do - expect { cli.invoke(:remove, [invalid_ip]) }.to output( - a_string_including("#{invalid_ip} is invalid") - ).to_stdout + expect { subject } + .to output_results("#{invalid_ip} is invalid") end it 'displays the summary correctly' do - expect { cli.invoke(:remove, [invalid_ip]) }.to output( - a_string_including('Removed 0, skipped 1') - ).to_stdout + expect { subject } + .to output_results('Removed 0, skipped 1') end end context 'when no IP address is provided' do it 'exits with an error message' do - expect { cli.remove }.to output( - a_string_including('No IP(s) given') - ).to_stdout + expect { subject } + .to output_results('No IP(s) given') .and raise_error(SystemExit) end end end describe '#export' do + let(:action) { :export } + let(:first_ip_range_block) { IpBlock.create(ip: '192.168.0.0/24', severity: :no_access) } let(:second_ip_range_block) { IpBlock.create(ip: '10.0.0.0/16', severity: :no_access) } let(:third_ip_range_block) { IpBlock.create(ip: '127.0.0.1', severity: :sign_up_block) } @@ -255,15 +261,13 @@ describe Mastodon::CLI::IpBlocks do let(:options) { { format: 'plain' } } it 'exports blocked IPs with "no_access" severity in plain format' do - expect { cli.invoke(:export, nil, options) }.to output( - a_string_including("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}") - ).to_stdout + expect { subject } + .to output_results("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}") end it 'does not export bloked IPs with different severities' do - expect { cli.invoke(:export, nil, options) }.to_not output( - a_string_including("#{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}") - ).to_stdout + expect { subject } + .to_not output_results("#{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}") end end @@ -271,23 +275,20 @@ describe Mastodon::CLI::IpBlocks do let(:options) { { format: 'nginx' } } it 'exports blocked IPs with "no_access" severity in plain format' do - expect { cli.invoke(:export, nil, options) }.to output( - a_string_including("deny #{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};\ndeny #{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix};") - ).to_stdout + expect { subject } + .to output_results("deny #{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};\ndeny #{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix};") end it 'does not export bloked IPs with different severities' do - expect { cli.invoke(:export, nil, options) }.to_not output( - a_string_including("deny #{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};") - ).to_stdout + expect { subject } + .to_not output_results("deny #{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};") end end context 'when --format option is not provided' do it 'exports blocked IPs in plain format by default' do - expect { cli.export }.to output( - a_string_including("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}") - ).to_stdout + expect { subject } + .to output_results("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}") end end end diff --git a/spec/lib/mastodon/cli/main_spec.rb b/spec/lib/mastodon/cli/main_spec.rb index b5b5d690621..59f1fc47841 100644 --- a/spec/lib/mastodon/cli/main_spec.rb +++ b/spec/lib/mastodon/cli/main_spec.rb @@ -4,13 +4,20 @@ require 'rails_helper' require 'mastodon/cli/main' describe Mastodon::CLI::Main do + subject { cli.invoke(action, arguments, options) } + + let(:cli) { described_class.new } + let(:arguments) { [] } + let(:options) { {} } + it_behaves_like 'CLI Command' - describe 'version' do + describe '#version' do + let(:action) { :version } + it 'returns the Mastodon version' do - expect { described_class.new.invoke(:version) }.to output( - a_string_including(Mastodon::Version.to_s) - ).to_stdout + expect { subject } + .to output_results(Mastodon::Version.to_s) end end end diff --git a/spec/lib/mastodon/cli/maintenance_spec.rb b/spec/lib/mastodon/cli/maintenance_spec.rb index 95e695ab559..02169b7a42a 100644 --- a/spec/lib/mastodon/cli/maintenance_spec.rb +++ b/spec/lib/mastodon/cli/maintenance_spec.rb @@ -4,20 +4,26 @@ require 'rails_helper' require 'mastodon/cli/maintenance' describe Mastodon::CLI::Maintenance do + subject { cli.invoke(action, arguments, options) } + let(:cli) { described_class.new } + let(:arguments) { [] } + let(:options) { {} } it_behaves_like 'CLI Command' describe '#fix_duplicates' do + let(:action) { :fix_duplicates } + context 'when the database version is too old' do before do allow(ActiveRecord::Migrator).to receive(:current_version).and_return(2000_01_01_000000) # Earlier than minimum end it 'Exits with error message' do - expect { cli.invoke :fix_duplicates }.to output( - a_string_including('is too old') - ).to_stdout.and raise_error(SystemExit) + expect { subject } + .to output_results('is too old') + .and raise_error(SystemExit) end end @@ -28,9 +34,9 @@ describe Mastodon::CLI::Maintenance do end it 'Exits with error message' do - expect { cli.invoke :fix_duplicates }.to output( - a_string_including('more recent') - ).to_stdout.and raise_error(SystemExit) + expect { subject } + .to output_results('more recent') + .and raise_error(SystemExit) end end @@ -41,9 +47,9 @@ describe Mastodon::CLI::Maintenance do end it 'Exits with error message' do - expect { cli.invoke :fix_duplicates }.to output( - a_string_including('Sidekiq is running') - ).to_stdout.and raise_error(SystemExit) + expect { subject } + .to output_results('Sidekiq is running') + .and raise_error(SystemExit) end end end diff --git a/spec/lib/mastodon/cli/media_spec.rb b/spec/lib/mastodon/cli/media_spec.rb index 6d510c1f5a4..6bbe7e74692 100644 --- a/spec/lib/mastodon/cli/media_spec.rb +++ b/spec/lib/mastodon/cli/media_spec.rb @@ -4,18 +4,24 @@ require 'rails_helper' require 'mastodon/cli/media' describe Mastodon::CLI::Media do + subject { cli.invoke(action, arguments, options) } + let(:cli) { described_class.new } + let(:arguments) { [] } + let(:options) { {} } it_behaves_like 'CLI Command' describe '#remove' do + let(:action) { :remove } + context 'with --prune-profiles and --remove-headers' do let(:options) { { prune_profiles: true, remove_headers: true } } it 'warns about usage and exits' do - expect { cli.invoke(:remove, [], options) }.to output( - a_string_including('--prune-profiles and --remove-headers should not be specified simultaneously') - ).to_stdout.and raise_error(SystemExit) + expect { subject } + .to output_results('--prune-profiles and --remove-headers should not be specified simultaneously') + .and raise_error(SystemExit) end end @@ -23,9 +29,9 @@ describe Mastodon::CLI::Media do let(:options) { { include_follows: true } } it 'warns about usage and exits' do - expect { cli.invoke(:remove, [], options) }.to output( - a_string_including('--include-follows can only be used with --prune-profiles or --remove-headers') - ).to_stdout.and raise_error(SystemExit) + expect { subject } + .to output_results('--include-follows can only be used with --prune-profiles or --remove-headers') + .and raise_error(SystemExit) end end @@ -38,9 +44,8 @@ describe Mastodon::CLI::Media do let(:options) { { prune_profiles: true } } it 'removes account avatars' do - expect { cli.invoke(:remove, [], options) }.to output( - a_string_including('Visited 1') - ).to_stdout + expect { subject } + .to output_results('Visited 1') expect(account.reload.avatar).to be_blank end @@ -50,9 +55,8 @@ describe Mastodon::CLI::Media do let(:options) { { remove_headers: true } } it 'removes account header' do - expect { cli.invoke(:remove, [], options) }.to output( - a_string_including('Visited 1') - ).to_stdout + expect { subject } + .to output_results('Visited 1') expect(account.reload.header).to be_blank end @@ -64,9 +68,8 @@ describe Mastodon::CLI::Media do context 'without options' do it 'removes account avatars' do - expect { cli.invoke(:remove) }.to output( - a_string_including('Removed 1') - ).to_stdout + expect { subject } + .to output_results('Removed 1') expect(media_attachment.reload.file).to be_blank expect(media_attachment.reload.thumbnail).to be_blank @@ -76,25 +79,24 @@ describe Mastodon::CLI::Media do end describe '#usage' do - context 'without options' do - let(:options) { {} } + let(:action) { :usage } + context 'without options' do it 'reports about storage size' do - expect { cli.invoke(:usage, [], options) }.to output( - a_string_including('0 Bytes') - ).to_stdout + expect { subject } + .to output_results('0 Bytes') end end end describe '#refresh' do - context 'without any options' do - let(:options) { {} } + let(:action) { :refresh } + context 'without any options' do it 'warns about usage and exits' do - expect { cli.invoke(:refresh, [], options) }.to output( - a_string_including('Specify the source') - ).to_stdout.and raise_error(SystemExit) + expect { subject } + .to output_results('Specify the source') + .and raise_error(SystemExit) end end @@ -108,9 +110,8 @@ describe Mastodon::CLI::Media do let(:status) { Fabricate(:status) } it 'redownloads the attachment file' do - expect { cli.invoke(:refresh, [], options) }.to output( - a_string_including('Downloaded 1 media') - ).to_stdout + expect { subject } + .to output_results('Downloaded 1 media') end end @@ -119,9 +120,9 @@ describe Mastodon::CLI::Media do let(:options) { { account: 'not-real-user@example.host' } } it 'warns about usage and exits' do - expect { cli.invoke(:refresh, [], options) }.to output( - a_string_including('No such account') - ).to_stdout.and raise_error(SystemExit) + expect { subject } + .to output_results('No such account') + .and raise_error(SystemExit) end end @@ -135,9 +136,8 @@ describe Mastodon::CLI::Media do let(:account) { Fabricate(:account) } it 'redownloads the attachment file' do - expect { cli.invoke(:refresh, [], options) }.to output( - a_string_including('Downloaded 1 media') - ).to_stdout + expect { subject } + .to output_results('Downloaded 1 media') end end end @@ -153,9 +153,8 @@ describe Mastodon::CLI::Media do let(:account) { Fabricate(:account, domain: domain) } it 'redownloads the attachment file' do - expect { cli.invoke(:refresh, [], options) }.to output( - a_string_including('Downloaded 1 media') - ).to_stdout + expect { subject } + .to output_results('Downloaded 1 media') end end end diff --git a/spec/lib/mastodon/cli/preview_cards_spec.rb b/spec/lib/mastodon/cli/preview_cards_spec.rb index a766d250eb7..951ae3758f0 100644 --- a/spec/lib/mastodon/cli/preview_cards_spec.rb +++ b/spec/lib/mastodon/cli/preview_cards_spec.rb @@ -4,11 +4,17 @@ require 'rails_helper' require 'mastodon/cli/preview_cards' describe Mastodon::CLI::PreviewCards do + subject { cli.invoke(action, arguments, options) } + let(:cli) { described_class.new } + let(:arguments) { [] } + let(:options) { {} } it_behaves_like 'CLI Command' describe '#remove' do + let(:action) { :remove } + context 'with relevant preview cards' do before do Fabricate(:preview_card, updated_at: 10.years.ago, type: :link) @@ -18,10 +24,11 @@ describe Mastodon::CLI::PreviewCards do 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 + expect { subject } + .to output_results( + 'Removed 2 preview cards', + 'approx. 119 KB' + ) end end @@ -29,10 +36,11 @@ describe Mastodon::CLI::PreviewCards 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 + expect { subject } + .to output_results( + 'Removed 1 link-type preview cards', + 'approx. 59.6 KB' + ) end end @@ -40,10 +48,11 @@ describe Mastodon::CLI::PreviewCards 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 + expect { subject } + .to output_results( + 'Removed 1 preview cards', + 'approx. 59.6 KB' + ) end end end diff --git a/spec/lib/mastodon/cli/settings_spec.rb b/spec/lib/mastodon/cli/settings_spec.rb index 7dcd1110bae..02d1042c567 100644 --- a/spec/lib/mastodon/cli/settings_spec.rb +++ b/spec/lib/mastodon/cli/settings_spec.rb @@ -7,59 +7,64 @@ describe Mastodon::CLI::Settings do it_behaves_like 'CLI Command' describe 'subcommand "registrations"' do + subject { cli.invoke(action, arguments, options) } + let(:cli) { Mastodon::CLI::Registrations.new } + let(:arguments) { [] } + let(:options) { {} } before do Setting.registrations_mode = nil end describe '#open' do + let(:action) { :open } + it 'changes "registrations_mode" to "open"' do - expect { cli.open }.to change(Setting, :registrations_mode).from(nil).to('open') + expect { subject }.to change(Setting, :registrations_mode).from(nil).to('open') end it 'displays success message' do - expect { cli.open }.to output( - a_string_including('OK') - ).to_stdout + expect { subject } + .to output_results('OK') end end describe '#approved' do + let(:action) { :approved } + it 'changes "registrations_mode" to "approved"' do - expect { cli.approved }.to change(Setting, :registrations_mode).from(nil).to('approved') + expect { subject }.to change(Setting, :registrations_mode).from(nil).to('approved') end it 'displays success message' do - expect { cli.approved }.to output( - a_string_including('OK') - ).to_stdout + expect { subject } + .to output_results('OK') end context 'with --require-reason' do - before do - cli.options = { require_reason: true } - end + let(:options) { { require_reason: true } } it 'changes "registrations_mode" to "approved"' do - expect { cli.approved }.to change(Setting, :registrations_mode).from(nil).to('approved') + expect { subject }.to change(Setting, :registrations_mode).from(nil).to('approved') end it 'sets "require_invite_text" to "true"' do - expect { cli.approved }.to change(Setting, :require_invite_text).from(false).to(true) + expect { subject }.to change(Setting, :require_invite_text).from(false).to(true) end end end describe '#close' do + let(:action) { :close } + it 'changes "registrations_mode" to "none"' do - expect { cli.close }.to change(Setting, :registrations_mode).from(nil).to('none') + expect { subject }.to change(Setting, :registrations_mode).from(nil).to('none') end it 'displays success message' do - expect { cli.close }.to output( - a_string_including('OK') - ).to_stdout + expect { subject } + .to output_results('OK') end end end diff --git a/spec/lib/mastodon/cli/statuses_spec.rb b/spec/lib/mastodon/cli/statuses_spec.rb index 70e4e2c086d..63d494bbb65 100644 --- a/spec/lib/mastodon/cli/statuses_spec.rb +++ b/spec/lib/mastodon/cli/statuses_spec.rb @@ -4,26 +4,31 @@ require 'rails_helper' require 'mastodon/cli/statuses' describe Mastodon::CLI::Statuses do + subject { cli.invoke(action, arguments, options) } + let(:cli) { described_class.new } + let(:arguments) { [] } + let(:options) { {} } it_behaves_like 'CLI Command' describe '#remove', use_transactional_tests: false do + let(:action) { :remove } + context 'with small batch size' do let(:options) { { batch_size: 0 } } it 'exits with error message' do - expect { cli.invoke :remove, [], options }.to output( - a_string_including('Cannot run') - ).to_stdout.and raise_error(SystemExit) + expect { subject } + .to output_results('Cannot run') + .and raise_error(SystemExit) end end context 'with default batch size' do it 'removes unreferenced statuses' do - expect { cli.invoke :remove }.to output( - a_string_including('Done after') - ).to_stdout + expect { subject } + .to output_results('Done after') end end end diff --git a/spec/lib/mastodon/cli/upgrade_spec.rb b/spec/lib/mastodon/cli/upgrade_spec.rb index 0d6494eeeee..6861e048876 100644 --- a/spec/lib/mastodon/cli/upgrade_spec.rb +++ b/spec/lib/mastodon/cli/upgrade_spec.rb @@ -4,23 +4,26 @@ require 'rails_helper' require 'mastodon/cli/upgrade' describe Mastodon::CLI::Upgrade do + subject { cli.invoke(action, arguments, options) } + let(:cli) { described_class.new } + let(:arguments) { [] } + let(:options) { {} } it_behaves_like 'CLI Command' describe '#storage_schema' do - context 'with records that dont need upgrading' do - let(:options) { {} } + let(:action) { :storage_schema } + context 'with records that dont need upgrading' do before do Fabricate(:account) Fabricate(:media_attachment) end it 'does not upgrade storage for the attachments' do - expect { cli.invoke(:storage_schema, [], options) }.to output( - a_string_including('Upgraded storage schema of 0 records') - ).to_stdout + expect { subject } + .to output_results('Upgraded storage schema of 0 records') end end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index d30e7201c4e..4394b470e6e 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -88,6 +88,7 @@ RSpec.configure do |config| config.include Chewy::Rspec::Helpers config.include Redisable config.include SignedRequestHelpers, type: :request + config.include CommandLineHelpers, type: :cli config.around(:each, use_transactional_tests: false) do |example| self.use_transactional_tests = false diff --git a/spec/support/command_line_helpers.rb b/spec/support/command_line_helpers.rb new file mode 100644 index 00000000000..6f9d63d9390 --- /dev/null +++ b/spec/support/command_line_helpers.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module CommandLineHelpers + def output_results(*args) + output( + include(*args) + ).to_stdout + end +end