Merge tag 'v2.8.0' into instance_only_statuses

This commit is contained in:
Renato "Lond" Cerqueira
2019-04-13 23:47:24 +02:00
689 changed files with 25483 additions and 9047 deletions

View File

@ -3,7 +3,7 @@ require 'rails_helper'
RSpec.describe AccountsController, type: :controller do
render_views
let(:alice) { Fabricate(:account, username: 'alice') }
let(:alice) { Fabricate(:account, username: 'alice', user: Fabricate(:user)) }
let(:eve) { Fabricate(:user) }
describe 'GET #show' do

View File

@ -10,7 +10,7 @@ RSpec.describe ActivityPub::InboxesController, type: :controller do
Fabricate(:account)
end
post :create
post :create, body: '{}'
expect(response).to have_http_status(202)
end
end
@ -21,7 +21,7 @@ RSpec.describe ActivityPub::InboxesController, type: :controller do
false
end
post :create
post :create, body: '{}'
expect(response).to have_http_status(401)
end
end

View File

@ -19,6 +19,10 @@ RSpec.describe Admin::SettingsController, type: :controller do
end
describe 'PUT #update' do
before do
allow_any_instance_of(Form::AdminSettings).to receive(:valid?).and_return(true)
end
describe 'for a record that doesnt exist' do
around do |example|
before = Setting.site_extended_description
@ -62,22 +66,6 @@ RSpec.describe Admin::SettingsController, type: :controller do
expect(Setting.site_title).to eq 'New title'
end
end
context do
around do |example|
open_registrations = Setting.open_registrations
example.run
Setting.open_registrations = open_registrations
end
it 'typecasts open_registrations to boolean' do
Setting.open_registrations = false
patch :update, params: { form_admin_settings: { open_registrations: '1' } }
expect(response).to redirect_to(edit_admin_settings_path)
expect(Setting.open_registrations).to eq true
end
end
end
end
end

View File

@ -0,0 +1,96 @@
require 'rails_helper'
describe Api::ProofsController do
let(:alice) { Fabricate(:account, username: 'alice') }
before do
stub_request(:get, 'https://keybase.io/_/api/1.0/sig/proof_valid.json?domain=cb6e6126.ngrok.io&kb_username=crypto_alice&sig_hash=111111111111111111111111111111111111111111111111111111111111111111&username=alice').to_return(status: 200, body: '{"proof_valid":true,"proof_live":false}')
stub_request(:get, 'https://keybase.io/_/api/1.0/sig/proof_live.json?domain=cb6e6126.ngrok.io&kb_username=crypto_alice&sig_hash=111111111111111111111111111111111111111111111111111111111111111111&username=alice').to_return(status: 200, body: '{"proof_valid":true,"proof_live":true}')
stub_request(:get, 'https://keybase.io/_/api/1.0/sig/proof_valid.json?domain=cb6e6126.ngrok.io&kb_username=hidden_alice&sig_hash=222222222222222222222222222222222222222222222222222222222222222222&username=alice').to_return(status: 200, body: '{"proof_valid":true,"proof_live":true}')
stub_request(:get, 'https://keybase.io/_/api/1.0/sig/proof_live.json?domain=cb6e6126.ngrok.io&kb_username=hidden_alice&sig_hash=222222222222222222222222222222222222222222222222222222222222222222&username=alice').to_return(status: 200, body: '{"proof_valid":true,"proof_live":true}')
end
describe 'GET #index' do
describe 'with a non-existent username' do
it '404s' do
get :index, params: { username: 'nonexistent', provider: 'keybase' }
expect(response).to have_http_status(:not_found)
end
end
describe 'with a user that has no proofs' do
it 'is an empty list of signatures' do
get :index, params: { username: alice.username, provider: 'keybase' }
expect(body_as_json[:signatures]).to eq []
end
end
describe 'with a user that has a live, valid proof' do
let(:token1) { '111111111111111111111111111111111111111111111111111111111111111111' }
let(:kb_name1) { 'crypto_alice' }
before do
Fabricate(:account_identity_proof, account: alice, verified: true, live: true, token: token1, provider_username: kb_name1)
end
it 'is a list with that proof in it' do
get :index, params: { username: alice.username, provider: 'keybase' }
expect(body_as_json[:signatures]).to eq [
{ kb_username: kb_name1, sig_hash: token1 },
]
end
describe 'add one that is neither live nor valid' do
let(:token2) { '222222222222222222222222222222222222222222222222222222222222222222' }
let(:kb_name2) { 'hidden_alice' }
before do
Fabricate(:account_identity_proof, account: alice, verified: false, live: false, token: token2, provider_username: kb_name2)
end
it 'is a list with both proofs' do
get :index, params: { username: alice.username, provider: 'keybase' }
expect(body_as_json[:signatures]).to eq [
{ kb_username: kb_name1, sig_hash: token1 },
{ kb_username: kb_name2, sig_hash: token2 },
]
end
end
end
describe 'a user that has an avatar' do
let(:alice) { Fabricate(:account, username: 'alice', avatar: attachment_fixture('avatar.gif')) }
context 'and a proof' do
let(:token1) { '111111111111111111111111111111111111111111111111111111111111111111' }
let(:kb_name1) { 'crypto_alice' }
before do
Fabricate(:account_identity_proof, account: alice, verified: true, live: true, token: token1, provider_username: kb_name1)
get :index, params: { username: alice.username, provider: 'keybase' }
end
it 'has two keys: signatures and avatar' do
expect(body_as_json.keys).to match_array [:signatures, :avatar]
end
it 'has the correct signatures' do
expect(body_as_json[:signatures]).to eq [
{ kb_username: kb_name1, sig_hash: token1 },
]
end
it 'has the correct avatar url' do
first_part = 'https://cb6e6126.ngrok.io/system/accounts/avatars/'
last_part = 'original/avatar.gif'
expect(body_as_json[:avatar]).to match /#{Regexp.quote(first_part)}(?:\d{3,5}\/){3}#{Regexp.quote(last_part)}/
end
end
end
end
end

View File

@ -0,0 +1,34 @@
require 'rails_helper'
RSpec.describe Api::V1::Polls::VotesController, type: :controller do
render_views
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
let(:scopes) { 'write:statuses' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
before { allow(controller).to receive(:doorkeeper_token) { token } }
describe 'POST #create' do
let(:poll) { Fabricate(:poll) }
before do
post :create, params: { poll_id: poll.id, choices: %w(1) }
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'creates a vote' do
vote = poll.votes.where(account: user.account).first
expect(vote).to_not be_nil
expect(vote.choice).to eq 1
end
it 'updates poll tallies' do
expect(poll.reload.cached_tallies).to eq [0, 1]
end
end
end

View File

@ -0,0 +1,23 @@
require 'rails_helper'
RSpec.describe Api::V1::PollsController, type: :controller do
render_views
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
let(:scopes) { 'read:statuses' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
before { allow(controller).to receive(:doorkeeper_token) { token } }
describe 'GET #show' do
let(:poll) { Fabricate(:poll) }
before do
get :show, params: { id: poll.id }
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
end

View File

@ -5,14 +5,14 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
shared_examples 'checks for enabled registrations' do |path|
around do |example|
open_registrations = Setting.open_registrations
registrations_mode = Setting.registrations_mode
example.run
Setting.open_registrations = open_registrations
Setting.registrations_mode = registrations_mode
end
it 'redirects if it is in single user mode while it is open for registration' do
Fabricate(:account)
Setting.open_registrations = true
Setting.registrations_mode = 'open'
expect(Rails.configuration.x).to receive(:single_user_mode).and_return(true)
get path
@ -21,7 +21,7 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
end
it 'redirects if it is not open for registration while it is not in single user mode' do
Setting.open_registrations = false
Setting.registrations_mode = 'none'
expect(Rails.configuration.x).to receive(:single_user_mode).and_return(false)
get path
@ -55,13 +55,13 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
context do
around do |example|
open_registrations = Setting.open_registrations
registrations_mode = Setting.registrations_mode
example.run
Setting.open_registrations = open_registrations
Setting.registrations_mode = registrations_mode
end
it 'returns http success' do
Setting.open_registrations = true
Setting.registrations_mode = 'open'
get :new
expect(response).to have_http_status(200)
end
@ -83,13 +83,13 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
context do
around do |example|
open_registrations = Setting.open_registrations
registrations_mode = Setting.registrations_mode
example.run
Setting.open_registrations = open_registrations
Setting.registrations_mode = registrations_mode
end
subject do
Setting.open_registrations = true
Setting.registrations_mode = 'open'
request.headers["Accept-Language"] = accept_language
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } }
end

View File

@ -17,7 +17,15 @@ describe ApplicationController, type: :controller do
context 'when account is suspended' do
it 'returns http gone' do
account = Fabricate(:account, suspended: true)
account = Fabricate(:account, suspended: true, user: Fabricate(:user))
get 'success', params: { account_username: account.username }
expect(response).to have_http_status(410)
end
end
context 'when account is deleted by owner' do
it 'returns http gone' do
account = Fabricate(:account, suspended: true, user: nil)
get 'success', params: { account_username: account.username }
expect(response).to have_http_status(410)
end
@ -25,19 +33,19 @@ describe ApplicationController, type: :controller do
context 'when account is not suspended' do
it 'assigns @account' do
account = Fabricate(:account)
account = Fabricate(:account, user: Fabricate(:user))
get 'success', params: { account_username: account.username }
expect(assigns(:account)).to eq account
end
it 'sets link headers' do
account = Fabricate(:account, username: 'username')
account = Fabricate(:account, username: 'username', user: Fabricate(:user))
get 'success', params: { account_username: 'username' }
expect(response.headers['Link'].to_s).to eq '<http://test.host/.well-known/webfinger?resource=acct%3Ausername%40cb6e6126.ngrok.io>; rel="lrdd"; type="application/xrd+xml", <http://test.host/users/username.atom>; rel="alternate"; type="application/atom+xml", <https://cb6e6126.ngrok.io/users/username>; rel="alternate"; type="application/activity+json"'
end
it 'returns http success' do
account = Fabricate(:account)
account = Fabricate(:account, user: Fabricate(:user))
get 'success', params: { account_username: account.username }
expect(response).to have_http_status(200)
end

View File

@ -1,6 +1,6 @@
require 'rails_helper'
describe Settings::FollowerDomainsController do
describe RelationshipsController do
render_views
let(:user) { Fabricate(:user) }
@ -12,24 +12,17 @@ describe Settings::FollowerDomainsController do
end
describe 'GET #show' do
subject { get :show, params: { page: 2 } }
subject { get :show, params: { page: 2, relationship: 'followed_by' } }
it 'assigns @account' do
sign_in user, scope: :user
subject
expect(assigns(:account)).to eq user.account
end
it 'assigns @domains' do
it 'assigns @accounts' do
Fabricate(:account, domain: 'old').follow!(user.account)
Fabricate(:account, domain: 'recent').follow!(user.account)
sign_in user, scope: :user
subject
assigned = assigns(:domains).per(1).to_a
assigned = assigns(:accounts).per(1).to_a
expect(assigned.size).to eq 1
expect(assigned[0].accounts_from_domain).to eq 1
expect(assigned[0].domain).to eq 'old'
end
@ -49,25 +42,24 @@ describe Settings::FollowerDomainsController do
stub_request(:post, 'http://example.com/salmon').to_return(status: 200)
end
shared_examples 'redirects back to followers page' do |notice|
shared_examples 'redirects back to followers page' do
it 'redirects back to followers page' do
poopfeast.follow!(user.account)
sign_in user, scope: :user
subject
expect(flash[:notice]).to eq notice
expect(response).to redirect_to(settings_follower_domains_path)
expect(response).to redirect_to(relationships_path)
end
end
context 'when select parameter is not provided' do
subject { patch :update }
include_examples 'redirects back to followers page', 'In the process of soft-blocking followers from 0 domains...'
include_examples 'redirects back to followers page'
end
context 'when select parameter is provided' do
subject { patch :update, params: { select: ['example.com'] } }
subject { patch :update, params: { form_account_batch: { account_ids: [poopfeast.id] }, block_domains: '' } }
it 'soft-blocks followers from selected domains' do
poopfeast.follow!(user.account)
@ -79,7 +71,7 @@ describe Settings::FollowerDomainsController do
end
include_examples 'authenticate user'
include_examples 'redirects back to followers page', 'In the process of soft-blocking followers from one domain...'
include_examples 'redirects back to followers page'
end
end
end

View File

@ -11,7 +11,7 @@ describe Settings::Exports::FollowingAccountsController do
sign_in user, scope: :user
get :index, format: :csv
expect(response.body).to eq "username@domain\n"
expect(response.body).to eq "Account address,Show boosts\nusername@domain,true\n"
end
end
end

View File

@ -11,7 +11,7 @@ describe Settings::Exports::MutedAccountsController do
sign_in user, scope: :user
get :index, format: :csv
expect(response.body).to eq "username@domain\n"
expect(response.body).to eq "Account address,Hide notifications\nusername@domain,true\n"
end
end
end

View File

@ -0,0 +1,168 @@
require 'rails_helper'
describe Settings::IdentityProofsController do
include RoutingHelper
render_views
let(:user) { Fabricate(:user) }
let(:valid_token) { '1'*66 }
let(:kbname) { 'kbuser' }
let(:provider) { 'keybase' }
let(:findable_id) { Faker::Number.number(5) }
let(:unfindable_id) { Faker::Number.number(5) }
let(:new_proof_params) do
{ provider: provider, provider_username: kbname, token: valid_token, username: user.account.username }
end
let(:status_text) { "i just proved that i am also #{kbname} on #{provider}." }
let(:status_posting_params) do
{ post_status: '0', status_text: status_text }
end
let(:postable_params) do
{ account_identity_proof: new_proof_params.merge(status_posting_params) }
end
before do
allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:status) { { 'proof_valid' => true, 'proof_live' => true } }
sign_in user, scope: :user
end
describe 'new proof creation' do
context 'GET #new' do
before do
allow_any_instance_of(ProofProvider::Keybase::Badge).to receive(:avatar_url) { full_pack_url('media/images/void.png') }
end
context 'with all of the correct params' do
it 'renders the template' do
get :new, params: new_proof_params
expect(response).to render_template(:new)
end
end
context 'without any params' do
it 'redirects to :index' do
get :new, params: {}
expect(response).to redirect_to settings_identity_proofs_path
end
end
context 'with params to prove a different, not logged-in user' do
let(:wrong_user_params) { new_proof_params.merge(username: 'someone_else') }
it 'shows a helpful alert' do
get :new, params: wrong_user_params
expect(flash[:alert]).to eq I18n.t('identity_proofs.errors.wrong_user', proving: 'someone_else', current: user.account.username)
end
end
context 'with params to prove the same username cased differently' do
let(:capitalized_username) { new_proof_params.merge(username: user.account.username.upcase) }
it 'renders the new template' do
get :new, params: capitalized_username
expect(response).to render_template(:new)
end
end
end
context 'POST #create' do
context 'when saving works' do
before do
allow(ProofProvider::Keybase::Worker).to receive(:perform_async)
allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:valid?) { true }
allow_any_instance_of(AccountIdentityProof).to receive(:on_success_path) { root_url }
end
it 'serializes a ProofProvider::Keybase::Worker' do
expect(ProofProvider::Keybase::Worker).to receive(:perform_async)
post :create, params: postable_params
end
it 'delegates redirection to the proof provider' do
expect_any_instance_of(AccountIdentityProof).to receive(:on_success_path)
post :create, params: postable_params
expect(response).to redirect_to root_url
end
it 'does not post a status' do
expect(PostStatusService).not_to receive(:new)
post :create, params: postable_params
end
context 'and the user has requested to post a status' do
let(:postable_params_with_status) do
postable_params.tap { |p| p[:account_identity_proof][:post_status] = '1' }
end
it 'posts a status' do
expect_any_instance_of(PostStatusService).to receive(:call).with(user.account, text: status_text)
post :create, params: postable_params_with_status
end
end
end
context 'when saving fails' do
before do
allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:valid?) { false }
end
it 'redirects to :index' do
post :create, params: postable_params
expect(response).to redirect_to settings_identity_proofs_path
end
it 'flashes a helpful message' do
post :create, params: postable_params
expect(flash[:alert]).to eq I18n.t('identity_proofs.errors.failed', provider: 'Keybase')
end
end
context 'it can also do an update if the provider and username match an existing proof' do
before do
allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:valid?) { true }
allow(ProofProvider::Keybase::Worker).to receive(:perform_async)
Fabricate(:account_identity_proof, account: user.account, provider: provider, provider_username: kbname)
allow_any_instance_of(AccountIdentityProof).to receive(:on_success_path) { root_url }
end
it 'calls update with the new token' do
expect_any_instance_of(AccountIdentityProof).to receive(:save) do |proof|
expect(proof.token).to eq valid_token
end
post :create, params: postable_params
end
end
end
end
describe 'GET #index' do
context 'with no existing proofs' do
it 'shows the helpful explanation' do
get :index
expect(response.body).to match I18n.t('identity_proofs.explanation_html')
end
end
context 'with two proofs' do
before do
allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:valid?) { true }
@proof1 = Fabricate(:account_identity_proof, account: user.account)
@proof2 = Fabricate(:account_identity_proof, account: user.account)
allow_any_instance_of(AccountIdentityProof).to receive(:badge) { double(avatar_url: '', profile_url: '', proof_url: '') }
allow_any_instance_of(AccountIdentityProof).to receive(:refresh!) { }
end
it 'has the first proof username on the page' do
get :index
expect(response.body).to match /#{Regexp.quote(@proof1.provider_username)}/
end
it 'has the second proof username on the page' do
get :index
expect(response.body).to match /#{Regexp.quote(@proof2.provider_username)}/
end
end
end
end

View File

@ -0,0 +1,15 @@
require 'rails_helper'
describe WellKnown::KeybaseProofConfigController, type: :controller do
render_views
describe 'GET #show' do
it 'renders json' do
get :show
expect(response).to have_http_status(200)
expect(response.content_type).to eq 'application/json'
expect { JSON.parse(response.body) }.not_to raise_exception
end
end
end

View File

@ -0,0 +1,8 @@
Fabricator(:account_identity_proof) do
account
provider 'keybase'
provider_username { sequence(:provider_username) { |i| "#{Faker::Lorem.characters(15)}" } }
token { sequence(:token) { |i| "#{i}#{Faker::Crypto.sha1()*2}"[0..65] } }
verified false
live false
end

View File

@ -0,0 +1,6 @@
This "Utah teapot" photograph is licensed under the Creative Commons
Attribution-Share Alike 3.0 Unported license:
https://creativecommons.org/licenses/by-sa/3.0/deed.en
Original source of work:
https://commons.wikimedia.org/wiki/File:Utah_teapot_simple_2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

View File

@ -0,0 +1,6 @@
Fabricator(:featured_tag) do
account
tag
statuses_count 1_337
last_status_at Time.now.utc
end

View File

@ -0,0 +1,8 @@
Fabricator(:poll) do
account
status
expires_at { 7.days.from_now }
options %w(Foo Bar)
multiple false
hide_totals false
end

View File

@ -0,0 +1,5 @@
Fabricator(:poll_vote) do
account
poll
choice 0
end

View File

@ -1,2 +1,3 @@
Fabricator(:site_upload) do
file { File.open(File.join(Rails.root, 'spec', 'fabricators', 'assets', 'utah_teapot.png')) }
end

View File

@ -0,0 +1,4 @@
Fabricator(:user_invite_request) do
user
text { Faker::Lorem.sentence }
end

4
spec/fixtures/files/mute-imports.txt vendored Normal file
View File

@ -0,0 +1,4 @@
bob
eve@example.com

View File

@ -0,0 +1,4 @@
Account address,Show boosts
bob,true
eve@example.com,false

View File

@ -0,0 +1,4 @@
Account address,Hide notifications
bob,true
eve@example.com,false

View File

@ -0,0 +1,272 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Admin::ActionLogsHelper, type: :helper do
klass = Class.new do
include ActionView::Helpers
include Admin::ActionLogsHelper
end
let(:hoge) { klass.new }
describe '#log_target' do
after do
hoge.log_target(log)
end
context 'log.target' do
let(:log) { double(target: true) }
it 'calls linkable_log_target' do
expect(hoge).to receive(:linkable_log_target).with(log.target)
end
end
context '!log.target' do
let(:log) { double(target: false, target_type: :type, recorded_changes: :change) }
it 'calls log_target_from_history' do
expect(hoge).to receive(:log_target_from_history).with(log.target_type, log.recorded_changes)
end
end
end
describe '#relevant_log_changes' do
let(:log) { double(target_type: target_type, action: log_action, recorded_changes: recorded_changes) }
let(:recorded_changes) { double }
after do
hoge.relevant_log_changes(log)
end
context "log.target_type == 'CustomEmoji' && [:enable, :disable, :destroy].include?(log.action)" do
let(:target_type) { 'CustomEmoji' }
let(:log_action) { :enable }
it "calls log.recorded_changes.slice('domain')" do
expect(recorded_changes).to receive(:slice).with('domain')
end
end
context "log.target_type == 'CustomEmoji' && log.action == :update" do
let(:target_type) { 'CustomEmoji' }
let(:log_action) { :update }
it "calls log.recorded_changes.slice('domain', 'visible_in_picker')" do
expect(recorded_changes).to receive(:slice).with('domain', 'visible_in_picker')
end
end
context "log.target_type == 'User' && [:promote, :demote].include?(log.action)" do
let(:target_type) { 'User' }
let(:log_action) { :promote }
it "calls log.recorded_changes.slice('moderator', 'admin')" do
expect(recorded_changes).to receive(:slice).with('moderator', 'admin')
end
end
context "log.target_type == 'User' && [:change_email].include?(log.action)" do
let(:target_type) { 'User' }
let(:log_action) { :change_email }
it "calls log.recorded_changes.slice('email', 'unconfirmed_email')" do
expect(recorded_changes).to receive(:slice).with('email', 'unconfirmed_email')
end
end
context "log.target_type == 'DomainBlock'" do
let(:target_type) { 'DomainBlock' }
let(:log_action) { nil }
it "calls log.recorded_changes.slice('severity', 'reject_media')" do
expect(recorded_changes).to receive(:slice).with('severity', 'reject_media')
end
end
context "log.target_type == 'Status' && log.action == :update" do
let(:target_type) { 'Status' }
let(:log_action) { :update }
it "log.recorded_changes.slice('sensitive')" do
expect(recorded_changes).to receive(:slice).with('sensitive')
end
end
end
describe '#log_extra_attributes' do
after do
hoge.log_extra_attributes(hoge: 'hoge')
end
it "calls content_tag(:span, key, class: 'diff-key')" do
allow(hoge).to receive(:log_change).with(anything)
expect(hoge).to receive(:content_tag).with(:span, :hoge, class: 'diff-key')
end
it 'calls safe_join twice' do
expect(hoge).to receive(:safe_join).with(
['<span class="diff-key">hoge</span>',
'=',
'<span class="diff-neutral">hoge</span>']
)
expect(hoge).to receive(:safe_join).with([nil], ' ')
end
end
describe '#log_change' do
after do
hoge.log_change(val)
end
context '!val.is_a?(Array)' do
let(:val) { 'hoge' }
it "calls content_tag(:span, val, class: 'diff-neutral')" do
expect(hoge).to receive(:content_tag).with(:span, val, class: 'diff-neutral')
end
end
context 'val.is_a?(Array)' do
let(:val) { %w(foo bar) }
it 'calls #content_tag twice and #safe_join' do
expect(hoge).to receive(:content_tag).with(:span, 'foo', class: 'diff-old')
expect(hoge).to receive(:content_tag).with(:span, 'bar', class: 'diff-new')
expect(hoge).to receive(:safe_join).with([nil, nil], '→')
end
end
end
describe '#icon_for_log' do
subject { hoge.icon_for_log(log) }
context "log.target_type == 'Account'" do
let(:log) { double(target_type: 'Account') }
it 'returns "user"' do
expect(subject).to be 'user'
end
end
context "log.target_type == 'User'" do
let(:log) { double(target_type: 'User') }
it 'returns "user"' do
expect(subject).to be 'user'
end
end
context "log.target_type == 'CustomEmoji'" do
let(:log) { double(target_type: 'CustomEmoji') }
it 'returns "file"' do
expect(subject).to be 'file'
end
end
context "log.target_type == 'Report'" do
let(:log) { double(target_type: 'Report') }
it 'returns "flag"' do
expect(subject).to be 'flag'
end
end
context "log.target_type == 'DomainBlock'" do
let(:log) { double(target_type: 'DomainBlock') }
it 'returns "lock"' do
expect(subject).to be 'lock'
end
end
context "log.target_type == 'EmailDomainBlock'" do
let(:log) { double(target_type: 'EmailDomainBlock') }
it 'returns "envelope"' do
expect(subject).to be 'envelope'
end
end
context "log.target_type == 'Status'" do
let(:log) { double(target_type: 'Status') }
it 'returns "pencil"' do
expect(subject).to be 'pencil'
end
end
end
describe '#class_for_log_icon' do
subject { hoge.class_for_log_icon(log) }
%i(enable unsuspend unsilence confirm promote resolve).each do |action|
context "log.action == #{action}" do
let(:log) { double(action: action) }
it 'returns "positive"' do
expect(subject).to be 'positive'
end
end
end
context 'log.action == :create' do
context 'opposite_verbs?(log)' do
let(:log) { double(action: :create, target_type: 'DomainBlock') }
it 'returns "negative"' do
expect(subject).to be 'negative'
end
end
context '!opposite_verbs?(log)' do
let(:log) { double(action: :create, target_type: '') }
it 'returns "positive"' do
expect(subject).to be 'positive'
end
end
end
%i(update reset_password disable_2fa memorialize change_email).each do |action|
context "log.action == #{action}" do
let(:log) { double(action: action) }
it 'returns "neutral"' do
expect(subject).to be 'neutral'
end
end
end
%i(demote silence disable suspend remove_avatar remove_header reopen).each do |action|
context "log.action == #{action}" do
let(:log) { double(action: action) }
it 'returns "negative"' do
expect(subject).to be 'negative'
end
end
end
context 'log.action == :destroy' do
context 'opposite_verbs?(log)' do
let(:log) { double(action: :destroy, target_type: 'DomainBlock') }
it 'returns "positive"' do
expect(subject).to be 'positive'
end
end
context '!opposite_verbs?(log)' do
let(:log) { double(action: :destroy, target_type: '') }
it 'returns "negative"' do
expect(subject).to be 'negative'
end
end
end
end
end

View File

@ -69,7 +69,7 @@ describe ApplicationHelper do
describe 'open_registrations?' do
it 'returns true when open for registrations' do
without_partial_double_verification do
expect(Setting).to receive(:open_registrations).and_return(true)
expect(Setting).to receive(:registrations_mode).and_return('open')
end
expect(helper.open_registrations?).to eq true
@ -77,7 +77,7 @@ describe ApplicationHelper do
it 'returns false when closed for registrations' do
without_partial_double_verification do
expect(Setting).to receive(:open_registrations).and_return(false)
expect(Setting).to receive(:registrations_mode).and_return('none')
end
expect(helper.open_registrations?).to eq false

View File

@ -12,6 +12,7 @@ RSpec.describe ActivityPub::Activity::Announce do
type: 'Announce',
actor: 'https://example.com/actor',
object: object_json,
to: 'http://example.com/followers',
}.with_indifferent_access
end

View File

@ -1,7 +1,7 @@
require 'rails_helper'
RSpec.describe ActivityPub::Activity::Create do
let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers') }
let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers', domain: 'example.com', uri: 'https://example.com/actor') }
let(:json) do
{
@ -28,6 +28,20 @@ RSpec.describe ActivityPub::Activity::Create do
subject.perform
end
context 'unknown object type' do
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Banana',
content: 'Lorem ipsum',
}
end
it 'does not create a status' do
expect(sender.statuses.count).to be_zero
end
end
context 'standalone' do
let(:object_json) do
{
@ -407,6 +421,89 @@ RSpec.describe ActivityPub::Activity::Create do
expect(status).to_not be_nil
end
end
context 'with poll' do
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Question',
content: 'Which color was the submarine?',
oneOf: [
{
name: 'Yellow',
replies: {
type: 'Collection',
totalItems: 10,
},
},
{
name: 'Blue',
replies: {
type: 'Collection',
totalItems: 3,
}
},
],
}
end
it 'creates status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.poll).to_not be_nil
end
it 'creates a poll' do
poll = sender.polls.first
expect(poll).to_not be_nil
expect(poll.status).to_not be_nil
expect(poll.options).to eq %w(Yellow Blue)
expect(poll.cached_tallies).to eq [10, 3]
end
end
context 'when a vote to a local poll' do
let(:poll) { Fabricate(:poll, options: %w(Yellow Blue)) }
let!(:local_status) { Fabricate(:status, poll: poll) }
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
name: 'Yellow',
inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status)
}
end
it 'adds a vote to the poll with correct uri' do
vote = poll.votes.first
expect(vote).to_not be_nil
expect(vote.uri).to eq object_json[:id]
expect(poll.reload.cached_tallies).to eq [1, 0]
end
end
context 'when a vote to an expired local poll' do
let(:poll) do
poll = Fabricate.build(:poll, options: %w(Yellow Blue), expires_at: 1.day.ago)
poll.save(validate: false)
poll
end
let!(:local_status) { Fabricate(:status, poll: poll) }
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
name: 'Yellow',
inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status)
}
end
it 'does not add a vote to the poll' do
expect(poll.votes.first).to be_nil
end
end
end
context 'when sender is followed by local users' do

View File

@ -1,14 +1,15 @@
require 'rails_helper'
RSpec.describe ActivityPub::Activity::Flag do
let(:sender) { Fabricate(:account, domain: 'example.com') }
let(:sender) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') }
let(:flagged) { Fabricate(:account) }
let(:status) { Fabricate(:status, account: flagged, uri: 'foobar') }
let(:flag_id) { nil }
let(:json) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: nil,
id: flag_id,
type: 'Flag',
content: 'Boo!!',
actor: ActivityPub::TagManager.instance.uri_for(sender),
@ -34,4 +35,22 @@ RSpec.describe ActivityPub::Activity::Flag do
expect(report.status_ids).to eq [status.id]
end
end
describe '#perform with a defined uri' do
subject { described_class.new(json, sender) }
let (:flag_id) { 'http://example.com/reports/1' }
before do
subject.perform
end
it 'creates a report' do
report = Report.find_by(account: sender, target_account: flagged)
expect(report).to_not be_nil
expect(report.comment).to eq 'Boo!!'
expect(report.status_ids).to eq [status.id]
expect(report.uri).to eq flag_id
end
end
end

View File

@ -0,0 +1,88 @@
require 'rails_helper'
RSpec.describe ActivityPub::Adapter do
class TestObject < ActiveModelSerializers::Model
attributes :foo
end
class TestWithBasicContextSerializer < ActivityPub::Serializer
attributes :foo
end
class TestWithNamedContextSerializer < ActivityPub::Serializer
context :security
attributes :foo
end
class TestWithNestedNamedContextSerializer < ActivityPub::Serializer
attributes :foo
has_one :virtual_object, key: :baz, serializer: TestWithNamedContextSerializer
def virtual_object
object
end
end
class TestWithContextExtensionSerializer < ActivityPub::Serializer
context_extensions :sensitive
attributes :foo
end
class TestWithNestedContextExtensionSerializer < ActivityPub::Serializer
context_extensions :manually_approves_followers
attributes :foo
has_one :virtual_object, key: :baz, serializer: TestWithContextExtensionSerializer
def virtual_object
object
end
end
describe '#serializable_hash' do
let(:serializer_class) {}
subject { ActiveModelSerializers::SerializableResource.new(TestObject.new(foo: 'bar'), serializer: serializer_class, adapter: described_class).as_json }
context 'when serializer defines no context' do
let(:serializer_class) { TestWithBasicContextSerializer }
it 'renders a basic @context' do
expect(subject).to include({ '@context' => 'https://www.w3.org/ns/activitystreams' })
end
end
context 'when serializer defines a named context' do
let(:serializer_class) { TestWithNamedContextSerializer }
it 'renders a @context with both items' do
expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })
end
end
context 'when serializer has children that define a named context' do
let(:serializer_class) { TestWithNestedNamedContextSerializer }
it 'renders a @context with both items' do
expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })
end
end
context 'when serializer defines context extensions' do
let(:serializer_class) { TestWithContextExtensionSerializer }
it 'renders a @context with the extension' do
expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', { 'sensitive' => 'as:sensitive' }] })
end
end
context 'when serializer has children that define context extensions' do
let(:serializer_class) { TestWithNestedContextExtensionSerializer }
it 'renders a @context with both extensions' do
expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'sensitive' => 'as:sensitive' }] })
end
end
end
end

View File

@ -106,11 +106,11 @@ describe LanguageDetector do
end
describe 'remote user' do
it 'nil for foreign user when language is not present' do
it 'detects Korean language' do
string = '안녕하세요'
result = described_class.instance.detect(string, account_remote)
expect(result).to eq nil
expect(result).to eq :ko
end
end

View File

@ -0,0 +1,82 @@
require 'rails_helper'
describe ProofProvider::Keybase::Verifier do
let(:my_domain) { Rails.configuration.x.local_domain }
let(:keybase_proof) do
local_proof = AccountIdentityProof.new(
provider: 'Keybase',
provider_username: 'cryptoalice',
token: '11111111111111111111111111'
)
described_class.new('alice', 'cryptoalice', '11111111111111111111111111', my_domain)
end
let(:query_params) do
"domain=#{my_domain}&kb_username=cryptoalice&sig_hash=11111111111111111111111111&username=alice"
end
describe '#valid?' do
let(:base_url) { 'https://keybase.io/_/api/1.0/sig/proof_valid.json' }
context 'when valid' do
before do
json_response_body = '{"status":{"code":0,"name":"OK"},"proof_valid":true}'
stub_request(:get, "#{base_url}?#{query_params}").to_return(status: 200, body: json_response_body)
end
it 'calls out to keybase and returns true' do
expect(keybase_proof.valid?).to eq true
end
end
context 'when invalid' do
before do
json_response_body = '{"status":{"code":0,"name":"OK"},"proof_valid":false}'
stub_request(:get, "#{base_url}?#{query_params}").to_return(status: 200, body: json_response_body)
end
it 'calls out to keybase and returns false' do
expect(keybase_proof.valid?).to eq false
end
end
context 'with an unexpected api response' do
before do
json_response_body = '{"status":{"code":100,"desc":"wrong size hex_id","fields":{"sig_hash":"wrong size hex_id"},"name":"INPUT_ERROR"}}'
stub_request(:get, "#{base_url}?#{query_params}").to_return(status: 200, body: json_response_body)
end
it 'swallows the error and returns false' do
expect(keybase_proof.valid?).to eq false
end
end
end
describe '#status' do
let(:base_url) { 'https://keybase.io/_/api/1.0/sig/proof_live.json' }
context 'with a normal response' do
before do
json_response_body = '{"status":{"code":0,"name":"OK"},"proof_live":false,"proof_valid":true}'
stub_request(:get, "#{base_url}?#{query_params}").to_return(status: 200, body: json_response_body)
end
it 'calls out to keybase and returns the status fields as proof_valid and proof_live' do
expect(keybase_proof.status).to include({ 'proof_valid' => true, 'proof_live' => false })
end
end
context 'with an unexpected keybase response' do
before do
json_response_body = '{"status":{"code":100,"desc":"missing non-optional field sig_hash","fields":{"sig_hash":"missing non-optional field sig_hash"},"name":"INPUT_ERROR"}}'
stub_request(:get, "#{base_url}?#{query_params}").to_return(status: 200, body: json_response_body)
end
it 'raises a ProofProvider::Keybase::UnexpectedResponseError' do
expect { keybase_proof.status }.to raise_error ProofProvider::Keybase::UnexpectedResponseError
end
end
end
end

View File

@ -0,0 +1,8 @@
# Preview all emails at http://localhost:3000/rails/mailers/admin_mailer
class AdminMailerPreview < ActionMailer::Preview
# Preview this email at http://localhost:3000/rails/mailers/admin_mailer/new_pending_account
def new_pending_account
AdminMailer.new_pending_account(Account.first, User.pending.first)
end
end

View File

@ -558,6 +558,11 @@ RSpec.describe Account, type: :model do
expect(account).to model_have_error_on_field(:username)
end
it 'squishes the username before validation' do
account = Fabricate(:account, domain: nil, username: " \u3000bob \t \u00a0 \n ")
expect(account.username).to eq 'bob'
end
context 'when is local' do
it 'is invalid if the username is not unique in case-insensitive comparison among local accounts' do
account_1 = Fabricate(:account, username: 'the_doctor')

View File

@ -237,9 +237,9 @@ describe AccountInteractions do
end
describe '#block_domain!' do
let(:domain_block) { Fabricate(:domain_block) }
let(:domain) { 'example.com' }
subject { account.block_domain!(domain_block) }
subject { account.block_domain!(domain) }
it 'creates and returns AccountDomainBlock' do
expect do

View File

@ -21,20 +21,22 @@ describe Export do
target_accounts.each(&account.method(:mute!))
export = Export.new(account).to_muted_accounts_csv
results = export.strip.split
results = export.strip.split("\n")
expect(results.size).to eq 2
expect(results.first).to eq 'one@local.host'
expect(results.size).to eq 3
expect(results.first).to eq 'Account address,Hide notifications'
expect(results.second).to eq 'one@local.host,true'
end
it 'returns a csv of the following accounts' do
target_accounts.each(&account.method(:follow!))
export = Export.new(account).to_following_accounts_csv
results = export.strip.split
results = export.strip.split("\n")
expect(results.size).to eq 2
expect(results.first).to eq 'one@local.host'
expect(results.size).to eq 3
expect(results.first).to eq 'Account address,Show boosts'
expect(results.second).to eq 'one@local.host,true'
end
end

View File

@ -0,0 +1,4 @@
require 'rails_helper'
RSpec.describe FeaturedTag, type: :model do
end

5
spec/models/poll_spec.rb Normal file
View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe Poll, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe PollVote, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,4 @@
require 'rails_helper'
RSpec.describe UserInviteRequest, type: :model do
end

View File

@ -31,34 +31,6 @@ describe InstancePresenter do
end
end
context do
around do |example|
open_registrations = Setting.open_registrations
example.run
Setting.open_registrations = open_registrations
end
it "delegates open_registrations to Setting" do
Setting.open_registrations = false
expect(instance_presenter.open_registrations).to eq false
end
end
context do
around do |example|
closed_registrations_message = Setting.closed_registrations_message
example.run
Setting.closed_registrations_message = closed_registrations_message
end
it "delegates closed_registrations_message to Setting" do
Setting.closed_registrations_message = "Closed message"
expect(instance_presenter.closed_registrations_message).to eq "Closed message"
end
end
context do
around do |example|
site_contact_email = Setting.site_contact_email

View File

@ -11,8 +11,9 @@ describe 'Localization' do
headers = { 'Accept-Language' => 'zh-HK' }
get "/about", headers: headers
expect(response.body).to include(
I18n.t('about.about_mastodon_html', locale: 'zh-HK')
I18n.t('about.tagline', locale: 'zh-HK')
)
end
@ -20,16 +21,18 @@ describe 'Localization' do
headers = { 'Accept-Language' => 'es-FAKE' }
get "/about", headers: headers
expect(response.body).to include(
I18n.t('about.about_mastodon_html', locale: 'es')
I18n.t('about.tagline', locale: 'es')
)
end
it 'falls back to english when locale is missing' do
headers = { 'Accept-Language' => '12-FAKE' }
get "/about", headers: headers
expect(response.body).to include(
I18n.t('about.about_mastodon_html', locale: 'en')
I18n.t('about.tagline', locale: 'en')
)
end
end

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
require 'rails_helper'
describe ActivityPub::NoteSerializer do
let!(:account) { Fabricate(:account) }
let!(:other) { Fabricate(:account) }
let!(:parent) { Fabricate(:status, account: account, visibility: :public) }
let!(:reply1) { Fabricate(:status, account: account, thread: parent, visibility: :public) }
let!(:reply2) { Fabricate(:status, account: account, thread: parent, visibility: :public) }
let!(:reply3) { Fabricate(:status, account: other, thread: parent, visibility: :public) }
let!(:reply4) { Fabricate(:status, account: account, thread: parent, visibility: :public) }
let!(:reply5) { Fabricate(:status, account: account, thread: parent, visibility: :direct) }
before(:each) do
@serialization = ActiveModelSerializers::SerializableResource.new(parent, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter)
end
subject { JSON.parse(@serialization.to_json) }
it 'has a Note type' do
expect(subject['type']).to eql('Note')
end
it 'has a replies collection' do
expect(subject['replies']['type']).to eql('Collection')
end
it 'has a replies collection with a first Page' do
expect(subject['replies']['first']['type']).to eql('CollectionPage')
end
it 'includes public self-replies in its replies collection' do
expect(subject['replies']['first']['items']).to include(reply1.uri, reply2.uri, reply4.uri)
end
it 'does not include replies from others in its replies collection' do
expect(subject['replies']['first']['items']).to_not include(reply3.uri)
end
it 'does not include replies with direct visibility in its replies collection' do
expect(subject['replies']['first']['items']).to_not include(reply5.uri)
end
end

View File

@ -4,18 +4,18 @@ describe AccountSearchService, type: :service do
describe '.call' do
describe 'with a query to ignore' do
it 'returns empty array for missing query' do
results = subject.call('', 10)
results = subject.call('', nil, limit: 10)
expect(results).to eq []
end
it 'returns empty array for hashtag query' do
results = subject.call('#tag', 10)
results = subject.call('#tag', nil, limit: 10)
expect(results).to eq []
end
it 'returns empty array for limit zero' do
Fabricate(:account, username: 'match')
results = subject.call('match', 0)
results = subject.call('match', nil, limit: 0)
expect(results).to eq []
end
@ -25,7 +25,7 @@ describe AccountSearchService, type: :service do
it 'does not return a nil entry in the array for the exact match' do
match = Fabricate(:account, username: 'matchingusername')
results = subject.call('match', 5)
results = subject.call('match', nil, limit: 5)
expect(results).to eq [match]
end
end
@ -35,7 +35,7 @@ describe AccountSearchService, type: :service do
before do
allow(Account).to receive(:find_local)
allow(Account).to receive(:search_for)
subject.call('@', 10)
subject.call('@', nil, limit: 10)
end
it 'uses find_local with empty query to look for local accounts' do
@ -47,7 +47,7 @@ describe AccountSearchService, type: :service do
before do
allow(Account).to receive(:find_local)
allow(Account).to receive(:search_for)
subject.call('one', 10)
subject.call('one', nil, limit: 10)
end
it 'uses find_local to look for local accounts' do
@ -55,7 +55,7 @@ describe AccountSearchService, type: :service do
end
it 'uses search_for to find matches' do
expect(Account).to have_received(:search_for).with('one', 10)
expect(Account).to have_received(:search_for).with('one', 10, 0)
end
end
@ -65,16 +65,16 @@ describe AccountSearchService, type: :service do
end
it 'uses find_remote to look for remote accounts' do
subject.call('two@example.com', 10)
subject.call('two@example.com', nil, limit: 10)
expect(Account).to have_received(:find_remote).with('two', 'example.com')
end
describe 'and there is no account provided' do
it 'uses search_for to find matches' do
allow(Account).to receive(:search_for)
subject.call('two@example.com', 10, nil, resolve: false)
subject.call('two@example.com', nil, limit: 10, resolve: false)
expect(Account).to have_received(:search_for).with('two example.com', 10)
expect(Account).to have_received(:search_for).with('two example.com', 10, 0)
end
end
@ -82,9 +82,9 @@ describe AccountSearchService, type: :service do
it 'uses advanced_search_for to find matches' do
account = Fabricate(:account)
allow(Account).to receive(:advanced_search_for)
subject.call('two@example.com', 10, account, resolve: false)
subject.call('two@example.com', account, limit: 10, resolve: false)
expect(Account).to have_received(:advanced_search_for).with('two example.com', account, 10, nil)
expect(Account).to have_received(:advanced_search_for).with('two example.com', account, 10, nil, 0)
end
end
end
@ -95,7 +95,7 @@ describe AccountSearchService, type: :service do
partial = Fabricate(:account, username: 'exactness')
exact = Fabricate(:account, username: 'exact')
results = subject.call('exact', 10)
results = subject.call('exact', nil, limit: 10)
expect(results.size).to eq 2
expect(results).to eq [exact, partial]
end
@ -114,7 +114,7 @@ describe AccountSearchService, type: :service do
exact = Fabricate(:account, username: 'e')
Rails.configuration.x.local_domain = 'example.com'
results = subject.call('e@example.com', 2)
results = subject.call('e@example.com', nil, limit: 2)
expect(results.size).to eq 2
expect(results).to eq([exact, remote]).or eq([exact, remote_too])
end
@ -125,7 +125,7 @@ describe AccountSearchService, type: :service do
service = double(call: nil)
allow(ResolveAccountService).to receive(:new).and_return(service)
results = subject.call('newuser@remote.com', 10, nil, resolve: true)
results = subject.call('newuser@remote.com', nil, limit: 10, resolve: true)
expect(service).to have_received(:call).with('newuser@remote.com')
end
@ -133,7 +133,7 @@ describe AccountSearchService, type: :service do
service = double(call: nil)
allow(ResolveAccountService).to receive(:new).and_return(service)
results = subject.call('newuser@remote.com', 10, nil, resolve: false)
results = subject.call('newuser@remote.com', nil, limit: 10, resolve: false)
expect(service).not_to have_received(:call)
end
end
@ -143,7 +143,7 @@ describe AccountSearchService, type: :service do
partial = Fabricate(:account, username: 'exactness')
exact = Fabricate(:account, username: 'exact', suspended: true)
results = subject.call('exact', 10)
results = subject.call('exact', nil, limit: 10)
expect(results.size).to eq 1
expect(results).to eq [partial]
end
@ -151,7 +151,7 @@ describe AccountSearchService, type: :service do
it "does not return suspended remote accounts" do
remote = Fabricate(:account, username: 'a', domain: 'remote', display_name: 'e', suspended: true)
results = subject.call('a@example.com', 2)
results = subject.call('a@example.com', nil, limit: 2)
expect(results.size).to eq 0
expect(results).to eq []
end

View File

@ -0,0 +1,122 @@
require 'rails_helper'
RSpec.describe ActivityPub::FetchRepliesService, type: :service do
let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') }
let(:status) { Fabricate(:status, account: actor) }
let(:collection_uri) { 'http://example.com/replies/1' }
let(:items) do
[
'http://example.com/self-reply-1',
'http://example.com/self-reply-2',
'http://example.com/self-reply-3',
'http://other.com/other-reply-1',
'http://other.com/other-reply-2',
'http://other.com/other-reply-3',
'http://example.com/self-reply-4',
'http://example.com/self-reply-5',
'http://example.com/self-reply-6',
]
end
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'Collection',
id: collection_uri,
items: items,
}.with_indifferent_access
end
subject { described_class.new }
describe '#call' do
context 'when the payload is a Collection with inlined replies' do
context 'when passing the collection itself' do
it 'spawns workers for up to 5 replies on the same server' do
allow(FetchReplyWorker).to receive(:push_bulk)
subject.call(status, payload)
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
end
end
context 'when passing the URL to the collection' do
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
end
it 'spawns workers for up to 5 replies on the same server' do
allow(FetchReplyWorker).to receive(:push_bulk)
subject.call(status, collection_uri)
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
end
end
end
context 'when the payload is an OrderedCollection with inlined replies' do
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'OrderedCollection',
id: collection_uri,
orderedItems: items,
}.with_indifferent_access
end
context 'when passing the collection itself' do
it 'spawns workers for up to 5 replies on the same server' do
allow(FetchReplyWorker).to receive(:push_bulk)
subject.call(status, payload)
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
end
end
context 'when passing the URL to the collection' do
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
end
it 'spawns workers for up to 5 replies on the same server' do
allow(FetchReplyWorker).to receive(:push_bulk)
subject.call(status, collection_uri)
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
end
end
end
context 'when the payload is a paginated Collection with inlined replies' do
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'Collection',
id: collection_uri,
first: {
type: 'CollectionPage',
partOf: collection_uri,
items: items,
}
}.with_indifferent_access
end
context 'when passing the collection itself' do
it 'spawns workers for up to 5 replies on the same server' do
allow(FetchReplyWorker).to receive(:push_bulk)
subject.call(status, payload)
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
end
end
context 'when passing the URL to the collection' do
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
end
it 'spawns workers for up to 5 replies on the same server' do
allow(FetchReplyWorker).to receive(:push_bulk)
subject.call(status, collection_uri)
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
end
end
end
end
end

View File

@ -28,4 +28,49 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
expect(account.fields[1].value).to eq 'Unit test'
end
end
context 'identity proofs' do
let(:payload) do
{
id: 'https://foo.test',
type: 'Actor',
inbox: 'https://foo.test/inbox',
attachment: [
{ type: 'IdentityProof', name: 'Alice', signatureAlgorithm: 'keybase', signatureValue: 'a' * 66 },
],
}.with_indifferent_access
end
it 'parses out of attachment' do
allow(ProofProvider::Keybase::Worker).to receive(:perform_async)
account = subject.call('alice', 'example.com', payload)
expect(account.identity_proofs.count).to eq 1
proof = account.identity_proofs.first
expect(proof.provider).to eq 'keybase'
expect(proof.provider_username).to eq 'Alice'
expect(proof.token).to eq 'a' * 66
end
it 'removes no longer present proofs' do
allow(ProofProvider::Keybase::Worker).to receive(:perform_async)
account = Fabricate(:account, username: 'alice', domain: 'example.com')
old_proof = Fabricate(:account_identity_proof, account: account, provider: 'keybase', provider_username: 'Bob', token: 'b' * 66)
subject.call('alice', 'example.com', payload)
expect(account.identity_proofs.count).to eq 1
expect(account.identity_proofs.find_by(id: old_proof.id)).to be_nil
end
it 'queues a validity check on the proof' do
allow(ProofProvider::Keybase::Worker).to receive(:perform_async)
account = subject.call('alice', 'example.com', payload)
expect(ProofProvider::Keybase::Worker).to have_received(:perform_async)
end
end
end

View File

@ -8,8 +8,10 @@ RSpec.describe AppSignUpService, type: :service do
describe '#call' do
it 'returns nil when registrations are closed' do
Setting.open_registrations = false
tmp = Setting.registrations_mode
Setting.registrations_mode = 'none'
expect(subject.call(app, good_params)).to be_nil
Setting.registrations_mode = tmp
end
it 'raises an error when params are missing' do

View File

@ -0,0 +1,169 @@
require 'rails_helper'
RSpec.describe ImportService, type: :service do
let!(:account) { Fabricate(:account, locked: false) }
let!(:bob) { Fabricate(:account, username: 'bob', locked: false) }
let!(:eve) { Fabricate(:account, username: 'eve', domain: 'example.com', locked: false) }
context 'import old-style list of muted users' do
subject { ImportService.new }
let(:csv) { attachment_fixture('mute-imports.txt') }
describe 'when no accounts are muted' do
let(:import) { Import.create(account: account, type: 'muting', data: csv) }
it 'mutes the listed accounts, including notifications' do
subject.call(import)
expect(account.muting.count).to eq 2
expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
end
end
describe 'when some accounts are muted and overwrite is not set' do
let(:import) { Import.create(account: account, type: 'muting', data: csv) }
it 'mutes the listed accounts, including notifications' do
account.mute!(bob, notifications: false)
subject.call(import)
expect(account.muting.count).to eq 2
expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
end
end
describe 'when some accounts are muted and overwrite is set' do
let(:import) { Import.create(account: account, type: 'muting', data: csv, overwrite: true) }
it 'mutes the listed accounts, including notifications' do
account.mute!(bob, notifications: false)
subject.call(import)
expect(account.muting.count).to eq 2
expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
end
end
end
context 'import new-style list of muted users' do
subject { ImportService.new }
let(:csv) { attachment_fixture('new-mute-imports.txt') }
describe 'when no accounts are muted' do
let(:import) { Import.create(account: account, type: 'muting', data: csv) }
it 'mutes the listed accounts, respecting notifications' do
subject.call(import)
expect(account.muting.count).to eq 2
expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false
end
end
describe 'when some accounts are muted and overwrite is not set' do
let(:import) { Import.create(account: account, type: 'muting', data: csv) }
it 'mutes the listed accounts, respecting notifications' do
account.mute!(bob, notifications: true)
subject.call(import)
expect(account.muting.count).to eq 2
expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false
end
end
describe 'when some accounts are muted and overwrite is set' do
let(:import) { Import.create(account: account, type: 'muting', data: csv, overwrite: true) }
it 'mutes the listed accounts, respecting notifications' do
account.mute!(bob, notifications: true)
subject.call(import)
expect(account.muting.count).to eq 2
expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true
expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false
end
end
end
context 'import old-style list of followed users' do
subject { ImportService.new }
let(:csv) { attachment_fixture('mute-imports.txt') }
before do
allow(NotificationWorker).to receive(:perform_async)
end
describe 'when no accounts are followed' do
let(:import) { Import.create(account: account, type: 'following', data: csv) }
it 'follows the listed accounts, including boosts' do
subject.call(import)
expect(account.following.count).to eq 2
expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
end
end
describe 'when some accounts are already followed and overwrite is not set' do
let(:import) { Import.create(account: account, type: 'following', data: csv) }
it 'follows the listed accounts, including notifications' do
account.follow!(bob, reblogs: false)
subject.call(import)
expect(account.following.count).to eq 2
expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
end
end
describe 'when some accounts are already followed and overwrite is set' do
let(:import) { Import.create(account: account, type: 'following', data: csv, overwrite: true) }
it 'mutes the listed accounts, including notifications' do
account.follow!(bob, reblogs: false)
subject.call(import)
expect(account.following.count).to eq 2
expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
end
end
end
context 'import new-style list of followed users' do
subject { ImportService.new }
let(:csv) { attachment_fixture('new-following-imports.txt') }
before do
allow(NotificationWorker).to receive(:perform_async)
end
describe 'when no accounts are followed' do
let(:import) { Import.create(account: account, type: 'following', data: csv) }
it 'follows the listed accounts, respecting boosts' do
subject.call(import)
expect(account.following.count).to eq 2
expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
expect(Follow.find_by(account: account, target_account: eve).show_reblogs).to be false
end
end
describe 'when some accounts are already followed and overwrite is not set' do
let(:import) { Import.create(account: account, type: 'following', data: csv) }
it 'mutes the listed accounts, respecting notifications' do
account.follow!(bob, reblogs: true)
subject.call(import)
expect(account.following.count).to eq 2
expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
expect(Follow.find_by(account: account, target_account: eve).show_reblogs).to be false
end
end
describe 'when some accounts are already followed and overwrite is set' do
let(:import) { Import.create(account: account, type: 'following', data: csv, overwrite: true) }
it 'mutes the listed accounts, respecting notifications' do
account.follow!(bob, reblogs: true)
subject.call(import)
expect(account.following.count).to eq 2
expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
expect(Follow.find_by(account: account, target_account: eve).show_reblogs).to be false
end
end
end
end

View File

@ -3,6 +3,27 @@ require 'rails_helper'
RSpec.describe ReblogService, type: :service do
let(:alice) { Fabricate(:account, username: 'alice') }
context 'creates a reblog with appropriate visibility' do
let(:bob) { Fabricate(:account, username: 'bob') }
let(:visibility) { :public }
let(:reblog_visibility) { :public }
let(:status) { Fabricate(:status, account: bob, visibility: visibility) }
subject { ReblogService.new }
before do
subject.call(alice, status, visibility: reblog_visibility)
end
describe 'boosting privately' do
let(:reblog_visibility) { :private }
it 'reblogs privately' do
expect(status.reblogs.first.visibility).to eq 'private'
end
end
end
context 'OStatus' do
let(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com') }
let(:status) { Fabricate(:status, account: bob, uri: 'tag:example.com;something:something') }

View File

@ -21,6 +21,11 @@ RSpec.describe ReportService, type: :service do
subject.call(source_account, remote_account, forward: false)
expect(a_request(:post, 'http://example.com/inbox')).to_not have_been_made
end
it 'has an uri' do
report = subject.call(source_account, remote_account, forward: true)
expect(report.uri).to_not be_nil
end
end
context 'when other reports already exist for the same target' do

View File

@ -10,7 +10,7 @@ describe SearchService, type: :service do
it 'returns empty results without searching' do
allow(AccountSearchService).to receive(:new)
allow(Tag).to receive(:search_for)
results = subject.call('', 10)
results = subject.call('', nil, 10)
expect(results).to eq(empty_results)
expect(AccountSearchService).not_to have_received(:new)
@ -27,7 +27,7 @@ describe SearchService, type: :service do
it 'returns the empty results' do
service = double(call: nil)
allow(ResolveURLService).to receive(:new).and_return(service)
results = subject.call(@query, 10)
results = subject.call(@query, nil, 10)
expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
expect(results).to eq empty_results
@ -40,7 +40,7 @@ describe SearchService, type: :service do
service = double(call: account)
allow(ResolveURLService).to receive(:new).and_return(service)
results = subject.call(@query, 10)
results = subject.call(@query, nil, 10)
expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
expect(results).to eq empty_results.merge(accounts: [account])
end
@ -52,7 +52,7 @@ describe SearchService, type: :service do
service = double(call: status)
allow(ResolveURLService).to receive(:new).and_return(service)
results = subject.call(@query, 10)
results = subject.call(@query, nil, 10)
expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
expect(results).to eq empty_results.merge(statuses: [status])
end
@ -67,8 +67,8 @@ describe SearchService, type: :service do
service = double(call: [account])
allow(AccountSearchService).to receive(:new).and_return(service)
results = subject.call(query, 10)
expect(service).to have_received(:call).with(query, 10, nil, resolve: false)
results = subject.call(query, nil, 10)
expect(service).to have_received(:call).with(query, nil, limit: 10, offset: 0, resolve: false)
expect(results).to eq empty_results.merge(accounts: [account])
end
end
@ -77,17 +77,17 @@ describe SearchService, type: :service do
it 'includes the tag in the results' do
query = '#tag'
tag = Tag.new
allow(Tag).to receive(:search_for).with('tag', 10).and_return([tag])
allow(Tag).to receive(:search_for).with('tag', 10, 0).and_return([tag])
results = subject.call(query, 10)
expect(Tag).to have_received(:search_for).with('tag', 10)
results = subject.call(query, nil, 10)
expect(Tag).to have_received(:search_for).with('tag', 10, 0)
expect(results).to eq empty_results.merge(hashtags: [tag])
end
it 'does not include tag when starts with @ character' do
query = '@username'
allow(Tag).to receive(:search_for)
results = subject.call(query, 10)
results = subject.call(query, nil, 10)
expect(Tag).not_to have_received(:search_for)
expect(results).to eq empty_results
end

View File

@ -1,7 +1,7 @@
require 'rails_helper'
RSpec.describe SuspendAccountService, type: :service do
describe '#call' do
describe '#call on local account' do
before do
stub_request(:post, "https://alice.com/inbox").to_return(status: 201)
stub_request(:post, "https://bob.com/inbox").to_return(status: 201)
@ -43,4 +43,46 @@ RSpec.describe SuspendAccountService, type: :service do
expect(a_request(:post, "https://bob.com/inbox")).to have_been_made.once
end
end
describe '#call on remote account' do
before do
stub_request(:post, "https://alice.com/inbox").to_return(status: 201)
stub_request(:post, "https://bob.com/inbox").to_return(status: 201)
end
subject do
-> { described_class.new.call(remote_bob) }
end
let!(:account) { Fabricate(:account) }
let!(:remote_alice) { Fabricate(:account, inbox_url: 'https://alice.com/inbox', protocol: :activitypub) }
let!(:remote_bob) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
let!(:status) { Fabricate(:status, account: remote_bob) }
let!(:media_attachment) { Fabricate(:media_attachment, account: remote_bob) }
let!(:notification) { Fabricate(:notification, account: remote_bob) }
let!(:favourite) { Fabricate(:favourite, account: remote_bob) }
let!(:active_relationship) { Fabricate(:follow, account: remote_bob, target_account: account) }
let!(:passive_relationship) { Fabricate(:follow, target_account: remote_bob) }
let!(:subscription) { Fabricate(:subscription, account: remote_bob) }
it 'deletes associated records' do
is_expected.to change {
[
remote_bob.statuses,
remote_bob.media_attachments,
remote_bob.stream_entries,
remote_bob.notifications,
remote_bob.favourites,
remote_bob.active_relationships,
remote_bob.passive_relationships,
remote_bob.subscriptions
].map(&:count)
}.from([1, 1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0, 0])
end
it 'sends a reject follow to follwer inboxes' do
subject.call
expect(a_request(:post, remote_bob.inbox_url)).to have_been_made.once
end
end
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe PollValidator, type: :validator do
describe '#validate' do
before do
validator.validate(poll)
end
let(:validator) { described_class.new }
let(:poll) { double(options: options, expires_at: expires_at, errors: errors) }
let(:errors) { double(add: nil) }
let(:options) { %w(foo bar) }
let(:expires_at) { 1.day.from_now }
it 'have no errors' do
expect(errors).not_to have_received(:add)
end
context 'expires just 5 min ago' do
let(:expires_at) { 5.minutes.from_now }
it 'not calls errors add' do
expect(errors).not_to have_received(:add)
end
end
end
end

View File

@ -6,23 +6,29 @@ describe 'about/show.html.haml', without_verify_partial_doubles: true do
before do
allow(view).to receive(:site_hostname).and_return('example.com')
allow(view).to receive(:site_title).and_return('example site')
allow(view).to receive(:new_user).and_return(User.new)
allow(view).to receive(:use_seamless_external_login?).and_return(false)
end
it 'has valid open graph tags' do
instance_presenter = double(:instance_presenter,
site_title: 'something',
site_short_description: 'something',
site_description: 'something',
version_number: '1.0',
source_url: 'https://github.com/tootsuite/mastodon',
open_registrations: false,
thumbnail: nil,
hero: nil,
mascot: nil,
user_count: 0,
status_count: 0,
contact_account: nil,
closed_registrations_message: 'yes')
instance_presenter = double(
:instance_presenter,
site_title: 'something',
site_short_description: 'something',
site_description: 'something',
version_number: '1.0',
source_url: 'https://github.com/tootsuite/mastodon',
open_registrations: false,
thumbnail: nil,
hero: nil,
mascot: nil,
user_count: 420,
status_count: 69,
active_user_count: 420,
contact_account: nil,
sample_accounts: []
)
assign(:instance_presenter, instance_presenter)
render

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
require 'rails_helper'
describe ActivityPub::FetchRepliesWorker do
subject { described_class.new }
let(:account) { Fabricate(:account, uri: 'https://example.com/user/1') }
let(:status) { Fabricate(:status, account: account) }
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'https://example.com/statuses_replies/1',
type: 'Collection',
items: [],
}
end
let(:json) { Oj.dump(payload) }
describe 'perform' do
it 'performs a request if the collection URI is from the same host' do
stub_request(:get, 'https://example.com/statuses_replies/1').to_return(status: 200, body: json)
subject.perform(status.id, 'https://example.com/statuses_replies/1')
expect(a_request(:get, 'https://example.com/statuses_replies/1')).to have_been_made.once
end
it 'does not perform a request if the collection URI is from a different host' do
stub_request(:get, 'https://other.com/statuses_replies/1').to_return(status: 200)
subject.perform(status.id, 'https://other.com/statuses_replies/1')
expect(a_request(:get, 'https://other.com/statuses_replies/1')).to_not have_been_made
end
it 'raises when request fails' do
stub_request(:get, 'https://example.com/statuses_replies/1').to_return(status: 500)
expect { subject.perform(status.id, 'https://example.com/statuses_replies/1') }.to raise_error Mastodon::UnexpectedResponseError
end
end
end