Merge tag 'v4.3.0-rc.1'
This commit is contained in:
commit
26c9b9ba39
3459 changed files with 130932 additions and 69993 deletions
|
|
@ -3,8 +3,6 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AccountStatusesFilter do
|
||||
subject { described_class.new(account, current_account, params) }
|
||||
|
||||
let(:account) { Fabricate(:account) }
|
||||
let(:current_account) { nil }
|
||||
let(:params) { {} }
|
||||
|
|
@ -38,6 +36,8 @@ RSpec.describe AccountStatusesFilter do
|
|||
end
|
||||
|
||||
describe '#results' do
|
||||
subject { described_class.new(account, current_account, params).results }
|
||||
|
||||
let(:tag) { Fabricate(:tag) }
|
||||
|
||||
before do
|
||||
|
|
@ -56,7 +56,7 @@ RSpec.describe AccountStatusesFilter do
|
|||
let(:params) { { only_media: true } }
|
||||
|
||||
it 'returns only statuses with media' do
|
||||
expect(subject.results.all?(&:with_media?)).to be true
|
||||
expect(subject.all?(&:with_media?)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ RSpec.describe AccountStatusesFilter do
|
|||
let(:params) { { tagged: tag.name } }
|
||||
|
||||
it 'returns only statuses with tag' do
|
||||
expect(subject.results.all? { |s| s.tags.include?(tag) }).to be true
|
||||
expect(subject.all? { |s| s.tags.include?(tag) }).to be true
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ RSpec.describe AccountStatusesFilter do
|
|||
let(:params) { { exclude_replies: true } }
|
||||
|
||||
it 'returns only statuses that are not replies' do
|
||||
expect(subject.results.none?(&:reply?)).to be true
|
||||
expect(subject.none?(&:reply?)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -80,7 +80,7 @@ RSpec.describe AccountStatusesFilter do
|
|||
let(:params) { { exclude_reblogs: true } }
|
||||
|
||||
it 'returns only statuses that are not reblogs' do
|
||||
expect(subject.results.none?(&:reblog?)).to be true
|
||||
expect(subject.none?(&:reblog?)).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -89,16 +89,12 @@ RSpec.describe AccountStatusesFilter do
|
|||
let(:current_account) { nil }
|
||||
let(:direct_status) { nil }
|
||||
|
||||
it 'returns only public statuses' do
|
||||
expect(subject.results.pluck(:visibility).uniq).to match_array %w(unlisted public)
|
||||
end
|
||||
it 'returns only public statuses, public replies, and public reblogs' do
|
||||
expect(results_unique_visibilities).to match_array %w(unlisted public)
|
||||
|
||||
it 'returns public replies' do
|
||||
expect(subject.results.pluck(:in_reply_to_id)).to_not be_empty
|
||||
end
|
||||
expect(results_in_reply_to_ids).to_not be_empty
|
||||
|
||||
it 'returns public reblogs' do
|
||||
expect(subject.results.pluck(:reblog_of_id)).to_not be_empty
|
||||
expect(results_reblog_of_ids).to_not be_empty
|
||||
end
|
||||
|
||||
it_behaves_like 'filter params'
|
||||
|
|
@ -112,23 +108,19 @@ RSpec.describe AccountStatusesFilter do
|
|||
end
|
||||
|
||||
it 'returns nothing' do
|
||||
expect(subject.results.to_a).to be_empty
|
||||
expect(subject.to_a).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when accessed by self' do
|
||||
let(:current_account) { account }
|
||||
|
||||
it 'returns everything' do
|
||||
expect(subject.results.pluck(:visibility).uniq).to match_array %w(direct private unlisted public)
|
||||
end
|
||||
it 'returns all statuses, replies, and reblogs' do
|
||||
expect(results_unique_visibilities).to match_array %w(direct private unlisted public)
|
||||
|
||||
it 'returns replies' do
|
||||
expect(subject.results.pluck(:in_reply_to_id)).to_not be_empty
|
||||
end
|
||||
expect(results_in_reply_to_ids).to_not be_empty
|
||||
|
||||
it 'returns reblogs' do
|
||||
expect(subject.results.pluck(:reblog_of_id)).to_not be_empty
|
||||
expect(results_reblog_of_ids).to_not be_empty
|
||||
end
|
||||
|
||||
it_behaves_like 'filter params'
|
||||
|
|
@ -141,23 +133,19 @@ RSpec.describe AccountStatusesFilter do
|
|||
current_account.follow!(account)
|
||||
end
|
||||
|
||||
it 'returns private statuses' do
|
||||
expect(subject.results.pluck(:visibility).uniq).to match_array %w(private unlisted public)
|
||||
end
|
||||
it 'returns private statuses, replies, and reblogs' do
|
||||
expect(results_unique_visibilities).to match_array %w(private unlisted public)
|
||||
|
||||
it 'returns replies' do
|
||||
expect(subject.results.pluck(:in_reply_to_id)).to_not be_empty
|
||||
end
|
||||
expect(results_in_reply_to_ids).to_not be_empty
|
||||
|
||||
it 'returns reblogs' do
|
||||
expect(subject.results.pluck(:reblog_of_id)).to_not be_empty
|
||||
expect(results_reblog_of_ids).to_not be_empty
|
||||
end
|
||||
|
||||
context 'when there is a direct status mentioning the non-follower' do
|
||||
let!(:direct_status) { status_with_mention!(:direct, current_account) }
|
||||
|
||||
it 'returns the direct status' do
|
||||
expect(subject.results.pluck(:id)).to include(direct_status.id)
|
||||
expect(results_ids).to include(direct_status.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -167,23 +155,19 @@ RSpec.describe AccountStatusesFilter do
|
|||
context 'when accessed by a non-follower' do
|
||||
let(:current_account) { Fabricate(:account) }
|
||||
|
||||
it 'returns only public statuses' do
|
||||
expect(subject.results.pluck(:visibility).uniq).to match_array %w(unlisted public)
|
||||
end
|
||||
it 'returns only public statuses, replies, and reblogs' do
|
||||
expect(results_unique_visibilities).to match_array %w(unlisted public)
|
||||
|
||||
it 'returns public replies' do
|
||||
expect(subject.results.pluck(:in_reply_to_id)).to_not be_empty
|
||||
end
|
||||
expect(results_in_reply_to_ids).to_not be_empty
|
||||
|
||||
it 'returns public reblogs' do
|
||||
expect(subject.results.pluck(:reblog_of_id)).to_not be_empty
|
||||
expect(results_reblog_of_ids).to_not be_empty
|
||||
end
|
||||
|
||||
context 'when there is a private status mentioning the non-follower' do
|
||||
let!(:private_status) { status_with_mention!(:private, current_account) }
|
||||
|
||||
it 'returns the private status' do
|
||||
expect(subject.results.pluck(:id)).to include(private_status.id)
|
||||
expect(results_ids).to include(private_status.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -195,7 +179,7 @@ RSpec.describe AccountStatusesFilter do
|
|||
end
|
||||
|
||||
it 'does not return reblog of blocked account' do
|
||||
expect(subject.results.pluck(:id)).to_not include(reblog.id)
|
||||
expect(results_ids).to_not include(reblog.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -209,7 +193,21 @@ RSpec.describe AccountStatusesFilter do
|
|||
end
|
||||
|
||||
it 'does not return reblog of blocked domain' do
|
||||
expect(subject.results.pluck(:id)).to_not include(reblog.id)
|
||||
expect(results_ids).to_not include(reblog.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when blocking an unrelated domain' do
|
||||
let(:other_account) { Fabricate(:account, domain: nil) }
|
||||
let(:reblogging_status) { Fabricate(:status, account: other_account, visibility: 'public') }
|
||||
let!(:reblog) { Fabricate(:status, account: account, visibility: 'public', reblog: reblogging_status) }
|
||||
|
||||
before do
|
||||
current_account.block_domain!('example.com')
|
||||
end
|
||||
|
||||
it 'returns the reblog from the non-blocked domain' do
|
||||
expect(results_ids).to include(reblog.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -235,7 +233,7 @@ RSpec.describe AccountStatusesFilter do
|
|||
end
|
||||
|
||||
it 'does not return reblog of muted account' do
|
||||
expect(subject.results.pluck(:id)).to_not include(reblog.id)
|
||||
expect(results_ids).to_not include(reblog.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -247,11 +245,29 @@ RSpec.describe AccountStatusesFilter do
|
|||
end
|
||||
|
||||
it 'does not return reblog of blocked-by account' do
|
||||
expect(subject.results.pluck(:id)).to_not include(reblog.id)
|
||||
expect(results_ids).to_not include(reblog.id)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'filter params'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def results_unique_visibilities
|
||||
subject.pluck(:visibility).uniq
|
||||
end
|
||||
|
||||
def results_in_reply_to_ids
|
||||
subject.pluck(:in_reply_to_id)
|
||||
end
|
||||
|
||||
def results_reblog_of_ids
|
||||
subject.pluck(:reblog_of_id)
|
||||
end
|
||||
|
||||
def results_ids
|
||||
subject.pluck(:id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ RSpec.describe ActivityPub::Activity::Add do
|
|||
end
|
||||
|
||||
it 'fetches the status and pins it' do
|
||||
allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil, request_id: nil| # rubocop:disable Lint/UnusedBlockArgument
|
||||
allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil, **|
|
||||
expect(uri).to eq 'https://example.com/unknown'
|
||||
expect(id).to be true
|
||||
expect(on_behalf_of&.following?(sender)).to be true
|
||||
|
|
@ -64,7 +64,7 @@ RSpec.describe ActivityPub::Activity::Add do
|
|||
|
||||
context 'when there is no local follower' do
|
||||
it 'tries to fetch the status' do
|
||||
allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil, request_id: nil| # rubocop:disable Lint/UnusedBlockArgument
|
||||
allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil, **|
|
||||
expect(uri).to eq 'https://example.com/unknown'
|
||||
expect(id).to be true
|
||||
expect(on_behalf_of).to be_nil
|
||||
|
|
|
|||
|
|
@ -77,13 +77,6 @@ RSpec.describe ActivityPub::Activity::Create do
|
|||
follower.follow!(sender)
|
||||
end
|
||||
|
||||
around do |example|
|
||||
Sidekiq::Testing.fake! do
|
||||
example.run
|
||||
Sidekiq::Worker.clear_all
|
||||
end
|
||||
end
|
||||
|
||||
it 'correctly processes posts and inserts them in timelines', :aggregate_failures do
|
||||
# Simulate a temporary failure preventing from fetching the parent post
|
||||
stub_request(:get, object_json[:id]).to_return(status: 500)
|
||||
|
|
@ -539,15 +532,21 @@ RSpec.describe ActivityPub::Activity::Create do
|
|||
mediaType: 'image/png',
|
||||
url: 'http://example.com/attachment.png',
|
||||
},
|
||||
{
|
||||
type: 'Document',
|
||||
mediaType: 'image/png',
|
||||
url: 'http://example.com/emoji.png',
|
||||
},
|
||||
],
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
it 'creates status with correctly-ordered media attachments' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.media_attachments.map(&:remote_url)).to include('http://example.com/attachment.png')
|
||||
expect(status.ordered_media_attachments.map(&:remote_url)).to eq ['http://example.com/attachment.png', 'http://example.com/emoji.png']
|
||||
expect(status.ordered_media_attachment_ids).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -894,58 +893,46 @@ RSpec.describe ActivityPub::Activity::Create do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with an encrypted message' do
|
||||
subject { described_class.new(json, sender, delivery: true, delivered_to_account_id: recipient.id) }
|
||||
context 'when object URI uses bearcaps' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
let(:token) { 'foo' }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#foo'].join,
|
||||
type: 'Create',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: Addressable::URI.new(scheme: 'bear', query_values: { t: token, u: object_json[:id] }).to_s,
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'EncryptedMessage',
|
||||
attributedTo: {
|
||||
type: 'Device',
|
||||
deviceId: '1234',
|
||||
},
|
||||
to: {
|
||||
type: 'Device',
|
||||
deviceId: target_device.device_id,
|
||||
},
|
||||
messageType: 1,
|
||||
cipherText: 'Foo',
|
||||
messageFranking: 'Baz678',
|
||||
digest: {
|
||||
digestAlgorithm: 'Bar456',
|
||||
digestValue: 'Foo123',
|
||||
},
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
to: 'https://www.w3.org/ns/activitystreams#Public',
|
||||
}
|
||||
end
|
||||
let(:target_device) { Fabricate(:device, account: recipient) }
|
||||
|
||||
before do
|
||||
stub_request(:get, object_json[:id])
|
||||
.with(headers: { Authorization: "Bearer #{token}" })
|
||||
.to_return(body: Oj.dump(object_json), headers: { 'Content-Type': 'application/activity+json' })
|
||||
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'creates an encrypted message' do
|
||||
encrypted_message = target_device.encrypted_messages.reload.first
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(encrypted_message).to_not be_nil
|
||||
expect(encrypted_message.from_device_id).to eq '1234'
|
||||
expect(encrypted_message.from_account).to eq sender
|
||||
expect(encrypted_message.type).to eq 1
|
||||
expect(encrypted_message.body).to eq 'Foo'
|
||||
expect(encrypted_message.digest).to eq 'Foo123'
|
||||
end
|
||||
|
||||
it 'creates a message franking' do
|
||||
encrypted_message = target_device.encrypted_messages.reload.first
|
||||
message_franking = encrypted_message.message_franking
|
||||
|
||||
crypt = ActiveSupport::MessageEncryptor.new(SystemKey.current_key, serializer: Oj)
|
||||
json = crypt.decrypt_and_verify(message_franking)
|
||||
|
||||
expect(json['source_account_id']).to eq sender.id
|
||||
expect(json['target_account_id']).to eq recipient.id
|
||||
expect(json['original_franking']).to eq 'Baz678'
|
||||
expect(status).to_not be_nil
|
||||
expect(status).to have_attributes(
|
||||
visibility: 'public',
|
||||
text: 'Lorem ipsum'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -47,9 +47,13 @@ RSpec.describe ActivityPub::Activity::Delete do
|
|||
expect(Status.find_by(id: status.id)).to be_nil
|
||||
end
|
||||
|
||||
it 'sends delete activity to followers of rebloggers' do
|
||||
it 'sends delete activity to followers of rebloggers', :inline_jobs do
|
||||
expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
|
||||
end
|
||||
|
||||
it 'deletes the reblog' do
|
||||
expect { reblog.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ RSpec.describe ActivityPub::Activity::Flag do
|
|||
expect(report).to_not be_nil
|
||||
expect(report.comment).to eq 'Boo!!'
|
||||
expect(report.status_ids).to eq [status.id]
|
||||
expect(report.application).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -54,7 +55,7 @@ RSpec.describe ActivityPub::Activity::Flag do
|
|||
}.with_indifferent_access, sender)
|
||||
end
|
||||
|
||||
let(:long_comment) { Faker::Lorem.characters(number: 6000) }
|
||||
let(:long_comment) { 'a' * described_class::COMMENT_SIZE_LIMIT * 2 }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
|
|
@ -63,10 +64,12 @@ RSpec.describe ActivityPub::Activity::Flag do
|
|||
it 'creates a report but with a truncated comment' do
|
||||
report = Report.find_by(account: sender, target_account: flagged)
|
||||
|
||||
expect(report).to_not be_nil
|
||||
expect(report.comment.length).to eq 5000
|
||||
expect(report.comment).to eq long_comment[0...5000]
|
||||
expect(report.status_ids).to eq [status.id]
|
||||
expect(report)
|
||||
.to be_present
|
||||
.and have_attributes(status_ids: [status.id])
|
||||
expect(report.comment)
|
||||
.to have_attributes(length: described_class::COMMENT_SIZE_LIMIT)
|
||||
.and eq(long_comment[0...described_class::COMMENT_SIZE_LIMIT])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::Move do
|
||||
RSpec::Matchers.define_negated_matcher :not_be_following, :be_following
|
||||
RSpec::Matchers.define_negated_matcher :not_be_requested, :be_requested
|
||||
|
||||
let(:follower) { Fabricate(:account) }
|
||||
let(:old_account) { Fabricate(:account, uri: 'https://example.org/alice', domain: 'example.org', protocol: :activitypub, inbox_url: 'https://example.org/inbox') }
|
||||
let(:new_account) { Fabricate(:account, uri: 'https://example.com/alice', domain: 'example.com', protocol: :activitypub, inbox_url: 'https://example.com/inbox', also_known_as: also_known_as) }
|
||||
|
|
@ -38,49 +41,37 @@ RSpec.describe ActivityPub::Activity::Move do
|
|||
subject.perform
|
||||
end
|
||||
|
||||
context 'when all conditions are met' do
|
||||
it 'sets moved account on old account' do
|
||||
expect(old_account.reload.moved_to_account_id).to eq new_account.id
|
||||
end
|
||||
|
||||
it 'makes followers unfollow old account' do
|
||||
expect(follower.following?(old_account)).to be false
|
||||
end
|
||||
|
||||
it 'makes followers follow-request the new account' do
|
||||
expect(follower.requested?(new_account)).to be true
|
||||
context 'when all conditions are met', :inline_jobs do
|
||||
it 'sets moved on old account, followers unfollow old account, followers request the new account' do
|
||||
expect(old_account.reload.moved_to_account_id)
|
||||
.to eq new_account.id
|
||||
expect(follower)
|
||||
.to not_be_following(old_account)
|
||||
.and be_requested(new_account)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the new account can't be resolved" do
|
||||
let(:returned_account) { nil }
|
||||
|
||||
it 'does not set moved account on old account' do
|
||||
expect(old_account.reload.moved_to_account_id).to be_nil
|
||||
end
|
||||
|
||||
it 'does not make followers unfollow old account' do
|
||||
expect(follower.following?(old_account)).to be true
|
||||
end
|
||||
|
||||
it 'does not make followers follow-request the new account' do
|
||||
expect(follower.requested?(new_account)).to be false
|
||||
it 'does not set moved on old account, does not unfollow old, does not follow request new' do
|
||||
expect(old_account.reload.moved_to_account_id)
|
||||
.to be_nil
|
||||
expect(follower)
|
||||
.to be_following(old_account)
|
||||
.and not_be_requested(new_account)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the new account does not references the old account' do
|
||||
let(:also_known_as) { [] }
|
||||
|
||||
it 'does not set moved account on old account' do
|
||||
expect(old_account.reload.moved_to_account_id).to be_nil
|
||||
end
|
||||
|
||||
it 'does not make followers unfollow old account' do
|
||||
expect(follower.following?(old_account)).to be true
|
||||
end
|
||||
|
||||
it 'does not make followers follow-request the new account' do
|
||||
expect(follower.requested?(new_account)).to be false
|
||||
it 'does not set moved on old account, does not unfollow old, does not follow request new' do
|
||||
expect(old_account.reload.moved_to_account_id)
|
||||
.to be_nil
|
||||
expect(follower)
|
||||
.to be_following(old_account)
|
||||
.and not_be_requested(new_account)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -91,16 +82,12 @@ RSpec.describe ActivityPub::Activity::Move do
|
|||
redis.del("move_in_progress:#{old_account.id}")
|
||||
end
|
||||
|
||||
it 'does not set moved account on old account' do
|
||||
expect(old_account.reload.moved_to_account_id).to be_nil
|
||||
end
|
||||
|
||||
it 'does not make followers unfollow old account' do
|
||||
expect(follower.following?(old_account)).to be true
|
||||
end
|
||||
|
||||
it 'does not make followers follow-request the new account' do
|
||||
expect(follower.requested?(new_account)).to be false
|
||||
it 'does not set moved on old account, does not unfollow old, does not follow request new' do
|
||||
expect(old_account.reload.moved_to_account_id)
|
||||
.to be_nil
|
||||
expect(follower)
|
||||
.to be_following(old_account)
|
||||
.and not_be_requested(new_account)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ RSpec.describe ActivityPub::Adapter do
|
|||
describe '#serializable_hash' do
|
||||
subject { ActiveModelSerializers::SerializableResource.new(TestObject.new(foo: 'bar'), serializer: serializer_class, adapter: described_class).as_json }
|
||||
|
||||
let(:serializer_class) {}
|
||||
let(:serializer_class) { nil }
|
||||
|
||||
context 'when serializer defines no context' do
|
||||
let(:serializer_class) { TestWithBasicContextSerializer }
|
||||
|
|
|
|||
|
|
@ -18,10 +18,6 @@ RSpec.describe ActivityPub::LinkedDataSignature do
|
|||
|
||||
let(:json) { raw_json.merge('signature' => signature) }
|
||||
|
||||
before do
|
||||
stub_jsonld_contexts!
|
||||
end
|
||||
|
||||
describe '#verify_actor!' do
|
||||
context 'when signature matches' do
|
||||
let(:raw_signature) do
|
||||
|
|
@ -99,16 +95,11 @@ RSpec.describe ActivityPub::LinkedDataSignature do
|
|||
describe '#sign!' do
|
||||
subject { described_class.new(raw_json).sign!(sender) }
|
||||
|
||||
it 'returns a hash' do
|
||||
it 'returns a hash with a signature, the expected context, and the signature can be verified', :aggregate_failures do
|
||||
expect(subject).to be_a Hash
|
||||
end
|
||||
|
||||
it 'contains signature' do
|
||||
expect(subject['signature']).to be_a Hash
|
||||
expect(subject['signature']['signatureValue']).to be_present
|
||||
end
|
||||
|
||||
it 'can be verified again' do
|
||||
expect(Array(subject['@context'])).to include('https://w3id.org/security/v1')
|
||||
expect(described_class.new(subject).verify_actor!).to eq sender
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -52,20 +52,27 @@ RSpec.describe ActivityPub::TagManager do
|
|||
expect(subject.to(status)).to include(subject.followers_uri_for(mentioned))
|
||||
end
|
||||
|
||||
it "returns URIs of mentions for direct silenced author's status only if they are followers or requesting to be" do
|
||||
bob = Fabricate(:account, username: 'bob')
|
||||
alice = Fabricate(:account, username: 'alice')
|
||||
foo = Fabricate(:account)
|
||||
author = Fabricate(:account, username: 'author', silenced: true)
|
||||
status = Fabricate(:status, visibility: :direct, account: author)
|
||||
bob.follow!(author)
|
||||
FollowRequest.create!(account: foo, target_account: author)
|
||||
status.mentions.create(account: alice)
|
||||
status.mentions.create(account: bob)
|
||||
status.mentions.create(account: foo)
|
||||
expect(subject.to(status)).to include(subject.uri_for(bob))
|
||||
expect(subject.to(status)).to include(subject.uri_for(foo))
|
||||
expect(subject.to(status)).to_not include(subject.uri_for(alice))
|
||||
context 'with followers and requested followers' do
|
||||
let!(:bob) { Fabricate(:account, username: 'bob') }
|
||||
let!(:alice) { Fabricate(:account, username: 'alice') }
|
||||
let!(:foo) { Fabricate(:account) }
|
||||
let!(:author) { Fabricate(:account, username: 'author', silenced: true) }
|
||||
let!(:status) { Fabricate(:status, visibility: :direct, account: author) }
|
||||
|
||||
before do
|
||||
bob.follow!(author)
|
||||
FollowRequest.create!(account: foo, target_account: author)
|
||||
status.mentions.create(account: alice)
|
||||
status.mentions.create(account: bob)
|
||||
status.mentions.create(account: foo)
|
||||
end
|
||||
|
||||
it "returns URIs of mentions for direct silenced author's status only if they are followers or requesting to be" do
|
||||
expect(subject.to(status))
|
||||
.to include(subject.uri_for(bob))
|
||||
.and include(subject.uri_for(foo))
|
||||
.and not_include(subject.uri_for(alice))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -97,20 +104,35 @@ RSpec.describe ActivityPub::TagManager do
|
|||
expect(subject.cc(status)).to include(subject.uri_for(mentioned))
|
||||
end
|
||||
|
||||
it "returns URIs of mentions for silenced author's non-direct status only if they are followers or requesting to be" do
|
||||
bob = Fabricate(:account, username: 'bob')
|
||||
context 'with followers and requested followers' do
|
||||
let!(:bob) { Fabricate(:account, username: 'bob') }
|
||||
let!(:alice) { Fabricate(:account, username: 'alice') }
|
||||
let!(:foo) { Fabricate(:account) }
|
||||
let!(:author) { Fabricate(:account, username: 'author', silenced: true) }
|
||||
let!(:status) { Fabricate(:status, visibility: :public, account: author) }
|
||||
|
||||
before do
|
||||
bob.follow!(author)
|
||||
FollowRequest.create!(account: foo, target_account: author)
|
||||
status.mentions.create(account: alice)
|
||||
status.mentions.create(account: bob)
|
||||
status.mentions.create(account: foo)
|
||||
end
|
||||
|
||||
it "returns URIs of mentions for silenced author's non-direct status only if they are followers or requesting to be" do
|
||||
expect(subject.cc(status))
|
||||
.to include(subject.uri_for(bob))
|
||||
.and include(subject.uri_for(foo))
|
||||
.and not_include(subject.uri_for(alice))
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns poster of reblogged post, if reblog' do
|
||||
bob = Fabricate(:account, username: 'bob', domain: 'example.com', inbox_url: 'http://example.com/bob')
|
||||
alice = Fabricate(:account, username: 'alice')
|
||||
foo = Fabricate(:account)
|
||||
author = Fabricate(:account, username: 'author', silenced: true)
|
||||
status = Fabricate(:status, visibility: :public, account: author)
|
||||
bob.follow!(author)
|
||||
FollowRequest.create!(account: foo, target_account: author)
|
||||
status.mentions.create(account: alice)
|
||||
status.mentions.create(account: bob)
|
||||
status.mentions.create(account: foo)
|
||||
expect(subject.cc(status)).to include(subject.uri_for(bob))
|
||||
expect(subject.cc(status)).to include(subject.uri_for(foo))
|
||||
expect(subject.cc(status)).to_not include(subject.uri_for(alice))
|
||||
status = Fabricate(:status, visibility: :public, account: bob)
|
||||
reblog = Fabricate(:status, visibility: :public, account: alice, reblog: status)
|
||||
expect(subject.cc(reblog)).to include(subject.uri_for(bob))
|
||||
end
|
||||
|
||||
it 'returns poster of reblogged post, if reblog' do
|
||||
|
|
|
|||
|
|
@ -2,17 +2,33 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::InstanceAccountsDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
RSpec.describe Admin::Metrics::Dimension::InstanceAccountsDimension do
|
||||
subject { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:limit) { 10 }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
let(:params) { ActionController::Parameters.new(domain: domain) }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
let(:domain) { 'host.example' }
|
||||
let(:alice) { Fabricate(:account, domain: domain) }
|
||||
let(:bob) { Fabricate(:account) }
|
||||
|
||||
before do
|
||||
Fabricate :follow, target_account: alice
|
||||
Fabricate :follow, target_account: bob
|
||||
Fabricate :status, account: alice
|
||||
Fabricate :status, account: bob
|
||||
end
|
||||
|
||||
it 'returns instances with follow counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(1)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(key: alice.username, value: '1')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,17 +2,31 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::InstanceLanguagesDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
RSpec.describe Admin::Metrics::Dimension::InstanceLanguagesDimension do
|
||||
subject { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:limit) { 10 }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
let(:params) { ActionController::Parameters.new(domain: domain) }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
let(:domain) { 'host.example' }
|
||||
let(:alice) { Fabricate(:account, domain: domain) }
|
||||
let(:bob) { Fabricate(:account) }
|
||||
|
||||
before do
|
||||
Fabricate :status, account: alice, language: 'en'
|
||||
Fabricate :status, account: bob, language: 'es'
|
||||
end
|
||||
|
||||
it 'returns locales with status counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(1)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(key: 'en', value: '1')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::LanguagesDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
RSpec.describe Admin::Metrics::Dimension::LanguagesDimension do
|
||||
subject { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
|
|
@ -11,8 +11,21 @@ describe Admin::Metrics::Dimension::LanguagesDimension do
|
|||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
let(:alice) { Fabricate(:user, locale: 'en', current_sign_in_at: 1.day.ago) }
|
||||
let(:bob) { Fabricate(:user, locale: 'en', current_sign_in_at: 30.days.ago) }
|
||||
|
||||
before do
|
||||
alice.update(current_sign_in_at: 1.day.ago)
|
||||
bob.update(current_sign_in_at: 30.days.ago)
|
||||
end
|
||||
|
||||
it 'returns locales with sign in counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(1)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(key: 'en', value: '1')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::ServersDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
RSpec.describe Admin::Metrics::Dimension::ServersDimension do
|
||||
subject { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
|
|
@ -11,8 +11,24 @@ describe Admin::Metrics::Dimension::ServersDimension do
|
|||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
let(:domain) { 'host.example' }
|
||||
let(:alice) { Fabricate(:account, domain: domain) }
|
||||
let(:bob) { Fabricate(:account) }
|
||||
|
||||
before do
|
||||
Fabricate :status, account: alice, created_at: 1.day.ago
|
||||
Fabricate :status, account: alice, created_at: 30.days.ago
|
||||
Fabricate :status, account: bob, created_at: 1.day.ago
|
||||
end
|
||||
|
||||
it 'returns domains with status counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(2)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(key: domain, value: '1'),
|
||||
include(key: Rails.configuration.x.local_domain, value: '1')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::SoftwareVersionsDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
RSpec.describe Admin::Metrics::Dimension::SoftwareVersionsDimension do
|
||||
subject { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
|
|
@ -11,8 +11,12 @@ describe Admin::Metrics::Dimension::SoftwareVersionsDimension do
|
|||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
it 'reports on the running software' do
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to include(
|
||||
include(key: 'mastodon', value: Mastodon::Version.to_s),
|
||||
include(key: 'ruby', value: include(RUBY_VERSION))
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::SourcesDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
RSpec.describe Admin::Metrics::Dimension::SourcesDimension do
|
||||
subject { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
|
|
@ -11,8 +11,21 @@ describe Admin::Metrics::Dimension::SourcesDimension do
|
|||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
let(:app) { Fabricate(:application) }
|
||||
let(:alice) { Fabricate(:user) }
|
||||
let(:bob) { Fabricate(:user) }
|
||||
|
||||
before do
|
||||
alice.update(created_by_application: app)
|
||||
end
|
||||
|
||||
it 'returns OAuth applications with user counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(1)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(key: app.name, value: '1')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::SpaceUsageDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
RSpec.describe Admin::Metrics::Dimension::SpaceUsageDimension do
|
||||
subject { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
|
|
@ -11,8 +11,13 @@ describe Admin::Metrics::Dimension::SpaceUsageDimension do
|
|||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
it 'reports on used storage space' do
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to include(
|
||||
include(key: 'media', value: /\d/),
|
||||
include(key: 'postgresql', value: /\d/),
|
||||
include(key: 'redis', value: /\d/)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,17 +2,37 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::TagLanguagesDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
RSpec.describe Admin::Metrics::Dimension::TagLanguagesDimension do
|
||||
subject { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:limit) { 10 }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
let(:params) { ActionController::Parameters.new(id: tag.id) }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
let(:alice) { Fabricate(:account) }
|
||||
let(:bob) { Fabricate(:account) }
|
||||
let(:tag) { Fabricate(:tag) }
|
||||
|
||||
before do
|
||||
alice_status_recent = Fabricate :status, account: alice, created_at: 1.day.ago, language: 'en'
|
||||
alice_status_older = Fabricate :status, account: alice, created_at: 30.days.ago, language: 'en'
|
||||
bob_status_recent = Fabricate :status, account: bob, created_at: 1.day.ago, language: 'es'
|
||||
|
||||
alice_status_older.tags << tag
|
||||
alice_status_recent.tags << tag
|
||||
bob_status_recent.tags << tag
|
||||
end
|
||||
|
||||
it 'returns languages with tag usage counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(2)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(key: 'en', value: '1'),
|
||||
include(key: 'es', value: '1')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,17 +2,38 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::TagServersDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
RSpec.describe Admin::Metrics::Dimension::TagServersDimension do
|
||||
subject { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:limit) { 10 }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
let(:params) { ActionController::Parameters.new(id: tag.id) }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
let(:alice) { Fabricate(:account, domain: domain) }
|
||||
let(:bob) { Fabricate(:account) }
|
||||
let(:domain) { 'host.example' }
|
||||
let(:tag) { Fabricate(:tag) }
|
||||
|
||||
before do
|
||||
alice_status_recent = Fabricate :status, account: alice, created_at: 1.day.ago
|
||||
alice_status_older = Fabricate :status, account: alice, created_at: 30.days.ago
|
||||
bob_status_recent = Fabricate :status, account: bob, created_at: 1.day.ago
|
||||
|
||||
alice_status_older.tags << tag
|
||||
alice_status_recent.tags << tag
|
||||
bob_status_recent.tags << tag
|
||||
end
|
||||
|
||||
it 'returns servers with tag usage counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(2)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(key: domain, value: '1'),
|
||||
include(key: Rails.configuration.x.local_domain, value: '1')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
22
spec/lib/admin/metrics/dimension_spec.rb
Normal file
22
spec/lib/admin/metrics/dimension_spec.rb
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Admin::Metrics::Dimension do
|
||||
describe '.retrieve' do
|
||||
subject { described_class.retrieve(reports, start_at, end_at, 5, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:params) { ActionController::Parameters.new({ instance_accounts: [123], instance_languages: ['en'] }) }
|
||||
let(:reports) { [:instance_accounts, :instance_languages] }
|
||||
|
||||
it 'returns instances of provided classes' do
|
||||
expect(subject)
|
||||
.to contain_exactly(
|
||||
be_a(Admin::Metrics::Dimension::InstanceAccountsDimension),
|
||||
be_a(Admin::Metrics::Dimension::InstanceLanguagesDimension)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2,16 +2,39 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::ActiveUsersMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
RSpec.describe Admin::Metrics::Measure::ActiveUsersMeasure do
|
||||
subject { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
context 'with activity tracking records' do
|
||||
before do
|
||||
3.times do
|
||||
travel_to(2.days.ago) { record_login_activity }
|
||||
end
|
||||
2.times do
|
||||
travel_to(1.day.ago) { record_login_activity }
|
||||
end
|
||||
travel_to(0.days.ago) { record_login_activity }
|
||||
end
|
||||
|
||||
it 'returns correct activity tracker counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(3)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(date: 2.days.ago.midnight.to_time, value: '3'),
|
||||
include(date: 1.day.ago.midnight.to_time, value: '2'),
|
||||
include(date: 0.days.ago.midnight.to_time, value: '1')
|
||||
)
|
||||
end
|
||||
|
||||
def record_login_activity
|
||||
ActivityTracker.record('activity:logins', Fabricate(:user).id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::InstanceAccountsMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
RSpec.describe Admin::Metrics::Measure::InstanceAccountsMeasure do
|
||||
subject { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let(:domain) { 'example.com' }
|
||||
|
||||
|
|
@ -20,12 +20,13 @@ describe Admin::Metrics::Measure::InstanceAccountsMeasure do
|
|||
Fabricate(:account, domain: "foo.#{domain}", created_at: 1.year.ago)
|
||||
Fabricate(:account, domain: "foo.#{domain}")
|
||||
Fabricate(:account, domain: "bar.#{domain}")
|
||||
Fabricate(:account, domain: 'other-host.example')
|
||||
end
|
||||
|
||||
describe 'total' do
|
||||
describe '#total' do
|
||||
context 'without include_subdomains' do
|
||||
it 'returns the expected number of accounts' do
|
||||
expect(measure.total).to eq 3
|
||||
expect(subject.total).to eq 3
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -33,14 +34,21 @@ describe Admin::Metrics::Measure::InstanceAccountsMeasure do
|
|||
let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') }
|
||||
|
||||
it 'returns the expected number of accounts' do
|
||||
expect(measure.total).to eq 6
|
||||
expect(subject.total).to eq 6
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
it 'returns correct instance_accounts counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(3)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(date: 2.days.ago.midnight.to_time, value: '0'),
|
||||
include(date: 1.day.ago.midnight.to_time, value: '0'),
|
||||
include(date: 0.days.ago.midnight.to_time, value: '1')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::InstanceFollowersMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
RSpec.describe Admin::Metrics::Measure::InstanceFollowersMeasure do
|
||||
subject { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let(:domain) { 'example.com' }
|
||||
|
||||
|
|
@ -22,12 +22,14 @@ describe Admin::Metrics::Measure::InstanceFollowersMeasure do
|
|||
Fabricate(:account, domain: "foo.#{domain}").follow!(local_account)
|
||||
Fabricate(:account, domain: "foo.#{domain}").follow!(local_account)
|
||||
Fabricate(:account, domain: "bar.#{domain}")
|
||||
|
||||
Fabricate(:account, domain: 'other.example').follow!(local_account)
|
||||
end
|
||||
|
||||
describe 'total' do
|
||||
describe '#total' do
|
||||
context 'without include_subdomains' do
|
||||
it 'returns the expected number of accounts' do
|
||||
expect(measure.total).to eq 2
|
||||
expect(subject.total).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -35,14 +37,21 @@ describe Admin::Metrics::Measure::InstanceFollowersMeasure do
|
|||
let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') }
|
||||
|
||||
it 'returns the expected number of accounts' do
|
||||
expect(measure.total).to eq 4
|
||||
expect(subject.total).to eq 4
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
it 'returns correct instance_followers counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(3)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(date: 2.days.ago.midnight.to_time, value: '0'),
|
||||
include(date: 1.day.ago.midnight.to_time, value: '0'),
|
||||
include(date: 0.days.ago.midnight.to_time, value: '2')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::InstanceFollowsMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
RSpec.describe Admin::Metrics::Measure::InstanceFollowsMeasure do
|
||||
subject { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let(:domain) { 'example.com' }
|
||||
|
||||
|
|
@ -24,10 +24,10 @@ describe Admin::Metrics::Measure::InstanceFollowsMeasure do
|
|||
Fabricate(:account, domain: "bar.#{domain}")
|
||||
end
|
||||
|
||||
describe 'total' do
|
||||
describe '#total' do
|
||||
context 'without include_subdomains' do
|
||||
it 'returns the expected number of accounts' do
|
||||
expect(measure.total).to eq 2
|
||||
expect(subject.total).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -35,14 +35,21 @@ describe Admin::Metrics::Measure::InstanceFollowsMeasure do
|
|||
let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') }
|
||||
|
||||
it 'returns the expected number of accounts' do
|
||||
expect(measure.total).to eq 4
|
||||
expect(subject.total).to eq 4
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
it 'returns correct instance_followers counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(3)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(date: 2.days.ago.midnight.to_time, value: '0'),
|
||||
include(date: 1.day.ago.midnight.to_time, value: '0'),
|
||||
include(date: 0.days.ago.midnight.to_time, value: '2')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
RSpec.describe Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure do
|
||||
subject { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let(:domain) { 'example.com' }
|
||||
|
||||
|
|
@ -20,11 +20,11 @@ describe Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure do
|
|||
remote_account_on_subdomain.media_attachments.create!(file: attachment_fixture('attachment.jpg'))
|
||||
end
|
||||
|
||||
describe 'total' do
|
||||
describe '#total' do
|
||||
context 'without include_subdomains' do
|
||||
it 'returns the expected number of accounts' do
|
||||
expected_total = remote_account.media_attachments.sum(:file_file_size) + remote_account.media_attachments.sum(:thumbnail_file_size)
|
||||
expect(measure.total).to eq expected_total
|
||||
expect(subject.total).to eq expected_total
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -36,14 +36,25 @@ describe Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure do
|
|||
account.media_attachments.sum(:file_file_size) + account.media_attachments.sum(:thumbnail_file_size)
|
||||
end
|
||||
|
||||
expect(measure.total).to eq expected_total
|
||||
expect(subject.total).to eq expected_total
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
it 'returns correct media_attachments counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(3)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(date: 2.days.ago.midnight.to_time, value: '0'),
|
||||
include(date: 1.day.ago.midnight.to_time, value: '0'),
|
||||
include(date: 0.days.ago.midnight.to_time, value: expected_domain_only_total.to_s)
|
||||
)
|
||||
end
|
||||
|
||||
def expected_domain_only_total
|
||||
remote_account.media_attachments.sum(:file_file_size) + remote_account.media_attachments.sum(:thumbnail_file_size)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::InstanceReportsMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
RSpec.describe Admin::Metrics::Measure::InstanceReportsMeasure do
|
||||
subject { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let(:domain) { 'example.com' }
|
||||
|
||||
|
|
@ -21,10 +21,10 @@ describe Admin::Metrics::Measure::InstanceReportsMeasure do
|
|||
Fabricate(:report, target_account: Fabricate(:account, domain: "bar.#{domain}"))
|
||||
end
|
||||
|
||||
describe 'total' do
|
||||
describe '#total' do
|
||||
context 'without include_subdomains' do
|
||||
it 'returns the expected number of accounts' do
|
||||
expect(measure.total).to eq 2
|
||||
expect(subject.total).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -32,14 +32,21 @@ describe Admin::Metrics::Measure::InstanceReportsMeasure do
|
|||
let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') }
|
||||
|
||||
it 'returns the expected number of accounts' do
|
||||
expect(measure.total).to eq 5
|
||||
expect(subject.total).to eq 5
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
it 'returns correct instance_reports counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(3)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(date: 2.days.ago.midnight.to_time, value: '0'),
|
||||
include(date: 1.day.ago.midnight.to_time, value: '0'),
|
||||
include(date: 0.days.ago.midnight.to_time, value: '2')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::InstanceStatusesMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
RSpec.describe Admin::Metrics::Measure::InstanceStatusesMeasure do
|
||||
subject { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let(:domain) { 'example.com' }
|
||||
|
||||
|
|
@ -21,10 +21,10 @@ describe Admin::Metrics::Measure::InstanceStatusesMeasure do
|
|||
Fabricate(:status, account: Fabricate(:account, domain: "bar.#{domain}"))
|
||||
end
|
||||
|
||||
describe 'total' do
|
||||
describe '#total' do
|
||||
context 'without include_subdomains' do
|
||||
it 'returns the expected number of accounts' do
|
||||
expect(measure.total).to eq 2
|
||||
expect(subject.total).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -32,14 +32,21 @@ describe Admin::Metrics::Measure::InstanceStatusesMeasure do
|
|||
let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') }
|
||||
|
||||
it 'returns the expected number of accounts' do
|
||||
expect(measure.total).to eq 5
|
||||
expect(subject.total).to eq 5
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
it 'returns correct instance_statuses counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(3)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(date: 2.days.ago.midnight.to_time, value: '0'),
|
||||
include(date: 1.day.ago.midnight.to_time, value: '0'),
|
||||
include(date: 0.days.ago.midnight.to_time, value: '2')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,16 +2,39 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::InteractionsMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
RSpec.describe Admin::Metrics::Measure::InteractionsMeasure do
|
||||
subject { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
context 'with activity tracking records' do
|
||||
before do
|
||||
3.times do
|
||||
travel_to(2.days.ago) { record_interaction_activity }
|
||||
end
|
||||
2.times do
|
||||
travel_to(1.day.ago) { record_interaction_activity }
|
||||
end
|
||||
travel_to(0.days.ago) { record_interaction_activity }
|
||||
end
|
||||
|
||||
it 'returns correct activity tracker counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(3)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(date: 2.days.ago.midnight.to_time, value: '3'),
|
||||
include(date: 1.day.ago.midnight.to_time, value: '2'),
|
||||
include(date: 0.days.ago.midnight.to_time, value: '1')
|
||||
)
|
||||
end
|
||||
|
||||
def record_interaction_activity
|
||||
ActivityTracker.increment('activity:interactions')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,16 +2,31 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::NewUsersMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
RSpec.describe Admin::Metrics::Measure::NewUsersMeasure do
|
||||
subject { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
context 'with user records' do
|
||||
before do
|
||||
3.times { Fabricate :user, created_at: 2.days.ago }
|
||||
2.times { Fabricate :user, created_at: 1.day.ago }
|
||||
Fabricate :user, created_at: 0.days.ago
|
||||
end
|
||||
|
||||
it 'returns correct user counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(3)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(date: 2.days.ago.midnight.to_time, value: '3'),
|
||||
include(date: 1.day.ago.midnight.to_time, value: '2'),
|
||||
include(date: 0.days.ago.midnight.to_time, value: '1')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,16 +2,31 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::OpenedReportsMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
RSpec.describe Admin::Metrics::Measure::OpenedReportsMeasure do
|
||||
subject { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
context 'with report records' do
|
||||
before do
|
||||
3.times { Fabricate :report, created_at: 2.days.ago }
|
||||
2.times { Fabricate :report, created_at: 1.day.ago }
|
||||
Fabricate :report, created_at: 0.days.ago
|
||||
end
|
||||
|
||||
it 'returns correct report counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(3)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(date: 2.days.ago.midnight.to_time, value: '3'),
|
||||
include(date: 1.day.ago.midnight.to_time, value: '2'),
|
||||
include(date: 0.days.ago.midnight.to_time, value: '1')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,16 +2,31 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::ResolvedReportsMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
RSpec.describe Admin::Metrics::Measure::ResolvedReportsMeasure do
|
||||
subject { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
context 'with report records' do
|
||||
before do
|
||||
3.times { Fabricate :report, action_taken_at: 2.days.ago }
|
||||
2.times { Fabricate :report, action_taken_at: 1.day.ago }
|
||||
Fabricate :report, action_taken_at: 0.days.ago
|
||||
end
|
||||
|
||||
it 'returns correct report counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(3)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(date: 2.days.ago.midnight.to_time, value: '3'),
|
||||
include(date: 1.day.ago.midnight.to_time, value: '2'),
|
||||
include(date: 0.days.ago.midnight.to_time, value: '1')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::TagAccountsMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
RSpec.describe Admin::Metrics::Measure::TagAccountsMeasure do
|
||||
subject { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let!(:tag) { Fabricate(:tag) }
|
||||
|
||||
|
|
@ -12,8 +12,39 @@ describe Admin::Metrics::Measure::TagAccountsMeasure do
|
|||
let(:params) { ActionController::Parameters.new(id: tag.id) }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
context 'with tagged accounts' do
|
||||
let(:alice) { Fabricate(:account, domain: 'alice.example') }
|
||||
let(:bob) { Fabricate(:account, domain: 'bob.example') }
|
||||
|
||||
before do
|
||||
3.times do
|
||||
travel_to(2.days.ago) { add_tag_history(alice) }
|
||||
end
|
||||
|
||||
2.times do
|
||||
travel_to(1.day.ago) do
|
||||
add_tag_history(alice)
|
||||
add_tag_history(bob)
|
||||
end
|
||||
end
|
||||
|
||||
add_tag_history(bob)
|
||||
end
|
||||
|
||||
it 'returns correct tag_accounts counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(3)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(date: 2.days.ago.midnight.to_time, value: '1'),
|
||||
include(date: 1.day.ago.midnight.to_time, value: '2'),
|
||||
include(date: 0.days.ago.midnight.to_time, value: '1')
|
||||
)
|
||||
end
|
||||
|
||||
def add_tag_history(account)
|
||||
tag.history.add(account.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::TagServersMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
RSpec.describe Admin::Metrics::Measure::TagServersMeasure do
|
||||
subject { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let!(:tag) { Fabricate(:tag) }
|
||||
|
||||
|
|
@ -12,8 +12,38 @@ describe Admin::Metrics::Measure::TagServersMeasure do
|
|||
let(:params) { ActionController::Parameters.new(id: tag.id) }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
context 'with tagged statuses' do
|
||||
let(:alice) { Fabricate(:account, domain: 'alice.example') }
|
||||
let(:bob) { Fabricate(:account, domain: 'bob.example') }
|
||||
|
||||
before do
|
||||
3.times do
|
||||
status_alice = Fabricate(:status, account: alice, created_at: 2.days.ago)
|
||||
status_alice.tags << tag
|
||||
end
|
||||
|
||||
2.times do
|
||||
status_alice = Fabricate(:status, account: alice, created_at: 1.day.ago)
|
||||
status_alice.tags << tag
|
||||
|
||||
status_bob = Fabricate(:status, account: bob, created_at: 1.day.ago)
|
||||
status_bob.tags << tag
|
||||
end
|
||||
|
||||
status_bob = Fabricate(:status, account: bob, created_at: 0.days.ago)
|
||||
status_bob.tags << tag
|
||||
end
|
||||
|
||||
it 'returns correct tag counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(3)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(date: 2.days.ago.midnight.to_time, value: '1'),
|
||||
include(date: 1.day.ago.midnight.to_time, value: '2'),
|
||||
include(date: 0.days.ago.midnight.to_time, value: '1')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::TagUsesMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
RSpec.describe Admin::Metrics::Measure::TagUsesMeasure do
|
||||
subject { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let!(:tag) { Fabricate(:tag) }
|
||||
|
||||
|
|
@ -12,8 +12,39 @@ describe Admin::Metrics::Measure::TagUsesMeasure do
|
|||
let(:params) { ActionController::Parameters.new(id: tag.id) }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
context 'with tagged accounts' do
|
||||
let(:alice) { Fabricate(:account, domain: 'alice.example') }
|
||||
let(:bob) { Fabricate(:account, domain: 'bob.example') }
|
||||
|
||||
before do
|
||||
3.times do
|
||||
travel_to(2.days.ago) { add_tag_history(alice) }
|
||||
end
|
||||
|
||||
2.times do
|
||||
travel_to(1.day.ago) do
|
||||
add_tag_history(alice)
|
||||
add_tag_history(bob)
|
||||
end
|
||||
end
|
||||
|
||||
add_tag_history(bob)
|
||||
end
|
||||
|
||||
it 'returns correct tag_uses counts' do
|
||||
expect(subject.data.size)
|
||||
.to eq(3)
|
||||
expect(subject.data.map(&:symbolize_keys))
|
||||
.to contain_exactly(
|
||||
include(date: 2.days.ago.midnight.to_time, value: '3'),
|
||||
include(date: 1.day.ago.midnight.to_time, value: '4'),
|
||||
include(date: 0.days.ago.midnight.to_time, value: '1')
|
||||
)
|
||||
end
|
||||
|
||||
def add_tag_history(account)
|
||||
tag.history.add(account.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
22
spec/lib/admin/metrics/measure_spec.rb
Normal file
22
spec/lib/admin/metrics/measure_spec.rb
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Admin::Metrics::Measure do
|
||||
describe '.retrieve' do
|
||||
subject { described_class.retrieve(reports, start_at, end_at, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:params) { ActionController::Parameters.new({ instance_accounts: [123], instance_followers: [123] }) }
|
||||
let(:reports) { [:instance_accounts, :instance_followers] }
|
||||
|
||||
it 'returns instances of provided classes' do
|
||||
expect(subject)
|
||||
.to contain_exactly(
|
||||
be_a(Admin::Metrics::Measure::InstanceAccountsMeasure),
|
||||
be_a(Admin::Metrics::Measure::InstanceFollowersMeasure)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::SystemCheck::BaseCheck do
|
||||
RSpec.describe Admin::SystemCheck::BaseCheck do
|
||||
subject(:check) { described_class.new(user) }
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::SystemCheck::DatabaseSchemaCheck do
|
||||
RSpec.describe Admin::SystemCheck::DatabaseSchemaCheck do
|
||||
subject(:check) { described_class.new(user) }
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::SystemCheck::ElasticsearchCheck do
|
||||
RSpec.describe Admin::SystemCheck::ElasticsearchCheck do
|
||||
subject(:check) { described_class.new(user) }
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
|
@ -127,7 +127,7 @@ describe Admin::SystemCheck::ElasticsearchCheck do
|
|||
end
|
||||
|
||||
def stub_elasticsearch_error
|
||||
client = instance_double(Elasticsearch::Transport::Client)
|
||||
client = instance_double(Elasticsearch::Client)
|
||||
allow(client).to receive(:info).and_raise(Elasticsearch::Transport::Transport::Error)
|
||||
allow(Chewy).to receive(:client).and_return(client)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::SystemCheck::MediaPrivacyCheck do
|
||||
RSpec.describe Admin::SystemCheck::MediaPrivacyCheck do
|
||||
subject(:check) { described_class.new(user) }
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::SystemCheck::Message do
|
||||
RSpec.describe Admin::SystemCheck::Message do
|
||||
subject(:check) { described_class.new(:key_value, :value_value, :action_value, :critical_value) }
|
||||
|
||||
it 'providers readers when initialized' do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::SystemCheck::RulesCheck do
|
||||
RSpec.describe Admin::SystemCheck::RulesCheck do
|
||||
subject(:check) { described_class.new(user) }
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::SystemCheck::SidekiqProcessCheck do
|
||||
RSpec.describe Admin::SystemCheck::SidekiqProcessCheck do
|
||||
subject(:check) { described_class.new(user) }
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::SystemCheck::SoftwareVersionCheck do
|
||||
RSpec.describe Admin::SystemCheck::SoftwareVersionCheck do
|
||||
include RoutingHelper
|
||||
|
||||
subject(:check) { described_class.new(user) }
|
||||
|
|
@ -51,8 +51,8 @@ describe Admin::SystemCheck::SoftwareVersionCheck do
|
|||
Fabricate(:software_update, version: '99.99.99', type: 'major', urgent: false)
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
expect(check.pass?).to be true
|
||||
it 'returns false' do
|
||||
expect(check.pass?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::SystemCheck do
|
||||
RSpec.describe Admin::SystemCheck do
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
describe 'perform' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AnnualReport::CommonlyInteractedWithAccounts do
|
||||
describe '#generate' do
|
||||
subject { described_class.new(account, Time.zone.now.year) }
|
||||
|
||||
context 'with an inactive account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
commonly_interacted_with_accounts: be_an(Array).and(be_empty)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an active account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
let(:other_account) { Fabricate :account }
|
||||
|
||||
before do
|
||||
_other = Fabricate :status
|
||||
Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: other_account).id
|
||||
Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: other_account).id
|
||||
end
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
commonly_interacted_with_accounts: contain_exactly(
|
||||
include(account_id: other_account.id, count: 2)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
41
spec/lib/annual_report/most_reblogged_accounts_spec.rb
Normal file
41
spec/lib/annual_report/most_reblogged_accounts_spec.rb
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AnnualReport::MostRebloggedAccounts do
|
||||
describe '#generate' do
|
||||
subject { described_class.new(account, Time.zone.now.year) }
|
||||
|
||||
context 'with an inactive account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
most_reblogged_accounts: be_an(Array).and(be_empty)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an active account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
let(:other_account) { Fabricate :account }
|
||||
|
||||
before do
|
||||
_other = Fabricate :status
|
||||
Fabricate :status, account: account, reblog: Fabricate(:status, account: other_account)
|
||||
Fabricate :status, account: account, reblog: Fabricate(:status, account: other_account)
|
||||
end
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
most_reblogged_accounts: contain_exactly(
|
||||
include(account_id: other_account.id, count: 2)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
40
spec/lib/annual_report/most_used_apps_spec.rb
Normal file
40
spec/lib/annual_report/most_used_apps_spec.rb
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AnnualReport::MostUsedApps do
|
||||
describe '#generate' do
|
||||
subject { described_class.new(account, Time.zone.now.year) }
|
||||
|
||||
context 'with an inactive account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
most_used_apps: be_an(Array).and(be_empty)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an active account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
let(:application) { Fabricate :application }
|
||||
|
||||
before do
|
||||
_other = Fabricate :status
|
||||
Fabricate.times 2, :status, account: account, application: application
|
||||
end
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
most_used_apps: contain_exactly(
|
||||
include(name: application.name, count: 2)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
44
spec/lib/annual_report/percentiles_spec.rb
Normal file
44
spec/lib/annual_report/percentiles_spec.rb
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AnnualReport::Percentiles do
|
||||
describe '#generate' do
|
||||
subject { described_class.new(account, Time.zone.now.year) }
|
||||
|
||||
context 'with an inactive account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
percentiles: include(
|
||||
followers: 0,
|
||||
statuses: 0
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an active account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
before do
|
||||
Fabricate.times 2, :status # Others as `account`
|
||||
Fabricate.times 2, :follow # Others as `target_account`
|
||||
Fabricate.times 2, :status, account: account
|
||||
Fabricate.times 2, :follow, target_account: account
|
||||
end
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
percentiles: include(
|
||||
followers: 50,
|
||||
statuses: 50
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
46
spec/lib/annual_report/time_series_spec.rb
Normal file
46
spec/lib/annual_report/time_series_spec.rb
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AnnualReport::TimeSeries do
|
||||
describe '#generate' do
|
||||
subject { described_class.new(account, Time.zone.now.year) }
|
||||
|
||||
context 'with an inactive account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
time_series: match(
|
||||
include(followers: 0, following: 0, month: 1, statuses: 0)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an active account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
let(:month_one_date) { DateTime.new(Time.zone.now.year, 1, 1, 12, 12, 12) }
|
||||
|
||||
let(:tag) { Fabricate :tag }
|
||||
|
||||
before do
|
||||
_other = Fabricate :status
|
||||
Fabricate :status, account: account, created_at: month_one_date
|
||||
Fabricate :follow, account: account, created_at: month_one_date
|
||||
Fabricate :follow, target_account: account, created_at: month_one_date
|
||||
end
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
time_series: match(
|
||||
include(followers: 1, following: 1, month: 1, statuses: 1)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
43
spec/lib/annual_report/top_hashtags_spec.rb
Normal file
43
spec/lib/annual_report/top_hashtags_spec.rb
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AnnualReport::TopHashtags do
|
||||
describe '#generate' do
|
||||
subject { described_class.new(account, Time.zone.now.year) }
|
||||
|
||||
context 'with an inactive account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
top_hashtags: be_an(Array).and(be_empty)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an active account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
let(:tag) { Fabricate :tag }
|
||||
|
||||
before do
|
||||
_other = Fabricate :status
|
||||
first = Fabricate :status, account: account
|
||||
first.tags << tag
|
||||
last = Fabricate :status, account: account
|
||||
last.tags << tag
|
||||
end
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
top_hashtags: contain_exactly(
|
||||
include(name: tag.name, count: 2)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
50
spec/lib/annual_report/top_statuses_spec.rb
Normal file
50
spec/lib/annual_report/top_statuses_spec.rb
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AnnualReport::TopStatuses do
|
||||
describe '#generate' do
|
||||
subject { described_class.new(account, Time.zone.now.year) }
|
||||
|
||||
context 'with an inactive account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
top_statuses: include(
|
||||
by_reblogs: be_nil,
|
||||
by_favourites: be_nil,
|
||||
by_replies: be_nil
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an active account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
let(:reblogged_status) { Fabricate :status, account: account }
|
||||
let(:favourited_status) { Fabricate :status, account: account }
|
||||
let(:replied_status) { Fabricate :status, account: account }
|
||||
|
||||
before do
|
||||
_other = Fabricate :status
|
||||
reblogged_status.status_stat.update(reblogs_count: 123)
|
||||
favourited_status.status_stat.update(favourites_count: 123)
|
||||
replied_status.status_stat.update(replies_count: 123)
|
||||
end
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
top_statuses: include(
|
||||
by_reblogs: reblogged_status.id,
|
||||
by_favourites: favourited_status.id,
|
||||
by_replies: replied_status.id
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
48
spec/lib/annual_report/type_distribution_spec.rb
Normal file
48
spec/lib/annual_report/type_distribution_spec.rb
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AnnualReport::TypeDistribution do
|
||||
describe '#generate' do
|
||||
subject { described_class.new(account, Time.zone.now.year) }
|
||||
|
||||
context 'with an inactive account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
type_distribution: include(
|
||||
total: 0,
|
||||
reblogs: 0,
|
||||
replies: 0,
|
||||
standalone: 0
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an active account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
before do
|
||||
_other = Fabricate :status
|
||||
Fabricate :status, reblog: Fabricate(:status), account: account
|
||||
Fabricate :status, in_reply_to_id: Fabricate(:status).id, account: account, reply: true
|
||||
Fabricate :status, account: account
|
||||
end
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
type_distribution: include(
|
||||
total: 3,
|
||||
reblogs: 1,
|
||||
replies: 1,
|
||||
standalone: 1
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
16
spec/lib/annual_report_spec.rb
Normal file
16
spec/lib/annual_report_spec.rb
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AnnualReport do
|
||||
describe '#generate' do
|
||||
subject { described_class.new(account, Time.zone.now.year) }
|
||||
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect { subject.generate }
|
||||
.to change(GeneratedAnnualReport, :count).by(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe CacheBuster do
|
||||
RSpec.describe CacheBuster do
|
||||
subject { described_class.new(secret_header: secret_header, secret: secret, http_method: http_method) }
|
||||
|
||||
let(:secret_header) { nil }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe ConnectionPool::SharedConnectionPool do
|
||||
RSpec.describe ConnectionPool::SharedConnectionPool do
|
||||
subject { described_class.new(size: 5, timeout: 5) { |site| mini_connection_class.new(site) } }
|
||||
|
||||
let(:mini_connection_class) do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe ConnectionPool::SharedTimedStack do
|
||||
RSpec.describe ConnectionPool::SharedTimedStack do
|
||||
subject { described_class.new(5) { |site| mini_connection_class.new(site) } }
|
||||
|
||||
let(:mini_connection_class) do
|
||||
|
|
|
|||
141
spec/lib/content_security_policy_spec.rb
Normal file
141
spec/lib/content_security_policy_spec.rb
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ContentSecurityPolicy do
|
||||
subject { described_class.new }
|
||||
|
||||
around do |example|
|
||||
original_asset_host = Rails.configuration.action_controller.asset_host
|
||||
original_web_domain = Rails.configuration.x.web_domain
|
||||
original_use_https = Rails.configuration.x.use_https
|
||||
example.run
|
||||
Rails.configuration.action_controller.asset_host = original_asset_host
|
||||
Rails.configuration.x.web_domain = original_web_domain
|
||||
Rails.configuration.x.use_https = original_use_https
|
||||
end
|
||||
|
||||
describe '#base_host' do
|
||||
before { Rails.configuration.x.web_domain = 'host.example' }
|
||||
|
||||
it 'returns the configured value for the web domain' do
|
||||
expect(subject.base_host).to eq 'host.example'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#assets_host' do
|
||||
context 'when asset_host is not configured' do
|
||||
before { Rails.configuration.action_controller.asset_host = nil }
|
||||
|
||||
context 'with a configured web domain' do
|
||||
before { Rails.configuration.x.web_domain = 'host.example' }
|
||||
|
||||
context 'when use_https is enabled' do
|
||||
before { Rails.configuration.x.use_https = true }
|
||||
|
||||
it 'returns value from base host with https protocol' do
|
||||
expect(subject.assets_host).to eq 'https://host.example'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when use_https is disabled' do
|
||||
before { Rails.configuration.x.use_https = false }
|
||||
|
||||
it 'returns value from base host with http protocol' do
|
||||
expect(subject.assets_host).to eq 'http://host.example'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when asset_host is configured' do
|
||||
before do
|
||||
Rails.configuration.action_controller.asset_host = 'https://assets.host.example'
|
||||
end
|
||||
|
||||
it 'returns full value from configured host' do
|
||||
expect(subject.assets_host).to eq 'https://assets.host.example'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#media_hosts' do
|
||||
context 'when there is no configured CDN' do
|
||||
it 'defaults to using the assets_host value' do
|
||||
expect(subject.media_hosts).to contain_exactly(subject.assets_host)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an S3 alias host is configured' do
|
||||
around do |example|
|
||||
ClimateControl.modify S3_ALIAS_HOST: 'asset-host.s3-alias.example' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses the s3 alias host value' do
|
||||
expect(subject.media_hosts).to contain_exactly(subject.assets_host, 'https://asset-host.s3-alias.example')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an S3 alias host with a trailing path is configured' do
|
||||
around do |example|
|
||||
ClimateControl.modify S3_ALIAS_HOST: 'asset-host.s3-alias.example/pathname' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses the s3 alias host value and preserves the path' do
|
||||
expect(subject.media_hosts).to contain_exactly(subject.assets_host, 'https://asset-host.s3-alias.example/pathname/')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an S3 cloudfront host is configured' do
|
||||
around do |example|
|
||||
ClimateControl.modify S3_CLOUDFRONT_HOST: 'asset-host.s3-cloudfront.example' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses the s3 cloudfront host value' do
|
||||
expect(subject.media_hosts).to contain_exactly(subject.assets_host, 'https://asset-host.s3-cloudfront.example')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an azure alias host is configured' do
|
||||
around do |example|
|
||||
ClimateControl.modify AZURE_ALIAS_HOST: 'asset-host.azure-alias.example' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses the azure alias host value' do
|
||||
expect(subject.media_hosts).to contain_exactly(subject.assets_host, 'https://asset-host.azure-alias.example')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when s3_enabled is configured' do
|
||||
around do |example|
|
||||
ClimateControl.modify S3_ENABLED: 'true', S3_HOSTNAME: 'asset-host.s3.example' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses the s3 hostname host value' do
|
||||
expect(subject.media_hosts).to contain_exactly(subject.assets_host, 'https://asset-host.s3.example')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when PAPERCLIP_ROOT_URL is configured' do
|
||||
around do |example|
|
||||
ClimateControl.modify PAPERCLIP_ROOT_URL: 'https://paperclip-host.example' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses the provided URL in the content security policy' do
|
||||
expect(subject.media_hosts).to contain_exactly(subject.assets_host, 'https://paperclip-host.example')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe DeliveryFailureTracker do
|
||||
RSpec.describe DeliveryFailureTracker do
|
||||
subject { described_class.new('http://example.com/inbox') }
|
||||
|
||||
describe '#track_success!' do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Extractor do
|
||||
RSpec.describe Extractor do
|
||||
describe 'extract_mentions_or_lists_with_indices' do
|
||||
it 'returns an empty array if the given string does not have at signs' do
|
||||
text = 'a string without at signs'
|
||||
|
|
@ -69,10 +69,10 @@ describe Extractor do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'extract_cashtags_with_indices' do
|
||||
it 'returns []' do
|
||||
describe 'extract_entities_with_indices' do
|
||||
it 'returns empty array when cashtag present' do
|
||||
text = '$cashtag'
|
||||
extracted = described_class.extract_cashtags_with_indices(text)
|
||||
extracted = described_class.extract_entities_with_indices(text)
|
||||
expect(extracted).to eq []
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe FastIpMap do
|
||||
RSpec.describe FastIpMap do
|
||||
describe '#include?' do
|
||||
subject { described_class.new([IPAddr.new('20.4.0.0/16'), IPAddr.new('145.22.30.0/24'), IPAddr.new('189.45.86.3')]) }
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ RSpec.describe FeedManager do
|
|||
end
|
||||
end
|
||||
|
||||
it 'tracks at least as many statuses as reblogs', skip_stub: true do
|
||||
expect(FeedManager::REBLOG_FALLOFF).to be <= FeedManager::MAX_ITEMS
|
||||
it 'tracks at least as many statuses as reblogs', :skip_stub do
|
||||
expect(described_class::REBLOG_FALLOFF).to be <= described_class::MAX_ITEMS
|
||||
end
|
||||
|
||||
describe '#key' do
|
||||
|
|
@ -206,13 +206,13 @@ RSpec.describe FeedManager do
|
|||
expect(described_class.instance.filter?(:mentions, reply, bob)).to be true
|
||||
end
|
||||
|
||||
it 'returns true for status by silenced account who recipient is not following' do
|
||||
it 'returns false for status by limited account who recipient is not following' do
|
||||
status = Fabricate(:status, text: 'Hello world', account: alice)
|
||||
alice.silence!
|
||||
expect(described_class.instance.filter?(:mentions, status, bob)).to be true
|
||||
expect(described_class.instance.filter?(:mentions, status, bob)).to be false
|
||||
end
|
||||
|
||||
it 'returns false for status by followed silenced account' do
|
||||
it 'returns false for status by followed limited account' do
|
||||
status = Fabricate(:status, text: 'Hello world', account: alice)
|
||||
alice.silence!
|
||||
bob.follow!(alice)
|
||||
|
|
@ -225,12 +225,12 @@ RSpec.describe FeedManager do
|
|||
it 'trims timelines if they will have more than FeedManager::MAX_ITEMS' do
|
||||
account = Fabricate(:account)
|
||||
status = Fabricate(:status)
|
||||
members = Array.new(FeedManager::MAX_ITEMS) { |count| [count, count] }
|
||||
members = Array.new(described_class::MAX_ITEMS) { |count| [count, count] }
|
||||
redis.zadd("feed:home:#{account.id}", members)
|
||||
|
||||
described_class.instance.push_to_home(account, status)
|
||||
|
||||
expect(redis.zcard("feed:home:#{account.id}")).to eq FeedManager::MAX_ITEMS
|
||||
expect(redis.zcard("feed:home:#{account.id}")).to eq described_class::MAX_ITEMS
|
||||
end
|
||||
|
||||
context 'with reblogs' do
|
||||
|
|
@ -260,7 +260,7 @@ RSpec.describe FeedManager do
|
|||
described_class.instance.push_to_home(account, reblogged)
|
||||
|
||||
# Fill the feed with intervening statuses
|
||||
FeedManager::REBLOG_FALLOFF.times do
|
||||
described_class::REBLOG_FALLOFF.times do
|
||||
described_class.instance.push_to_home(account, Fabricate(:status))
|
||||
end
|
||||
|
||||
|
|
@ -321,7 +321,7 @@ RSpec.describe FeedManager do
|
|||
described_class.instance.push_to_home(account, reblogs.first)
|
||||
|
||||
# Fill the feed with intervening statuses
|
||||
FeedManager::REBLOG_FALLOFF.times do
|
||||
described_class::REBLOG_FALLOFF.times do
|
||||
described_class.instance.push_to_home(account, Fabricate(:status))
|
||||
end
|
||||
|
||||
|
|
@ -467,7 +467,7 @@ RSpec.describe FeedManager do
|
|||
status = Fabricate(:status, reblog: reblogged)
|
||||
|
||||
described_class.instance.push_to_home(receiver, reblogged)
|
||||
FeedManager::REBLOG_FALLOFF.times { described_class.instance.push_to_home(receiver, Fabricate(:status)) }
|
||||
described_class::REBLOG_FALLOFF.times { described_class.instance.push_to_home(receiver, Fabricate(:status)) }
|
||||
described_class.instance.push_to_home(receiver, status)
|
||||
|
||||
# The reblogging status should show up under normal conditions.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe HashtagNormalizer do
|
||||
RSpec.describe HashtagNormalizer do
|
||||
subject { described_class.new }
|
||||
|
||||
describe '#normalize' do
|
||||
|
|
|
|||
|
|
@ -41,6 +41,14 @@ RSpec.describe HtmlAwareFormatter do
|
|||
expect(subject).to_not include 'status__content__spoiler-link'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when given text containing ruby tags for east-asian languages' do
|
||||
let(:text) { '<ruby>明日 <rp>(</rp><rt>Ashita</rt><rp>)</rp></ruby>' }
|
||||
|
||||
it 'keeps the ruby tags' do
|
||||
expect(subject).to eq '<ruby>明日 <rp>(</rp><rt>Ashita</rt><rp>)</rp></ruby>'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Importer::AccountsIndexImporter do
|
||||
RSpec.describe Importer::AccountsIndexImporter do
|
||||
describe 'import!' do
|
||||
let(:pool) { Concurrent::FixedThreadPool.new(5) }
|
||||
let(:importer) { described_class.new(batch_size: 123, executor: pool) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Importer::BaseImporter do
|
||||
RSpec.describe Importer::BaseImporter do
|
||||
describe 'import!' do
|
||||
let(:pool) { Concurrent::FixedThreadPool.new(5) }
|
||||
let(:importer) { described_class.new(batch_size: 123, executor: pool) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Importer::PublicStatusesIndexImporter do
|
||||
RSpec.describe Importer::PublicStatusesIndexImporter do
|
||||
describe 'import!' do
|
||||
let(:pool) { Concurrent::FixedThreadPool.new(5) }
|
||||
let(:importer) { described_class.new(batch_size: 123, executor: pool) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Importer::StatusesIndexImporter do
|
||||
RSpec.describe Importer::StatusesIndexImporter do
|
||||
describe 'import!' do
|
||||
let(:pool) { Concurrent::FixedThreadPool.new(5) }
|
||||
let(:importer) { described_class.new(batch_size: 123, executor: pool) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Importer::TagsIndexImporter do
|
||||
RSpec.describe Importer::TagsIndexImporter do
|
||||
describe 'import!' do
|
||||
let(:pool) { Concurrent::FixedThreadPool.new(5) }
|
||||
let(:importer) { described_class.new(batch_size: 123, executor: pool) }
|
||||
|
|
|
|||
|
|
@ -33,6 +33,14 @@ RSpec.describe LinkDetailsExtractor do
|
|||
expect(subject.canonical_url).to eq original_url
|
||||
end
|
||||
end
|
||||
|
||||
context 'when canonical URL is set to "undefined"' do
|
||||
let(:url) { 'undefined' }
|
||||
|
||||
it 'ignores the canonical URLs' do
|
||||
expect(subject.canonical_url).to eq original_url
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when only basic metadata is present' do
|
||||
|
|
@ -46,22 +54,13 @@ RSpec.describe LinkDetailsExtractor do
|
|||
</html>
|
||||
HTML
|
||||
|
||||
describe '#title' do
|
||||
it 'returns the title from title tag' do
|
||||
expect(subject.title).to eq 'Man bites dog'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#description' do
|
||||
it 'returns the description from meta tag' do
|
||||
expect(subject.description).to eq "A dog's tale"
|
||||
end
|
||||
end
|
||||
|
||||
describe '#language' do
|
||||
it 'returns the language from lang attribute' do
|
||||
expect(subject.language).to eq 'en'
|
||||
end
|
||||
it 'extracts the expected values from html metadata' do
|
||||
expect(subject)
|
||||
.to have_attributes(
|
||||
title: eq('Man bites dog'),
|
||||
description: eq("A dog's tale"),
|
||||
language: eq('en')
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -90,34 +89,16 @@ RSpec.describe LinkDetailsExtractor do
|
|||
end
|
||||
|
||||
shared_examples 'structured data' do
|
||||
describe '#title' do
|
||||
it 'returns the title from structured data' do
|
||||
expect(subject.title).to eq 'Man bites dog'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#description' do
|
||||
it 'returns the description from structured data' do
|
||||
expect(subject.description).to eq "A dog's tale"
|
||||
end
|
||||
end
|
||||
|
||||
describe '#published_at' do
|
||||
it 'returns the publicaton time from structured data' do
|
||||
expect(subject.published_at).to eq '2022-01-31T19:53:00+00:00'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#author_name' do
|
||||
it 'returns the author name from structured data' do
|
||||
expect(subject.author_name).to eq 'Charlie Brown'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#provider_name' do
|
||||
it 'returns the provider name from structured data' do
|
||||
expect(subject.provider_name).to eq 'Pet News'
|
||||
end
|
||||
it 'extracts the expected values from structured data' do
|
||||
expect(subject)
|
||||
.to have_attributes(
|
||||
title: eq('Man bites dog'),
|
||||
description: eq("A dog's tale"),
|
||||
published_at: eq('2022-01-31T19:53:00+00:00'),
|
||||
author_name: eq('Charlie Brown'),
|
||||
provider_name: eq('Pet News'),
|
||||
language: eq('en')
|
||||
)
|
||||
end
|
||||
|
||||
describe '#language' do
|
||||
|
|
@ -162,6 +143,24 @@ RSpec.describe LinkDetailsExtractor do
|
|||
include_examples 'structured data'
|
||||
end
|
||||
|
||||
context 'with the first tag is null' do
|
||||
let(:html) { <<~HTML }
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<script type="application/ld+json">
|
||||
null
|
||||
</script>
|
||||
<script type="application/ld+json">
|
||||
#{ld_json}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
|
||||
include_examples 'structured data'
|
||||
end
|
||||
|
||||
context 'with preceding block of unsupported LD+JSON' do
|
||||
let(:html) { <<~HTML }
|
||||
<!doctype html>
|
||||
|
|
@ -225,6 +224,35 @@ RSpec.describe LinkDetailsExtractor do
|
|||
|
||||
include_examples 'structured data'
|
||||
end
|
||||
|
||||
context 'with author names as array' do
|
||||
let(:ld_json) do
|
||||
{
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'NewsArticle',
|
||||
'headline' => 'A lot of authors',
|
||||
'description' => 'But we decided to cram them into one',
|
||||
'author' => {
|
||||
'@type' => 'Person',
|
||||
'name' => ['Author 1', 'Author 2'],
|
||||
},
|
||||
}.to_json
|
||||
end
|
||||
let(:html) { <<~HTML }
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<script type="application/ld+json">
|
||||
#{ld_json}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
|
||||
it 'joins author names' do
|
||||
expect(subject.author_name).to eq 'Author 1, Author 2'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Open Graph protocol data is present' do
|
||||
|
|
@ -245,58 +273,19 @@ RSpec.describe LinkDetailsExtractor do
|
|||
</html>
|
||||
HTML
|
||||
|
||||
describe '#canonical_url' do
|
||||
it 'returns the URL from Open Graph protocol data' do
|
||||
expect(subject.canonical_url).to eq 'https://example.com/dog.html'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#title' do
|
||||
it 'returns the title from Open Graph protocol data' do
|
||||
expect(subject.title).to eq 'Man bites dog'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#description' do
|
||||
it 'returns the description from Open Graph protocol data' do
|
||||
expect(subject.description).to eq "A dog's tale"
|
||||
end
|
||||
end
|
||||
|
||||
describe '#published_at' do
|
||||
it 'returns the publicaton time from Open Graph protocol data' do
|
||||
expect(subject.published_at).to eq '2022-01-31T19:53:00+00:00'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#author_name' do
|
||||
it 'returns the author name from Open Graph protocol data' do
|
||||
expect(subject.author_name).to eq 'Charlie Brown'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#language' do
|
||||
it 'returns the language from Open Graph protocol data' do
|
||||
expect(subject.language).to eq 'en'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#image' do
|
||||
it 'returns the image from Open Graph protocol data' do
|
||||
expect(subject.image).to eq 'https://example.com/snoopy.jpg'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#image:alt' do
|
||||
it 'returns the image description from Open Graph protocol data' do
|
||||
expect(subject.image_alt).to eq 'A good boy'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#provider_name' do
|
||||
it 'returns the provider name from Open Graph protocol data' do
|
||||
expect(subject.provider_name).to eq 'Pet News'
|
||||
end
|
||||
it 'extracts the expected values from open graph data' do
|
||||
expect(subject)
|
||||
.to have_attributes(
|
||||
canonical_url: eq('https://example.com/dog.html'),
|
||||
title: eq('Man bites dog'),
|
||||
description: eq("A dog's tale"),
|
||||
published_at: eq('2022-01-31T19:53:00+00:00'),
|
||||
author_name: eq('Charlie Brown'),
|
||||
language: eq('en'),
|
||||
image: eq('https://example.com/snoopy.jpg'),
|
||||
image_alt: eq('A good boy'),
|
||||
provider_name: eq('Pet News')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -3,27 +3,30 @@
|
|||
require 'rails_helper'
|
||||
require 'mastodon/cli/cache'
|
||||
|
||||
describe Mastodon::CLI::Cache do
|
||||
let(:cli) { described_class.new }
|
||||
RSpec.describe Mastodon::CLI::Cache do
|
||||
subject { cli.invoke(action, arguments, options) }
|
||||
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
end
|
||||
end
|
||||
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) }
|
||||
|
|
@ -33,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
|
||||
|
|
@ -50,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
|
||||
|
|
@ -62,9 +63,8 @@ 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 raise_error(Thor::Error, /Unknown/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,47 +3,46 @@
|
|||
require 'rails_helper'
|
||||
require 'mastodon/cli/canonical_email_blocks'
|
||||
|
||||
describe Mastodon::CLI::CanonicalEmailBlocks do
|
||||
let(:cli) { described_class.new }
|
||||
RSpec.describe Mastodon::CLI::CanonicalEmailBlocks do
|
||||
subject { cli.invoke(action, arguments, options) }
|
||||
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
end
|
||||
end
|
||||
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
|
||||
|
|
@ -51,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
|
||||
|
|
|
|||
|
|
@ -3,10 +3,93 @@
|
|||
require 'rails_helper'
|
||||
require 'mastodon/cli/domains'
|
||||
|
||||
describe Mastodon::CLI::Domains do
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
RSpec.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 invalid limited federation mode argument' do
|
||||
let(:arguments) { ['example.host'] }
|
||||
let(:options) { { limited_federation_mode: true } }
|
||||
|
||||
it 'warns about usage and exits' do
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, /DOMAIN parameter not supported/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without a domains argument' do
|
||||
it 'warns about usage and exits' do
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, 'No domain(s) given')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with accounts from the domain' do
|
||||
let(:domain) { 'host.example' }
|
||||
let!(:account) { Fabricate(:account, domain: domain) }
|
||||
let(:arguments) { [domain] }
|
||||
|
||||
it 'removes the account' do
|
||||
expect { subject }
|
||||
.to output_results('Removed 1 accounts')
|
||||
|
||||
expect { account.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#crawl' do
|
||||
let(:action) { :crawl }
|
||||
|
||||
context 'with accounts from the domain' do
|
||||
let(:domain) { 'host.example' }
|
||||
|
||||
before do
|
||||
Fabricate(:account, domain: domain)
|
||||
stub_request(:get, 'https://host.example/api/v1/instance').to_return(status: 200, body: {}.to_json)
|
||||
stub_request(:get, 'https://host.example/api/v1/instance/peers').to_return(status: 200, body: {}.to_json)
|
||||
stub_request(:get, 'https://host.example/api/v1/instance/activity').to_return(status: 200, body: {}.to_json)
|
||||
stub_const('Mastodon::CLI::Domains::CRAWL_SLEEP_TIME', 0)
|
||||
end
|
||||
|
||||
context 'with --format of summary' do
|
||||
let(:options) { { format: 'summary' } }
|
||||
|
||||
it 'crawls the domains and summarizes results' do
|
||||
expect { subject }
|
||||
.to output_results('Visited 1 domains, 0 failed')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --format of domains' do
|
||||
let(:options) { { format: 'domains' } }
|
||||
|
||||
it 'crawls the domains and summarizes results' do
|
||||
expect { subject }
|
||||
.to output_results(domain)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --format of json' do
|
||||
let(:options) { { format: 'json' } }
|
||||
|
||||
it 'crawls the domains and summarizes results' do
|
||||
expect { subject }
|
||||
.to output_results(json_summary)
|
||||
end
|
||||
|
||||
def json_summary
|
||||
Oj.dump('host.example': { activity: {} })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,10 +3,100 @@
|
|||
require 'rails_helper'
|
||||
require 'mastodon/cli/email_domain_blocks'
|
||||
|
||||
describe Mastodon::CLI::EmailDomainBlocks do
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
RSpec.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) }
|
||||
|
||||
it 'lists the blocks' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
parent_block.domain,
|
||||
child_block.domain
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#add' do
|
||||
let(:action) { :add }
|
||||
|
||||
context 'without any options' do
|
||||
it 'warns about usage and exits' do
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, 'No domain(s) given')
|
||||
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 { subject }
|
||||
.to output_results('is already blocked')
|
||||
.and(not_change(EmailDomainBlock, :count))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no blocks exist' do
|
||||
let(:domain) { 'host.example' }
|
||||
let(:arguments) { [domain] }
|
||||
|
||||
it 'adds a new block' do
|
||||
expect { subject }
|
||||
.to output_results('Added 1')
|
||||
.and(change(EmailDomainBlock, :count).by(1))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#remove' do
|
||||
let(:action) { :remove }
|
||||
|
||||
context 'without any options' do
|
||||
it 'warns about usage and exits' do
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, 'No domain(s) given')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when blocks exist' do
|
||||
let(:domain) { 'host.example' }
|
||||
let(:arguments) { [domain] }
|
||||
|
||||
before { Fabricate(:email_domain_block, domain: domain) }
|
||||
|
||||
it 'removes the block' do
|
||||
expect { subject }
|
||||
.to output_results('Removed 1')
|
||||
.and(change(EmailDomainBlock, :count).by(-1))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no blocks exist' do
|
||||
let(:domain) { 'host.example' }
|
||||
let(:arguments) { [domain] }
|
||||
|
||||
it 'does not remove a block' do
|
||||
expect { subject }
|
||||
.to output_results('is not yet blocked')
|
||||
.and(not_change(EmailDomainBlock, :count))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,10 +3,62 @@
|
|||
require 'rails_helper'
|
||||
require 'mastodon/cli/emoji'
|
||||
|
||||
describe Mastodon::CLI::Emoji do
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
RSpec.describe Mastodon::CLI::Emoji 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 existing custom emoji' do
|
||||
before { Fabricate(:custom_emoji) }
|
||||
|
||||
it 'reports a successful purge' do
|
||||
expect { subject }
|
||||
.to output_results('OK')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#import' do
|
||||
context 'with existing custom emoji' do
|
||||
let(:import_path) { Rails.root.join('spec', 'fixtures', 'files', 'elite-assets.tar.gz') }
|
||||
let(:action) { :import }
|
||||
let(:arguments) { [import_path] }
|
||||
|
||||
it 'reports about imported emoji' do
|
||||
expect { subject }
|
||||
.to output_results('Imported 1')
|
||||
.and change(CustomEmoji, :count).by(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#export' do
|
||||
context 'with existing custom emoji' do
|
||||
before do
|
||||
FileUtils.rm_rf(export_path.dirname)
|
||||
FileUtils.mkdir_p(export_path.dirname)
|
||||
|
||||
Fabricate(:custom_emoji)
|
||||
end
|
||||
|
||||
after { FileUtils.rm_rf(export_path.dirname) }
|
||||
|
||||
let(:export_path) { Rails.root.join('tmp', 'cli-tests', 'export.tar.gz') }
|
||||
let(:arguments) { [export_path.dirname.to_s] }
|
||||
let(:action) { :export }
|
||||
|
||||
it 'reports about exported emoji' do
|
||||
expect { subject }
|
||||
.to output_results('Exported 1')
|
||||
.and change { File.exist?(export_path) }.from(false).to(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,25 +3,26 @@
|
|||
require 'rails_helper'
|
||||
require 'mastodon/cli/feeds'
|
||||
|
||||
describe Mastodon::CLI::Feeds do
|
||||
let(:cli) { described_class.new }
|
||||
RSpec.describe Mastodon::CLI::Feeds do
|
||||
subject { cli.invoke(action, arguments, options) }
|
||||
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
end
|
||||
end
|
||||
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
|
||||
|
||||
|
|
@ -31,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
|
||||
|
||||
|
|
@ -41,22 +41,22 @@ 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 raise_error(Thor::Error, 'No such account')
|
||||
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
|
||||
|
|
|
|||
|
|
@ -3,16 +3,17 @@
|
|||
require 'rails_helper'
|
||||
require 'mastodon/cli/ip_blocks'
|
||||
|
||||
describe Mastodon::CLI::IpBlocks do
|
||||
let(:cli) { described_class.new }
|
||||
RSpec.describe Mastodon::CLI::IpBlocks do
|
||||
subject { cli.invoke(action, arguments, options) }
|
||||
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
end
|
||||
end
|
||||
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',
|
||||
|
|
@ -29,29 +30,28 @@ 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)
|
||||
|
||||
blocked_ip_addresses = IpBlock.where(ip: ip_list).pluck(:ip)
|
||||
expected_ip_addresses = ip_list.map { |ip| IPAddr.new(ip) }
|
||||
|
||||
expect(blocked_ip_addresses).to match_array(expected_ip_addresses)
|
||||
def blocked_ip_addresses
|
||||
IpBlock.where(ip: ip_list).pluck(:ip)
|
||||
end
|
||||
|
||||
it 'sets the severity for all blocked IP addresses' do
|
||||
cli.invoke(:add, ip_list, options)
|
||||
|
||||
blocked_ips_severity = IpBlock.where(ip: ip_list).pluck(:severity).all?(options[:severity])
|
||||
|
||||
expect(blocked_ips_severity).to be(true)
|
||||
def expected_ip_addresses
|
||||
ip_list.map { |ip| IPAddr.new(ip) }
|
||||
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
|
||||
def blocked_ips_severity
|
||||
IpBlock.where(ip: ip_list).pluck(:severity).all?(options[:severity])
|
||||
end
|
||||
|
||||
it 'blocks and sets severity for ip address and displays summary' do
|
||||
expect { subject }
|
||||
.to output_results("Added #{ip_list.size}, skipped 0, failed 0")
|
||||
expect(blocked_ip_addresses)
|
||||
.to match_array(expected_ip_addresses)
|
||||
expect(blocked_ips_severity)
|
||||
.to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -61,28 +61,25 @@ 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
|
||||
before { allow(IpBlock).to receive(:new).and_call_original }
|
||||
|
||||
cli.invoke(:add, ip_list, options)
|
||||
it 'skips already block ip and displays the correct summary' do
|
||||
expect { subject }
|
||||
.to output_results("#{ip_list.last} is already blocked\nAdded #{ip_list.size - 1}, skipped 1, failed 0")
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
context 'with --force option' do
|
||||
let!(:blocked_ip) { IpBlock.create(ip: ip_list.last, severity: 'no_access') }
|
||||
let(:options) { { severity: 'sign_up_requires_approval', force: true } }
|
||||
|
||||
it 'overwrites the existing IP block record' do
|
||||
expect { cli.invoke(:add, ip_list, options) }
|
||||
.to change { blocked_ip.reload.severity }
|
||||
expect { subject }
|
||||
.to output_results('Added 11')
|
||||
.and change { blocked_ip.reload.severity }
|
||||
.from('no_access')
|
||||
.to('sign_up_requires_approval')
|
||||
end
|
||||
|
|
@ -93,11 +90,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
|
||||
|
||||
|
|
@ -128,6 +125,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)
|
||||
|
|
@ -136,24 +134,24 @@ 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
|
||||
.and raise_error(SystemExit)
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, 'No IP(s) given')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#remove' do
|
||||
let(:action) { :remove }
|
||||
|
||||
context 'when removing exact matches' do
|
||||
let(:ip_list) do
|
||||
[
|
||||
|
|
@ -170,22 +168,17 @@ 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)
|
||||
|
||||
it 'removes exact ip blocks and displays success message with a summary' do
|
||||
expect { subject }
|
||||
.to output_results("Removed #{ip_list.size}, skipped 0")
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --force option' do
|
||||
|
|
@ -195,62 +188,60 @@ describe Mastodon::CLI::IpBlocks do
|
|||
let(:arguments) { ['192.168.0.5', '10.0.1.50'] }
|
||||
let(:options) { { force: true } }
|
||||
|
||||
it 'removes blocks for IP ranges that cover given IP(s)' do
|
||||
cli.invoke(:remove, arguments, options)
|
||||
it 'removes blocks for IP ranges that cover given IP(s) and keeps other ranges' do
|
||||
expect { subject }
|
||||
.to output_results('Removed 2')
|
||||
|
||||
expect(IpBlock.where(id: [first_ip_range_block.id, second_ip_range_block.id])).to_not exist
|
||||
expect(covered_ranges).to_not exist
|
||||
expect(other_ranges).to exist
|
||||
end
|
||||
|
||||
it 'does not remove other IP ranges' do
|
||||
cli.invoke(:remove, arguments, options)
|
||||
def covered_ranges
|
||||
IpBlock.where(id: [first_ip_range_block.id, second_ip_range_block.id])
|
||||
end
|
||||
|
||||
expect(IpBlock.where(id: third_ip_range_block.id)).to exist
|
||||
def other_ranges
|
||||
IpBlock.where(id: third_ip_range_block.id)
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
it 'displays the summary correctly' do
|
||||
expect { cli.invoke(:remove, [unblocked_ip]) }.to output(
|
||||
a_string_including('Removed 0, skipped 1')
|
||||
).to_stdout
|
||||
it 'skips the IP address and displays summary' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
"#{unblocked_ip} is not yet blocked",
|
||||
'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
|
||||
end
|
||||
|
||||
it 'displays the summary correctly' do
|
||||
expect { cli.invoke(:remove, [invalid_ip]) }.to output(
|
||||
a_string_including('Removed 0, skipped 1')
|
||||
).to_stdout
|
||||
it 'skips the invalid IP address and displays summary' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
"#{invalid_ip} is invalid",
|
||||
'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
|
||||
.and raise_error(SystemExit)
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, 'No IP(s) given')
|
||||
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) }
|
||||
|
|
@ -259,15 +250,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
|
||||
it 'does not export blocked IPs with different severities' do
|
||||
expect { subject }
|
||||
.to_not output_results("#{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -275,23 +264,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
|
||||
it 'does not export blocked IPs with different severities' do
|
||||
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
|
||||
|
|
|
|||
|
|
@ -3,18 +3,174 @@
|
|||
require 'rails_helper'
|
||||
require 'mastodon/cli/main'
|
||||
|
||||
describe Mastodon::CLI::Main do
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
RSpec.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
|
||||
let(:action) { :version }
|
||||
|
||||
it 'returns the Mastodon version' do
|
||||
expect { subject }
|
||||
.to output_results(Mastodon::Version.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'version' do
|
||||
it 'returns the Mastodon version' do
|
||||
expect { described_class.new.invoke(:version) }.to output(
|
||||
a_string_including(Mastodon::Version.to_s)
|
||||
).to_stdout
|
||||
describe '#self_destruct' do
|
||||
let(:action) { :self_destruct }
|
||||
|
||||
context 'with self destruct mode enabled' do
|
||||
before do
|
||||
allow(SelfDestructHelper).to receive(:self_destruct?).and_return(true)
|
||||
end
|
||||
|
||||
context 'with pending accounts' do
|
||||
before { Fabricate(:account) }
|
||||
|
||||
it 'reports about pending accounts' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'already enabled',
|
||||
'still pending deletion'
|
||||
)
|
||||
.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with sidekiq notices being processed' do
|
||||
before do
|
||||
Account.delete_all
|
||||
stats_double = instance_double(Sidekiq::Stats, enqueued: 5)
|
||||
allow(Sidekiq::Stats).to receive(:new).and_return(stats_double)
|
||||
end
|
||||
|
||||
it 'reports about notices' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'already enabled',
|
||||
'notices are still being'
|
||||
)
|
||||
.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with sidekiq failed deliveries' do
|
||||
before do
|
||||
Account.delete_all
|
||||
stats_double = instance_double(Sidekiq::Stats, enqueued: 0, retry_size: 10)
|
||||
allow(Sidekiq::Stats).to receive(:new).and_return(stats_double)
|
||||
end
|
||||
|
||||
it 'reports about notices' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'already enabled',
|
||||
'some have failed and are scheduled'
|
||||
)
|
||||
.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with self descruct mode ready' do
|
||||
before do
|
||||
Account.delete_all
|
||||
stats_double = instance_double(Sidekiq::Stats, enqueued: 0, retry_size: 0)
|
||||
allow(Sidekiq::Stats).to receive(:new).and_return(stats_double)
|
||||
end
|
||||
|
||||
it 'reports about notices' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'already enabled',
|
||||
'can safely delete all data'
|
||||
)
|
||||
.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with self destruct mode disabled' do
|
||||
before do
|
||||
allow(SelfDestructHelper).to receive(:self_destruct?).and_return(false)
|
||||
end
|
||||
|
||||
context 'with an incorrect response to hostname' do
|
||||
before do
|
||||
answer_hostname_incorrectly
|
||||
end
|
||||
|
||||
it 'exits with mismatch error message' do
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, /Domains do not match/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a correct response to hostname but no to proceed' do
|
||||
before do
|
||||
answer_hostname_correctly
|
||||
decline_proceed
|
||||
end
|
||||
|
||||
it 'passes first step but stops before instructions' do
|
||||
expect { subject }
|
||||
.to output_results('operation WILL NOT')
|
||||
.and raise_error(Thor::Error, /Self-destruct will not begin/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a correct response to hostname and yes to proceed' do
|
||||
before do
|
||||
answer_hostname_correctly
|
||||
accept_proceed
|
||||
end
|
||||
|
||||
it 'instructs to set the appropriate environment variable' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'operation WILL NOT',
|
||||
'the following variable'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def answer_hostname_incorrectly
|
||||
allow(cli.shell)
|
||||
.to receive(:ask)
|
||||
.with('Type in the domain of the server to confirm:')
|
||||
.and_return('wrong.host')
|
||||
.once
|
||||
end
|
||||
|
||||
def answer_hostname_correctly
|
||||
allow(cli.shell)
|
||||
.to receive(:ask)
|
||||
.with('Type in the domain of the server to confirm:')
|
||||
.and_return(Rails.configuration.x.local_domain)
|
||||
.once
|
||||
end
|
||||
|
||||
def decline_proceed
|
||||
allow(cli.shell)
|
||||
.to receive(:no?)
|
||||
.with('Are you sure you want to proceed?')
|
||||
.and_return(true)
|
||||
.once
|
||||
end
|
||||
|
||||
def accept_proceed
|
||||
allow(cli.shell)
|
||||
.to receive(:no?)
|
||||
.with('Are you sure you want to proceed?')
|
||||
.and_return(false)
|
||||
.once
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,10 +3,601 @@
|
|||
require 'rails_helper'
|
||||
require 'mastodon/cli/maintenance'
|
||||
|
||||
describe Mastodon::CLI::Maintenance do
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
RSpec.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 { subject }
|
||||
.to raise_error(Thor::Error, /is too old/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the database version is too new and the user does not continue' do
|
||||
before do
|
||||
allow(ActiveRecord::Migrator).to receive(:current_version).and_return(2100_01_01_000000) # Later than maximum
|
||||
allow(cli.shell).to receive(:yes?).with('Continue anyway? (Yes/No)').and_return(false).once
|
||||
end
|
||||
|
||||
it 'Exits with error message' do
|
||||
expect { subject }
|
||||
.to output_results('more recent')
|
||||
.and raise_error(Thor::Error, /more recent/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Sidekiq is running' do
|
||||
before do
|
||||
allow(ActiveRecord::Migrator).to receive(:current_version).and_return(2022_01_01_000000) # Higher than minimum, lower than maximum
|
||||
allow(Sidekiq::ProcessSet).to receive(:new).and_return [:process]
|
||||
end
|
||||
|
||||
it 'Exits with error message' do
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, /Sidekiq is running/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when requirements are met' do
|
||||
before do
|
||||
allow(ActiveRecord::Migrator).to receive(:current_version).and_return(2023_08_22_081029) # The latest migration before the cutoff
|
||||
agree_to_backup_warning
|
||||
end
|
||||
|
||||
context 'with duplicate accounts' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
choose_local_account_to_keep
|
||||
end
|
||||
|
||||
let(:duplicate_account_username) { 'username' }
|
||||
let(:duplicate_account_domain) { 'host.example' }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Deduplicating accounts',
|
||||
'Multiple local accounts were found for',
|
||||
'Restoring index_accounts_on_username_and_domain_lower',
|
||||
'Reindexing textual indexes on accounts…',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_remote_accounts, :count).from(2).to(1)
|
||||
.and change(duplicate_local_accounts, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_remote_accounts
|
||||
Account.where(username: duplicate_account_username, domain: duplicate_account_domain)
|
||||
end
|
||||
|
||||
def duplicate_local_accounts
|
||||
Account.where(username: duplicate_account_username, domain: nil)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :accounts, name: :index_accounts_on_username_and_domain_lower
|
||||
_remote_account = Fabricate(:account, username: duplicate_account_username, domain: duplicate_account_domain)
|
||||
_remote_account_dupe = Fabricate.build(:account, username: duplicate_account_username, domain: duplicate_account_domain).save(validate: false)
|
||||
_local_account = Fabricate(:account, username: duplicate_account_username, domain: nil)
|
||||
_local_account_dupe = Fabricate.build(:account, username: duplicate_account_username, domain: nil).save(validate: false)
|
||||
end
|
||||
|
||||
def choose_local_account_to_keep
|
||||
allow(cli.shell)
|
||||
.to receive(:ask)
|
||||
.with(/Account to keep unchanged/, anything)
|
||||
.and_return('0')
|
||||
.once
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate users on email' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:duplicate_email) { 'duplicate@example.host' }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Deduplicating user records',
|
||||
'Restoring users indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_users, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_users
|
||||
User.where(email: duplicate_email)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :users, :email
|
||||
Fabricate(:user, email: duplicate_email)
|
||||
Fabricate.build(:user, email: duplicate_email).save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate users on confirmation_token' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:duplicate_confirmation_token) { '123ABC' }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Deduplicating user records',
|
||||
'Unsetting confirmation token',
|
||||
'Restoring users indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_users, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_users
|
||||
User.where(confirmation_token: duplicate_confirmation_token)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :users, :confirmation_token
|
||||
Fabricate(:user, confirmation_token: duplicate_confirmation_token)
|
||||
Fabricate.build(:user, confirmation_token: duplicate_confirmation_token).save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate users on reset_password_token' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:duplicate_reset_password_token) { '123ABC' }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Deduplicating user records',
|
||||
'Unsetting password reset token',
|
||||
'Restoring users indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_users, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_users
|
||||
User.where(reset_password_token: duplicate_reset_password_token)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :users, :reset_password_token
|
||||
Fabricate(:user, reset_password_token: duplicate_reset_password_token)
|
||||
Fabricate.build(:user, reset_password_token: duplicate_reset_password_token).save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate account_domain_blocks' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:duplicate_domain) { 'example.host' }
|
||||
let(:account) { Fabricate(:account) }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Removing duplicate account domain blocks',
|
||||
'Restoring account domain blocks indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_account_domain_blocks, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_account_domain_blocks
|
||||
AccountDomainBlock.where(account: account, domain: duplicate_domain)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :account_domain_blocks, [:account_id, :domain]
|
||||
Fabricate(:account_domain_block, account: account, domain: duplicate_domain)
|
||||
Fabricate.build(:account_domain_block, account: account, domain: duplicate_domain).save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate announcement_reactions' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:account) { Fabricate(:account) }
|
||||
let(:announcement) { Fabricate(:announcement) }
|
||||
let(:name) { Fabricate(:custom_emoji).shortcode }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Removing duplicate announcement reactions',
|
||||
'Restoring announcement_reactions indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_announcement_reactions, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_announcement_reactions
|
||||
AnnouncementReaction.where(account: account, announcement: announcement, name: name)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :announcement_reactions, [:account_id, :announcement_id, :name]
|
||||
Fabricate(:announcement_reaction, account: account, announcement: announcement, name: name)
|
||||
Fabricate.build(:announcement_reaction, account: account, announcement: announcement, name: name).save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate conversations' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:uri) { 'https://example.host/path' }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Deduplicating conversations',
|
||||
'Restoring conversations indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_conversations, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_conversations
|
||||
Conversation.where(uri: uri)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :conversations, :uri
|
||||
Fabricate(:conversation, uri: uri)
|
||||
Fabricate.build(:conversation, uri: uri).save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate custom_emojis' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:duplicate_shortcode) { 'wowzers' }
|
||||
let(:duplicate_domain) { 'example.host' }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Deduplicating custom_emojis',
|
||||
'Restoring custom_emojis indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_custom_emojis, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_custom_emojis
|
||||
CustomEmoji.where(shortcode: duplicate_shortcode, domain: duplicate_domain)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :custom_emojis, [:shortcode, :domain]
|
||||
Fabricate(:custom_emoji, shortcode: duplicate_shortcode, domain: duplicate_domain)
|
||||
Fabricate.build(:custom_emoji, shortcode: duplicate_shortcode, domain: duplicate_domain).save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate custom_emoji_categories' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:duplicate_name) { 'name_value' }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Deduplicating custom_emoji_categories',
|
||||
'Restoring custom_emoji_categories indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_custom_emoji_categories, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_custom_emoji_categories
|
||||
CustomEmojiCategory.where(name: duplicate_name)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :custom_emoji_categories, :name
|
||||
Fabricate(:custom_emoji_category, name: duplicate_name)
|
||||
Fabricate.build(:custom_emoji_category, name: duplicate_name).save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate domain_allows' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:domain) { 'example.host' }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Deduplicating domain_allows',
|
||||
'Restoring domain_allows indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_domain_allows, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_domain_allows
|
||||
DomainAllow.where(domain: domain)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :domain_allows, :domain
|
||||
Fabricate(:domain_allow, domain: domain)
|
||||
Fabricate.build(:domain_allow, domain: domain).save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate domain_blocks' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:domain) { 'example.host' }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Deduplicating domain_blocks',
|
||||
'Restoring domain_blocks indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_domain_blocks, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_domain_blocks
|
||||
DomainBlock.where(domain: domain)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :domain_blocks, :domain
|
||||
Fabricate(:domain_block, domain: domain)
|
||||
Fabricate.build(:domain_block, domain: domain).save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate email_domain_blocks' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:domain) { 'example.host' }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Deduplicating email_domain_blocks',
|
||||
'Restoring email_domain_blocks indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_email_domain_blocks, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_email_domain_blocks
|
||||
EmailDomainBlock.where(domain: domain)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :email_domain_blocks, :domain
|
||||
Fabricate(:email_domain_block, domain: domain)
|
||||
Fabricate.build(:email_domain_block, domain: domain).save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate media_attachments' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:shortcode) { 'codenam' }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Deduplicating media_attachments',
|
||||
'Restoring media_attachments indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_media_attachments, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_media_attachments
|
||||
MediaAttachment.where(shortcode: shortcode)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :media_attachments, :shortcode
|
||||
Fabricate(:media_attachment, shortcode: shortcode)
|
||||
Fabricate.build(:media_attachment, shortcode: shortcode).save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate preview_cards' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:url) { 'https://example.host/path' }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Deduplicating preview_cards',
|
||||
'Restoring preview_cards indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_preview_cards, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_preview_cards
|
||||
PreviewCard.where(url: url)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :preview_cards, :url
|
||||
Fabricate(:preview_card, url: url)
|
||||
Fabricate.build(:preview_card, url: url).save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate statuses' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:uri) { 'https://example.host/path' }
|
||||
let(:account) { Fabricate(:account) }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Deduplicating statuses',
|
||||
'Restoring statuses indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_statuses, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_statuses
|
||||
Status.where(uri: uri)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :statuses, :uri
|
||||
Fabricate(:status, account: account, uri: uri)
|
||||
duplicate = Fabricate.build(:status, account: account, uri: uri)
|
||||
duplicate.save(validate: false)
|
||||
Fabricate(:status_pin, account: account, status: duplicate)
|
||||
Fabricate(:status, in_reply_to_id: duplicate.id)
|
||||
Fabricate(:status, reblog_of_id: duplicate.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate tags' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:name) { 'tagname' }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Deduplicating tags',
|
||||
'Restoring tags indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_tags, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_tags
|
||||
Tag.where(name: name)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :tags, name: 'index_tags_on_name_lower_btree'
|
||||
Fabricate(:tag, name: name)
|
||||
Fabricate.build(:tag, name: name).save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate webauthn_credentials' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:external_id) { '123_123_123' }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Deduplicating webauthn_credentials',
|
||||
'Restoring webauthn_credentials indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_webauthn_credentials, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_webauthn_credentials
|
||||
WebauthnCredential.where(external_id: external_id)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :webauthn_credentials, :external_id
|
||||
Fabricate(:webauthn_credential, external_id: external_id)
|
||||
Fabricate.build(:webauthn_credential, external_id: external_id).save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate webhooks' do
|
||||
before do
|
||||
prepare_duplicate_data
|
||||
end
|
||||
|
||||
let(:url) { 'https://example.host/path' }
|
||||
|
||||
it 'runs the deduplication process' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Deduplicating webhooks',
|
||||
'Restoring webhooks indexes',
|
||||
'Finished!'
|
||||
)
|
||||
.and change(duplicate_webhooks, :count).from(2).to(1)
|
||||
end
|
||||
|
||||
def duplicate_webhooks
|
||||
Webhook.where(url: url)
|
||||
end
|
||||
|
||||
def prepare_duplicate_data
|
||||
ActiveRecord::Base.connection.remove_index :webhooks, :url
|
||||
Fabricate(:webhook, url: url)
|
||||
Fabricate.build(:webhook, url: url).save(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
def agree_to_backup_warning
|
||||
allow(cli.shell)
|
||||
.to receive(:yes?)
|
||||
.with('Continue? (Yes/No)')
|
||||
.and_return(true)
|
||||
.once
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,10 +3,247 @@
|
|||
require 'rails_helper'
|
||||
require 'mastodon/cli/media'
|
||||
|
||||
describe Mastodon::CLI::Media do
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
RSpec.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 { subject }
|
||||
.to raise_error(Thor::Error, '--prune-profiles and --remove-headers should not be specified simultaneously')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --include-follows but not including --prune-profiles and --remove-headers' do
|
||||
let(:options) { { include_follows: true } }
|
||||
|
||||
it 'warns about usage and exits' do
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, '--include-follows can only be used with --prune-profiles or --remove-headers')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a relevant account' do
|
||||
let!(:account) do
|
||||
Fabricate(:account, domain: 'example.com', updated_at: 1.month.ago, last_webfingered_at: 1.month.ago, avatar: attachment_fixture('attachment.jpg'), header: attachment_fixture('attachment.jpg'))
|
||||
end
|
||||
|
||||
context 'with --prune-profiles' do
|
||||
let(:options) { { prune_profiles: true } }
|
||||
|
||||
it 'removes account avatars' do
|
||||
expect { subject }
|
||||
.to output_results('Visited 1')
|
||||
|
||||
expect(account.reload.avatar).to be_blank
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --remove-headers' do
|
||||
let(:options) { { remove_headers: true } }
|
||||
|
||||
it 'removes account header' do
|
||||
expect { subject }
|
||||
.to output_results('Visited 1')
|
||||
|
||||
expect(account.reload.header).to be_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a relevant media attachment' do
|
||||
let!(:media_attachment) { Fabricate(:media_attachment, remote_url: 'https://example.com/image.jpg', created_at: 1.month.ago) }
|
||||
|
||||
context 'without options' do
|
||||
it 'removes account avatars' do
|
||||
expect { subject }
|
||||
.to output_results('Removed 1')
|
||||
|
||||
expect(media_attachment.reload.file).to be_blank
|
||||
expect(media_attachment.reload.thumbnail).to be_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#usage' do
|
||||
let(:action) { :usage }
|
||||
|
||||
context 'without options' do
|
||||
it 'reports about storage size' do
|
||||
expect { subject }
|
||||
.to output_results('0 Bytes')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#lookup' do
|
||||
let(:action) { :lookup }
|
||||
let(:arguments) { [url] }
|
||||
|
||||
context 'with valid url not connected to a record' do
|
||||
let(:url) { 'https://example.host/assets/1' }
|
||||
|
||||
it 'warns about url and exits' do
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, 'Not a media URL')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a valid media url' do
|
||||
let(:status) { Fabricate(:status) }
|
||||
let(:media_attachment) { Fabricate(:media_attachment, status: status) }
|
||||
let(:url) { media_attachment.file.url(:original) }
|
||||
|
||||
it 'displays the url of a connected status' do
|
||||
expect { subject }
|
||||
.to output_results(status.id.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#refresh' do
|
||||
let(:action) { :refresh }
|
||||
|
||||
context 'without any options' do
|
||||
it 'warns about usage and exits' do
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, /Specify the source/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --status option' do
|
||||
before do
|
||||
media_attachment.update(file_file_name: nil)
|
||||
end
|
||||
|
||||
let(:media_attachment) { Fabricate(:media_attachment, status: status, remote_url: 'https://host.example/asset.jpg') }
|
||||
let(:options) { { status: status.id } }
|
||||
let(:status) { Fabricate(:status) }
|
||||
|
||||
it 'redownloads the attachment file' do
|
||||
expect { subject }
|
||||
.to output_results('Downloaded 1 media')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --account option' do
|
||||
context 'when the account does not exist' do
|
||||
let(:options) { { account: 'not-real-user@example.host' } }
|
||||
|
||||
it 'warns about usage and exits' do
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, 'No such account')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the account exists' do
|
||||
before do
|
||||
media_attachment.update(file_file_name: nil)
|
||||
end
|
||||
|
||||
let(:media_attachment) { Fabricate(:media_attachment, account: account) }
|
||||
let(:options) { { account: account.acct } }
|
||||
let(:account) { Fabricate(:account) }
|
||||
|
||||
it 'redownloads the attachment file' do
|
||||
expect { subject }
|
||||
.to output_results('Downloaded 1 media')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --domain option' do
|
||||
before do
|
||||
media_attachment.update(file_file_name: nil)
|
||||
end
|
||||
|
||||
let(:domain) { 'example.host' }
|
||||
let(:media_attachment) { Fabricate(:media_attachment, account: account) }
|
||||
let(:options) { { domain: domain } }
|
||||
let(:account) { Fabricate(:account, domain: domain) }
|
||||
|
||||
it 'redownloads the attachment file' do
|
||||
expect { subject }
|
||||
.to output_results('Downloaded 1 media')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --days option' do
|
||||
before do
|
||||
Fabricate(:media_attachment, remote_url: 'https://example.com/image.jpg', id: Mastodon::Snowflake.id_at(50.days.ago))
|
||||
Fabricate(:media_attachment, remote_url: 'https://example.com/image.jpg', id: Mastodon::Snowflake.id_at(5.days.ago))
|
||||
Fabricate(:media_attachment, remote_url: '', id: Mastodon::Snowflake.id_at(5.days.ago))
|
||||
end
|
||||
|
||||
let(:options) { { days: 10 } }
|
||||
|
||||
it 'redownloads the attachment file for the remote records more recent than the option' do
|
||||
expect { subject }
|
||||
.to output_results('Downloaded 1 media')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#remove_orphans' do
|
||||
let(:action) { :remove_orphans }
|
||||
|
||||
before do
|
||||
FileUtils.mkdir_p Rails.public_path.join('system')
|
||||
end
|
||||
|
||||
context 'without any options' do
|
||||
it 'runs without error' do
|
||||
expect { subject }
|
||||
.to output_results('Removed', 'orphans (approx')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when in azure mode' do
|
||||
before do
|
||||
allow(Paperclip::Attachment).to receive(:default_options).and_return(storage: :azure)
|
||||
end
|
||||
|
||||
it 'warns about usage and exits' do
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, /azure storage driver is not supported/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when in fog mode' do
|
||||
before do
|
||||
allow(Paperclip::Attachment).to receive(:default_options).and_return(storage: :fog)
|
||||
end
|
||||
|
||||
it 'warns about usage and exits' do
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, /fog storage driver is not supported/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when in filesystem mode' do
|
||||
before do
|
||||
allow(File).to receive(:delete).and_return(true)
|
||||
media_attachment.delete
|
||||
end
|
||||
|
||||
let(:media_attachment) { Fabricate(:media_attachment) }
|
||||
|
||||
it 'removes the unlinked files' do
|
||||
expect { subject }
|
||||
.to output_results('Removed', 'orphans (approx')
|
||||
expect(File).to have_received(:delete).with(media_attachment.file.path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,10 +3,58 @@
|
|||
require 'rails_helper'
|
||||
require 'mastodon/cli/preview_cards'
|
||||
|
||||
describe Mastodon::CLI::PreviewCards do
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
RSpec.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)
|
||||
Fabricate(:preview_card, updated_at: 10.months.ago, type: :photo)
|
||||
Fabricate(:preview_card, updated_at: 10.days.ago, type: :photo)
|
||||
end
|
||||
|
||||
context 'with no arguments' do
|
||||
it 'deletes thumbnails for local preview cards' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Removed 2 preview cards',
|
||||
'approx. 119 KB'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the --link option' do
|
||||
let(:options) { { link: true } }
|
||||
|
||||
it 'deletes thumbnails for local preview cards' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Removed 1 link-type preview cards',
|
||||
'approx. 59.6 KB'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the --days option' do
|
||||
let(:options) { { days: 365 } }
|
||||
|
||||
it 'deletes thumbnails for local preview cards' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
'Removed 1 preview cards',
|
||||
'approx. 59.6 KB'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,10 +3,89 @@
|
|||
require 'rails_helper'
|
||||
require 'mastodon/cli/search'
|
||||
|
||||
describe Mastodon::CLI::Search do
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
RSpec.describe Mastodon::CLI::Search do
|
||||
subject { cli.invoke(action, arguments, options) }
|
||||
|
||||
let(:cli) { described_class.new }
|
||||
let(:arguments) { [] }
|
||||
let(:options) { {} }
|
||||
|
||||
it_behaves_like 'CLI Command'
|
||||
|
||||
describe '#deploy' do
|
||||
let(:action) { :deploy }
|
||||
|
||||
context 'with concurrency out of range' do
|
||||
let(:options) { { concurrency: -100 } }
|
||||
|
||||
it 'Exits with error message' do
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, /this concurrency setting/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with batch size out of range' do
|
||||
let(:options) { { batch_size: -100_000 } }
|
||||
|
||||
it 'Exits with error message' do
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, /this batch_size setting/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when server communication raises an error' do
|
||||
let(:options) { { reset_chewy: true } }
|
||||
|
||||
before { allow(Chewy::Stash::Specification).to receive(:reset!).and_raise(Elasticsearch::Transport::Transport::Errors::InternalServerError) }
|
||||
|
||||
it 'Exits with error message' do
|
||||
expect { subject }
|
||||
.to raise_error(Thor::Error, /issue connecting to the search/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without options' do
|
||||
before { stub_search_indexes }
|
||||
|
||||
let(:indexed_count) { 1 }
|
||||
let(:deleted_count) { 2 }
|
||||
|
||||
it 'reports about storage size' do
|
||||
expect { subject }
|
||||
.to output_results(
|
||||
"Indexed #{described_class::INDICES.size * indexed_count} records",
|
||||
"de-indexed #{described_class::INDICES.size * deleted_count}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def stub_search_indexes
|
||||
described_class::INDICES.each do |index|
|
||||
allow(index)
|
||||
.to receive_messages(
|
||||
specification: instance_double(Chewy::Index::Specification, changed?: true, lock!: nil),
|
||||
purge: nil
|
||||
)
|
||||
|
||||
importer_double = importer_double_for(index)
|
||||
allow(importer_double).to receive(:on_progress).and_yield([indexed_count, deleted_count])
|
||||
allow("Importer::#{index}Importer".constantize)
|
||||
.to receive(:new)
|
||||
.and_return(importer_double)
|
||||
end
|
||||
end
|
||||
|
||||
def importer_double_for(index)
|
||||
instance_double(
|
||||
"Importer::#{index}Importer".constantize,
|
||||
clean_up!: nil,
|
||||
estimate!: 100,
|
||||
import!: nil,
|
||||
on_failure: nil,
|
||||
# on_progress: nil,
|
||||
optimize_for_import!: nil,
|
||||
optimize_for_search!: nil
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,67 +3,58 @@
|
|||
require 'rails_helper'
|
||||
require 'mastodon/cli/settings'
|
||||
|
||||
describe Mastodon::CLI::Settings do
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
end
|
||||
end
|
||||
RSpec.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
|
||||
it 'changes "registrations_mode" to "open"' do
|
||||
expect { cli.open }.to change(Setting, :registrations_mode).from(nil).to('open')
|
||||
end
|
||||
let(:action) { :open }
|
||||
|
||||
it 'displays success message' do
|
||||
expect { cli.open }.to output(
|
||||
a_string_including('OK')
|
||||
).to_stdout
|
||||
it 'changes "registrations_mode" to "open" and displays success' do
|
||||
expect { subject }
|
||||
.to change(Setting, :registrations_mode).from(nil).to('open')
|
||||
.and output_results('OK')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#approved' do
|
||||
it 'changes "registrations_mode" to "approved"' do
|
||||
expect { cli.approved }.to change(Setting, :registrations_mode).from(nil).to('approved')
|
||||
end
|
||||
let(:action) { :approved }
|
||||
|
||||
it 'displays success message' do
|
||||
expect { cli.approved }.to output(
|
||||
a_string_including('OK')
|
||||
).to_stdout
|
||||
it 'changes "registrations_mode" to "approved" and displays success' do
|
||||
expect { subject }
|
||||
.to change(Setting, :registrations_mode).from(nil).to('approved')
|
||||
.and 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')
|
||||
end
|
||||
|
||||
it 'sets "require_invite_text" to "true"' do
|
||||
expect { cli.approved }.to change(Setting, :require_invite_text).from(false).to(true)
|
||||
it 'changes registrations_mode and require_invite_text' do
|
||||
expect { subject }
|
||||
.to output_results('OK')
|
||||
.and change(Setting, :registrations_mode).from(nil).to('approved')
|
||||
.and change(Setting, :require_invite_text).from(false).to(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#close' do
|
||||
it 'changes "registrations_mode" to "none"' do
|
||||
expect { cli.close }.to change(Setting, :registrations_mode).from(nil).to('none')
|
||||
end
|
||||
let(:action) { :close }
|
||||
|
||||
it 'displays success message' do
|
||||
expect { cli.close }.to output(
|
||||
a_string_including('OK')
|
||||
).to_stdout
|
||||
it 'changes "registrations_mode" to "none" and displays success' do
|
||||
expect { subject }
|
||||
.to change(Setting, :registrations_mode).from(nil).to('none')
|
||||
.and output_results('OK')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,10 +3,32 @@
|
|||
require 'rails_helper'
|
||||
require 'mastodon/cli/statuses'
|
||||
|
||||
describe Mastodon::CLI::Statuses do
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
RSpec.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 { subject }
|
||||
.to raise_error(Thor::Error, /Cannot run/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with default batch size' do
|
||||
it 'removes unreferenced statuses' do
|
||||
expect { subject }
|
||||
.to output_results('Done after')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,10 +3,28 @@
|
|||
require 'rails_helper'
|
||||
require 'mastodon/cli/upgrade'
|
||||
|
||||
describe Mastodon::CLI::Upgrade do
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
RSpec.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
|
||||
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 { subject }
|
||||
.to output_results('Upgraded storage schema of 0 records')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
require 'rails_helper'
|
||||
require 'mastodon/migration_warning'
|
||||
|
||||
describe Mastodon::MigrationWarning do
|
||||
RSpec.describe Mastodon::MigrationWarning do
|
||||
describe 'migration_duration_warning' do
|
||||
before do
|
||||
allow(migration).to receive(:valid_environment?).and_return(true)
|
||||
|
|
|
|||
263
spec/lib/mastodon/redis_configuration_spec.rb
Normal file
263
spec/lib/mastodon/redis_configuration_spec.rb
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Mastodon::RedisConfiguration do
|
||||
let(:redis_environment) { described_class.new }
|
||||
|
||||
before do
|
||||
# We use one numbered namespace per parallel test runner
|
||||
# in the test env. This here should test the non-test
|
||||
# behavior, so we disable it temporarily.
|
||||
allow(Rails.env).to receive(:test?).and_return(false)
|
||||
end
|
||||
|
||||
shared_examples 'setting a different driver' do
|
||||
context 'when setting the `REDIS_DRIVER` variable to `ruby`' do
|
||||
around do |example|
|
||||
ClimateControl.modify REDIS_DRIVER: 'ruby' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'sets the driver accordingly' do
|
||||
expect(subject[:driver]).to eq :ruby
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'setting a namespace' do
|
||||
context 'when setting the `REDIS_NAMESPACE` variable' do
|
||||
around do |example|
|
||||
ClimateControl.modify REDIS_NAMESPACE: 'testns' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses the value for the namespace' do
|
||||
expect(subject[:namespace]).to eq 'testns'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'secondary configuration' do |prefix|
|
||||
context "when no `#{prefix}_REDIS_` environment variables are present" do
|
||||
it 'uses the url from the base config' do
|
||||
expect(subject[:url]).to eq 'redis://localhost:6379/0'
|
||||
end
|
||||
|
||||
context 'when the base config uses sentinel' do
|
||||
around do |example|
|
||||
ClimateControl.modify REDIS_SENTINELS: '192.168.0.1:3000,192.168.0.2:4000', REDIS_SENTINEL_MASTER: 'mainsentinel' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses the sentinel configuration from base config' do
|
||||
expect(subject[:url]).to eq 'redis://mainsentinel/0'
|
||||
expect(subject[:name]).to eq 'mainsentinel'
|
||||
expect(subject[:sentinels]).to contain_exactly({ host: '192.168.0.1', port: 3000 }, { host: '192.168.0.2', port: 4000 })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the `#{prefix}_REDIS_URL` environment variable is present" do
|
||||
around do |example|
|
||||
ClimateControl.modify "#{prefix}_REDIS_URL": 'redis::/user@other.example.com/4' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses the provided URL' do
|
||||
expect(subject[:url]).to eq 'redis::/user@other.example.com/4'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when giving separate environment variables' do
|
||||
around do |example|
|
||||
ClimateControl.modify "#{prefix}_REDIS_PASSWORD": 'testpass1', "#{prefix}_REDIS_HOST": 'redis2.example.com', "#{prefix}_REDIS_PORT": '3322', "#{prefix}_REDIS_DB": '8' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'constructs the url from them' do
|
||||
expect(subject[:url]).to eq 'redis://:testpass1@redis2.example.com:3322/8'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'sentinel support' do |prefix = nil|
|
||||
prefix = prefix ? "#{prefix}_" : ''
|
||||
|
||||
context 'when configuring sentinel support' do
|
||||
around do |example|
|
||||
ClimateControl.modify "#{prefix}REDIS_PASSWORD": 'testpass1', "#{prefix}REDIS_HOST": 'redis2.example.com', "#{prefix}REDIS_SENTINELS": '192.168.0.1:3000,192.168.0.2:4000', "#{prefix}REDIS_SENTINEL_MASTER": 'mainsentinel' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'constructs the url using the sentinel master name' do
|
||||
expect(subject[:url]).to eq 'redis://:testpass1@mainsentinel/0'
|
||||
end
|
||||
|
||||
it 'uses the redis password to authenticate with sentinels' do
|
||||
expect(subject[:sentinel_password]).to eq 'testpass1'
|
||||
end
|
||||
|
||||
it 'includes the sentinel master name and list of sentinels' do
|
||||
expect(subject[:name]).to eq 'mainsentinel'
|
||||
expect(subject[:sentinels]).to contain_exactly({ host: '192.168.0.1', port: 3000 }, { host: '192.168.0.2', port: 4000 })
|
||||
end
|
||||
|
||||
context "when giving dedicated credentials in `#{prefix}REDIS_SENTINEL_USERNAME` and `#{prefix}REDIS_SENTINEL_PASSWORD`" do
|
||||
around do |example|
|
||||
ClimateControl.modify "#{prefix}REDIS_SENTINEL_USERNAME": 'sentinel_user', "#{prefix}REDIS_SENTINEL_PASSWORD": 'sentinel_pass1' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses the credential to authenticate with sentinels' do
|
||||
expect(subject[:sentinel_username]).to eq 'sentinel_user'
|
||||
expect(subject[:sentinel_password]).to eq 'sentinel_pass1'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when giving sentinels without port numbers' do
|
||||
context "when no default port is given via `#{prefix}REDIS_SENTINEL_PORT`" do
|
||||
around do |example|
|
||||
ClimateControl.modify "#{prefix}REDIS_SENTINELS": '192.168.0.1,192.168.0.2', "#{prefix}REDIS_SENTINEL_MASTER": 'mainsentinel' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses the default sentinel port' do
|
||||
expect(subject[:sentinels]).to contain_exactly({ host: '192.168.0.1', port: 26_379 }, { host: '192.168.0.2', port: 26_379 })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when adding port numbers to some, but not all sentinels' do
|
||||
around do |example|
|
||||
ClimateControl.modify "#{prefix}REDIS_SENTINELS": '192.168.0.1:5678,192.168.0.2', "#{prefix}REDIS_SENTINEL_MASTER": 'mainsentinel' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses the given port number when available and the default otherwise' do
|
||||
expect(subject[:sentinels]).to contain_exactly({ host: '192.168.0.1', port: 5678 }, { host: '192.168.0.2', port: 26_379 })
|
||||
end
|
||||
end
|
||||
|
||||
context "when a default port is given via `#{prefix}REDIS_SENTINEL_PORT`" do
|
||||
around do |example|
|
||||
ClimateControl.modify "#{prefix}REDIS_SENTINEL_PORT": '1234', "#{prefix}REDIS_SENTINELS": '192.168.0.1,192.168.0.2', "#{prefix}REDIS_SENTINEL_MASTER": 'mainsentinel' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses the given port number' do
|
||||
expect(subject[:sentinels]).to contain_exactly({ host: '192.168.0.1', port: 1234 }, { host: '192.168.0.2', port: 1234 })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#base' do
|
||||
subject { redis_environment.base }
|
||||
|
||||
context 'when no `REDIS_` environment variables are present' do
|
||||
it 'uses defaults' do
|
||||
expect(subject).to eq({
|
||||
url: 'redis://localhost:6379/0',
|
||||
driver: :hiredis,
|
||||
namespace: nil,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the `REDIS_URL` environment variable is present' do
|
||||
around do |example|
|
||||
ClimateControl.modify REDIS_URL: 'redis::/user@example.com/2' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses the provided URL' do
|
||||
expect(subject).to eq({
|
||||
url: 'redis::/user@example.com/2',
|
||||
driver: :hiredis,
|
||||
namespace: nil,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when giving separate environment variables' do
|
||||
around do |example|
|
||||
ClimateControl.modify REDIS_PASSWORD: 'testpass', REDIS_HOST: 'redis.example.com', REDIS_PORT: '3333', REDIS_DB: '3' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'constructs the url from them' do
|
||||
expect(subject).to eq({
|
||||
url: 'redis://:testpass@redis.example.com:3333/3',
|
||||
driver: :hiredis,
|
||||
namespace: nil,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'setting a different driver'
|
||||
include_examples 'setting a namespace'
|
||||
include_examples 'sentinel support'
|
||||
end
|
||||
|
||||
describe '#sidekiq' do
|
||||
subject { redis_environment.sidekiq }
|
||||
|
||||
include_examples 'secondary configuration', 'SIDEKIQ'
|
||||
include_examples 'setting a different driver'
|
||||
include_examples 'setting a namespace'
|
||||
include_examples 'sentinel support', 'SIDEKIQ'
|
||||
end
|
||||
|
||||
describe '#cache' do
|
||||
subject { redis_environment.cache }
|
||||
|
||||
it 'includes extra configuration' do
|
||||
expect(subject).to eq({
|
||||
url: 'redis://localhost:6379/0',
|
||||
driver: :hiredis,
|
||||
namespace: 'cache',
|
||||
expires_in: 10.minutes,
|
||||
connect_timeout: 5,
|
||||
pool: {
|
||||
size: 5,
|
||||
timeout: 5,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
context 'when `REDIS_NAMESPACE` is not set' do
|
||||
it 'uses the `cache` namespace' do
|
||||
expect(subject[:namespace]).to eq 'cache'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when setting the `REDIS_NAMESPACE` variable' do
|
||||
around do |example|
|
||||
ClimateControl.modify REDIS_NAMESPACE: 'testns' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'attaches the `_cache` postfix to the namespace' do
|
||||
expect(subject[:namespace]).to eq 'testns_cache'
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'secondary configuration', 'CACHE'
|
||||
include_examples 'setting a different driver'
|
||||
include_examples 'sentinel support', 'CACHE'
|
||||
end
|
||||
end
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe OStatus::TagManager do
|
||||
RSpec.describe OStatus::TagManager do
|
||||
describe '#unique_tag' do
|
||||
it 'returns a unique tag' do
|
||||
expect(described_class.instance.unique_tag(Time.utc(2000), 12, 'Status')).to eq 'tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status'
|
||||
|
|
|
|||
93
spec/lib/paperclip/response_with_limit_adapter_spec.rb
Normal file
93
spec/lib/paperclip/response_with_limit_adapter_spec.rb
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Paperclip::ResponseWithLimitAdapter do
|
||||
subject { described_class.new(response_with_limit) }
|
||||
|
||||
before { stub_request(:get, url).to_return(headers: headers, body: body) }
|
||||
|
||||
let(:response_with_limit) { ResponseWithLimit.new(response, 50.kilobytes) }
|
||||
let(:response) { Request.new(:get, url).perform(&:itself) }
|
||||
let(:url) { 'https://example.com/dir/foo.png' }
|
||||
let(:headers) { nil }
|
||||
let(:body) { attachment_fixture('600x400.jpeg').binmode.read }
|
||||
|
||||
it 'writes temporary file' do
|
||||
expect(subject.tempfile.read).to eq body
|
||||
expect(subject.size).to eq body.bytesize
|
||||
end
|
||||
|
||||
context 'with Content-Disposition header' do
|
||||
let(:headers) { { 'Content-Disposition' => 'attachment; filename="bar.png"' } }
|
||||
|
||||
it 'uses filename from header' do
|
||||
expect(subject.original_filename).to eq 'bar.png'
|
||||
end
|
||||
|
||||
it 'detects MIME type from content' do
|
||||
expect(subject.content_type).to eq 'image/jpeg'
|
||||
end
|
||||
end
|
||||
|
||||
context 'without Content-Disposition header' do
|
||||
it 'uses filename from path' do
|
||||
expect(subject.original_filename).to eq 'foo.png'
|
||||
end
|
||||
|
||||
it 'detects MIME type from content' do
|
||||
expect(subject.content_type).to eq 'image/jpeg'
|
||||
end
|
||||
end
|
||||
|
||||
context 'without filename in path' do
|
||||
let(:url) { 'https://example.com/' }
|
||||
|
||||
it 'falls back to "data"' do
|
||||
expect(subject.original_filename).to eq 'data'
|
||||
end
|
||||
|
||||
it 'detects MIME type from content' do
|
||||
expect(subject.content_type).to eq 'image/jpeg'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with very long filename' do
|
||||
let(:url) { 'https://example.com/abcdefghijklmnopqrstuvwxyz.0123456789' }
|
||||
|
||||
it 'truncates the filename' do
|
||||
expect(subject.original_filename).to eq 'abcdefghijklmnopqrst.0123'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when response size exceeds limit' do
|
||||
context 'with Content-Length header' do
|
||||
let(:headers) { { 'Content-Length' => 5.megabytes } }
|
||||
|
||||
it 'raises without reading the body' do
|
||||
allow(response).to receive(:body).and_call_original
|
||||
|
||||
expect { subject }.to raise_error(Mastodon::LengthValidationError, 'Content-Length 5242880 exceeds limit of 51200')
|
||||
|
||||
expect(response).to_not have_received(:body)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without Content-Length header' do
|
||||
let(:body) { SecureRandom.random_bytes(1.megabyte) }
|
||||
|
||||
it 'raises while reading the body' do
|
||||
expect { subject }.to raise_error(Mastodon::LengthValidationError, 'Body size exceeds limit of 51200')
|
||||
expect(response.content_length).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when response times out' do
|
||||
it 'raises' do
|
||||
allow(response.body.connection).to receive(:readpartial).and_raise(HTTP::TimeoutError)
|
||||
|
||||
expect { subject }.to raise_error(HTTP::TimeoutError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe PermalinkRedirector do
|
||||
RSpec.describe PermalinkRedirector do
|
||||
let(:remote_account) { Fabricate(:account, username: 'alice', domain: 'example.com', url: 'https://example.com/@alice', id: 2) }
|
||||
|
||||
describe '#redirect_url' do
|
||||
|
|
@ -29,5 +29,20 @@ describe PermalinkRedirector do
|
|||
redirector = described_class.new('@alice/123')
|
||||
expect(redirector.redirect_path).to eq 'https://example.com/status-123'
|
||||
end
|
||||
|
||||
it 'returns path for legacy status links with a query param' do
|
||||
redirector = described_class.new('statuses/123?foo=bar')
|
||||
expect(redirector.redirect_path).to eq 'https://example.com/status-123'
|
||||
end
|
||||
|
||||
it 'returns path for pretty status links with a query param' do
|
||||
redirector = described_class.new('@alice/123?foo=bar')
|
||||
expect(redirector.redirect_path).to eq 'https://example.com/status-123'
|
||||
end
|
||||
|
||||
it 'returns path for deck URLs with query params' do
|
||||
redirector = described_class.new('/deck/directory?local=true')
|
||||
expect(redirector.redirect_path).to eq '/directory?local=true'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -72,6 +72,14 @@ RSpec.describe PlainTextFormatter do
|
|||
expect(subject).to eq 'Lorem ipsum'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when text contains HTML ruby tags' do
|
||||
let(:status) { Fabricate(:status, account: remote_account, text: '<p>Lorem <ruby>明日 <rp>(</rp><rt>Ashita</rt><rp>)</rp></ruby> ipsum</p>') }
|
||||
|
||||
it 'strips the comment' do
|
||||
expect(subject).to eq 'Lorem 明日 (Ashita) ipsum'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe RequestPool do
|
||||
RSpec.describe RequestPool do
|
||||
subject { described_class.new }
|
||||
|
||||
describe '#with' do
|
||||
|
|
@ -33,18 +33,14 @@ describe RequestPool do
|
|||
|
||||
subject
|
||||
|
||||
threads = Array.new(20) do |_i|
|
||||
Thread.new do
|
||||
20.times do
|
||||
subject.with('http://example.com') do |http_client|
|
||||
http_client.get('/').flush
|
||||
end
|
||||
end
|
||||
multi_threaded_execution(5) do
|
||||
subject.with('http://example.com') do |http_client|
|
||||
http_client.get('/').flush
|
||||
# Nudge scheduler to yield and exercise the full pool
|
||||
sleep(0.01)
|
||||
end
|
||||
end
|
||||
|
||||
threads.map(&:join)
|
||||
|
||||
expect(subject.size).to be > 1
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
require 'rails_helper'
|
||||
require 'securerandom'
|
||||
|
||||
describe Request do
|
||||
RSpec.describe Request do
|
||||
subject { described_class.new(:get, 'http://example.com') }
|
||||
|
||||
describe '#headers' do
|
||||
|
|
@ -64,8 +64,11 @@ describe Request do
|
|||
end
|
||||
|
||||
it 'closes underlying connection' do
|
||||
expect_any_instance_of(HTTP::Client).to receive(:close)
|
||||
allow(subject.send(:http_client)).to receive(:close)
|
||||
|
||||
expect { |block| subject.perform(&block) }.to yield_control
|
||||
|
||||
expect(subject.send(:http_client)).to have_received(:close)
|
||||
end
|
||||
|
||||
it 'returns response which implements body_with_limit' do
|
||||
|
|
@ -97,7 +100,7 @@ describe Request do
|
|||
describe "response's body_with_limit method" do
|
||||
it 'rejects body more than 1 megabyte by default' do
|
||||
stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes))
|
||||
expect { subject.perform(&:body_with_limit) }.to raise_error Mastodon::LengthValidationError
|
||||
expect { subject.perform(&:body_with_limit) }.to raise_error(Mastodon::LengthValidationError, 'Body size exceeds limit of 1048576')
|
||||
end
|
||||
|
||||
it 'accepts body less than 1 megabyte by default' do
|
||||
|
|
@ -107,17 +110,17 @@ describe Request do
|
|||
|
||||
it 'rejects body by given size' do
|
||||
stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.kilobytes))
|
||||
expect { subject.perform { |response| response.body_with_limit(1.kilobyte) } }.to raise_error Mastodon::LengthValidationError
|
||||
expect { subject.perform { |response| response.body_with_limit(1.kilobyte) } }.to raise_error(Mastodon::LengthValidationError, 'Body size exceeds limit of 1024')
|
||||
end
|
||||
|
||||
it 'rejects too large chunked body' do
|
||||
stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes), headers: { 'Transfer-Encoding' => 'chunked' })
|
||||
expect { subject.perform(&:body_with_limit) }.to raise_error Mastodon::LengthValidationError
|
||||
expect { subject.perform(&:body_with_limit) }.to raise_error(Mastodon::LengthValidationError, 'Body size exceeds limit of 1048576')
|
||||
end
|
||||
|
||||
it 'rejects too large monolithic body' do
|
||||
stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes), headers: { 'Content-Length' => 2.megabytes })
|
||||
expect { subject.perform(&:body_with_limit) }.to raise_error Mastodon::LengthValidationError
|
||||
expect { subject.perform(&:body_with_limit) }.to raise_error(Mastodon::LengthValidationError, 'Content-Length 2097152 exceeds limit of 1048576')
|
||||
end
|
||||
|
||||
it 'truncates large monolithic body' do
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Sanitize::Config do
|
||||
RSpec.describe Sanitize::Config do
|
||||
describe '::MASTODON_STRICT' do
|
||||
subject { Sanitize::Config::MASTODON_STRICT }
|
||||
subject { described_class::MASTODON_STRICT }
|
||||
|
||||
it 'converts h1 to p strong' do
|
||||
expect(Sanitize.fragment('<h1>Foo</h1>', subject)).to eq '<p><strong>Foo</strong></p>'
|
||||
|
|
@ -18,6 +18,10 @@ describe Sanitize::Config do
|
|||
expect(Sanitize.fragment('<p>Check out:</p><ol start="3" reversed=""><li>Foo</li><li>Bar</li></ol>', subject)).to eq '<p>Check out:</p><ol start="3" reversed=""><li>Foo</li><li>Bar</li></ol>'
|
||||
end
|
||||
|
||||
it 'keeps ruby tags' do
|
||||
expect(Sanitize.fragment('<p><ruby>明日 <rp>(</rp><rt>Ashita</rt><rp>)</rp></ruby></p>', subject)).to eq '<p><ruby>明日 <rp>(</rp><rt>Ashita</rt><rp>)</rp></ruby></p>'
|
||||
end
|
||||
|
||||
it 'removes a without href' do
|
||||
expect(Sanitize.fragment('<a>Test</a>', subject)).to eq 'Test'
|
||||
end
|
||||
|
|
@ -2,22 +2,25 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe ScopeTransformer do
|
||||
RSpec.describe ScopeTransformer do
|
||||
describe '#apply' do
|
||||
subject { described_class.new.apply(ScopeParser.new.parse(input)) }
|
||||
|
||||
shared_examples 'a scope' do |namespace, term, access|
|
||||
it 'parses the term' do
|
||||
expect(subject.term).to eq term
|
||||
it 'parses the attributes' do
|
||||
expect(subject)
|
||||
.to have_attributes(
|
||||
term: term,
|
||||
namespace: namespace,
|
||||
access: access
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
it 'parses the namespace' do
|
||||
expect(subject.namespace).to eq namespace
|
||||
end
|
||||
context 'with scope "profile"' do
|
||||
let(:input) { 'profile' }
|
||||
|
||||
it 'parses the access' do
|
||||
expect(subject.access).to eq access
|
||||
end
|
||||
it_behaves_like 'a scope', nil, 'profile', 'read'
|
||||
end
|
||||
|
||||
context 'with scope "read"' do
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
require 'rails_helper'
|
||||
require 'parslet/rig/rspec'
|
||||
|
||||
describe SearchQueryParser do
|
||||
RSpec.describe SearchQueryParser do
|
||||
let(:parser) { described_class.new }
|
||||
|
||||
context 'with term' do
|
||||
|
|
|
|||
|
|
@ -2,12 +2,43 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe SearchQueryTransformer do
|
||||
RSpec.describe SearchQueryTransformer do
|
||||
subject { described_class.new.apply(parser, current_account: account) }
|
||||
|
||||
let(:account) { Fabricate(:account) }
|
||||
let(:parser) { SearchQueryParser.new.parse(query) }
|
||||
|
||||
shared_examples 'date operator' do |operator|
|
||||
let(:statement_operations) { [] }
|
||||
|
||||
[
|
||||
['2022-01-01', '2022-01-01'],
|
||||
['"2022-01-01"', '2022-01-01'],
|
||||
['12345678', '12345678'],
|
||||
['"12345678"', '12345678'],
|
||||
].each do |value, parsed|
|
||||
context "with #{operator}:#{value}" do
|
||||
let(:query) { "#{operator}:#{value}" }
|
||||
|
||||
it 'transforms clauses' do
|
||||
ops = statement_operations.index_with { |_op| parsed }
|
||||
|
||||
expect(subject.send(:must_clauses)).to be_empty
|
||||
expect(subject.send(:must_not_clauses)).to be_empty
|
||||
expect(subject.send(:filter_clauses).map(&:term)).to contain_exactly(**ops, time_zone: 'UTC')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with #{operator}:\"abc\"" do
|
||||
let(:query) { "#{operator}:\"abc\"" }
|
||||
|
||||
it 'raises an exception' do
|
||||
expect { subject }.to raise_error(Mastodon::FilterValidationError, 'Invalid date abc')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with "hello world"' do
|
||||
let(:query) { 'hello world' }
|
||||
|
||||
|
|
@ -68,13 +99,33 @@ describe SearchQueryTransformer do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with \'before:"2022-01-01 23:00"\'' do
|
||||
let(:query) { 'before:"2022-01-01 23:00"' }
|
||||
context 'with \'is:"foo bar"\'' do
|
||||
let(:query) { 'is:"foo bar"' }
|
||||
|
||||
it 'transforms clauses' do
|
||||
expect(subject.send(:must_clauses)).to be_empty
|
||||
expect(subject.send(:must_not_clauses)).to be_empty
|
||||
expect(subject.send(:filter_clauses).map(&:term)).to contain_exactly(lt: '2022-01-01 23:00', time_zone: 'UTC')
|
||||
expect(subject.send(:filter_clauses).map(&:term)).to contain_exactly('foo bar')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with date operators' do
|
||||
context 'with "before"' do
|
||||
it_behaves_like 'date operator', 'before' do
|
||||
let(:statement_operations) { [:lt] }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with "after"' do
|
||||
it_behaves_like 'date operator', 'after' do
|
||||
let(:statement_operations) { [:gt] }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with "during"' do
|
||||
it_behaves_like 'date operator', 'during' do
|
||||
let(:statement_operations) { [:gte, :lte] }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
34
spec/lib/signature_parser_spec.rb
Normal file
34
spec/lib/signature_parser_spec.rb
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe SignatureParser do
|
||||
describe '.parse' do
|
||||
subject { described_class.parse(header) }
|
||||
|
||||
context 'with Signature headers conforming to draft-cavage-http-signatures-12' do
|
||||
let(:header) do
|
||||
# This example signature string deliberately mixes uneven spacing
|
||||
# and quoting styles to ensure everything is covered
|
||||
'keyId = "https://remote.domain/users/bob#main-key,",algorithm= rsa-sha256 , headers="host date digest (request-target)",signature="gmhMjgMROGElJU3fpehV2acD5kMHeELi8EFP2UPHOdQ54H0r55AxIpji+J3lPe+N2qSb/4H1KXIh6f0lRu8TGSsu12OQmg5hiO8VA9flcA/mh9Lpk+qwlQZIPRqKP9xUEfqD+Z7ti5wPzDKrWAUK/7FIqWgcT/mlqB1R1MGkpMFc/q4CIs2OSNiWgA4K+Kp21oQxzC2kUuYob04gAZ7cyE/FTia5t08uv6lVYFdRsn4XNPn1MsHgFBwBMRG79ng3SyhoG4PrqBEi5q2IdLq3zfre/M6He3wlCpyO2VJNdGVoTIzeZ0Zz8jUscPV3XtWUchpGclLGSaKaq/JyNZeiYQ=="' # rubocop:disable Layout/LineLength
|
||||
end
|
||||
|
||||
it 'correctly parses the header' do
|
||||
expect(subject).to eq({
|
||||
'keyId' => 'https://remote.domain/users/bob#main-key,',
|
||||
'algorithm' => 'rsa-sha256',
|
||||
'headers' => 'host date digest (request-target)',
|
||||
'signature' => 'gmhMjgMROGElJU3fpehV2acD5kMHeELi8EFP2UPHOdQ54H0r55AxIpji+J3lPe+N2qSb/4H1KXIh6f0lRu8TGSsu12OQmg5hiO8VA9flcA/mh9Lpk+qwlQZIPRqKP9xUEfqD+Z7ti5wPzDKrWAUK/7FIqWgcT/mlqB1R1MGkpMFc/q4CIs2OSNiWgA4K+Kp21oQxzC2kUuYob04gAZ7cyE/FTia5t08uv6lVYFdRsn4XNPn1MsHgFBwBMRG79ng3SyhoG4PrqBEi5q2IdLq3zfre/M6He3wlCpyO2VJNdGVoTIzeZ0Zz8jUscPV3XtWUchpGclLGSaKaq/JyNZeiYQ==', # rubocop:disable Layout/LineLength
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a malformed Signature header' do
|
||||
let(:header) { 'hello this is malformed!' }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { subject }.to raise_error(described_class::ParsingError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe StatusCacheHydrator do
|
||||
RSpec.describe StatusCacheHydrator do
|
||||
let(:status) { Fabricate(:status) }
|
||||
let(:account) { Fabricate(:account) }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe StatusFilter do
|
||||
RSpec.describe StatusFilter do
|
||||
describe '#filtered?' do
|
||||
let(:status) { Fabricate(:status) }
|
||||
|
||||
|
|
@ -23,7 +23,8 @@ describe StatusFilter do
|
|||
|
||||
context 'when status policy does not allow show' do
|
||||
it 'filters the status' do
|
||||
allow_any_instance_of(StatusPolicy).to receive(:show?).and_return(false)
|
||||
policy = instance_double(StatusPolicy, show?: false)
|
||||
allow(StatusPolicy).to receive(:new).and_return(policy)
|
||||
|
||||
expect(filter).to be_filtered
|
||||
end
|
||||
|
|
@ -74,7 +75,8 @@ describe StatusFilter do
|
|||
|
||||
context 'when status policy does not allow show' do
|
||||
it 'filters the status' do
|
||||
allow_any_instance_of(StatusPolicy).to receive(:show?).and_return(false)
|
||||
policy = instance_double(StatusPolicy, show?: false)
|
||||
allow(StatusPolicy).to receive(:new).and_return(policy)
|
||||
|
||||
expect(filter).to be_filtered
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe StatusFinder do
|
||||
RSpec.describe StatusFinder do
|
||||
include RoutingHelper
|
||||
|
||||
describe '#status' do
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue