Rebase to current 4.3.0-alpha-glitch-soc, update theming files

This commit is contained in:
Tarrien committed 2024-02-16 10:14:20 -05:00
1 parent aeb49bcba0
commit fff7926498
71 files changed
+30937 -2221

No files matched your search

@@ -0,0 +1,77 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Api::V1::ConversationsController do
render_views
let!(:user) { Fabricate(:user, account_attributes: { username: 'alice' }) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:other) { Fabricate(:user) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
let(:scopes) { 'read:statuses' }
before do
PostStatusService.new.call(other.account, text: 'Hey @alice', visibility: 'direct')
PostStatusService.new.call(user.account, text: 'Hey, nobody here', visibility: 'direct')
end
it 'returns pagination headers', :aggregate_failures do
get :index, params: { limit: 1 }
expect(response).to have_http_status(200)
expect(response.headers['Link'].links.size).to eq(2)
end
it 'returns conversations', :aggregate_failures do
get :index
json = body_as_json
expect(json.size).to eq 2
expect(json[0][:accounts].size).to eq 1
<<<<<<< HEAD
end
context 'with since_id' do
context 'when requesting old posts' do
it 'returns conversations' do
get :index, params: { since_id: Mastodon::Snowflake.id_at(1.hour.ago, with_random: false) }
json = body_as_json
expect(json.size).to eq 2
end
end
context 'when requesting posts in the future' do
it 'returns no conversation' do
get :index, params: { since_id: Mastodon::Snowflake.id_at(1.hour.from_now, with_random: false) }
json = body_as_json
expect(json.size).to eq 0
end
end
=======
>>>>>>> 4c6c790f8 (Fix /api/v1/conversations sometimes returning empty accounts (#25499))
end
context 'with since_id' do
context 'when requesting old posts' do
it 'returns conversations' do
get :index, params: { since_id: Mastodon::Snowflake.id_at(1.hour.ago, with_random: false) }
json = body_as_json
expect(json.size).to eq 2
end
end
context 'when requesting posts in the future' do
it 'returns no conversation' do
get :index, params: { since_id: Mastodon::Snowflake.id_at(1.hour.from_now, with_random: false) }
json = body_as_json
expect(json.size).to eq 0
end
end
end
end
end
@@ -0,0 +1,108 @@
# frozen_string_literal: true
require 'rails_helper'
describe RelationshipsController do
render_views
let(:user) { Fabricate(:user) }
describe 'GET #show' do
context 'when signed in' do
before do
sign_in user, scope: :user
get :show, params: { page: 2, relationship: 'followed_by' }
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns private cache control headers' do
expect(response.headers['Cache-Control']).to include('private, no-store')
end
end
context 'when not signed in' do
before do
get :show, params: { page: 2, relationship: 'followed_by' }
end
it 'redirects when not signed in' do
expect(response).to redirect_to '/auth/sign_in'
end
end
end
describe 'PATCH #update' do
let(:alice) { Fabricate(:account, username: 'alice', domain: 'example.com') }
shared_examples 'redirects back to followers page' do
it 'redirects back to followers page' do
alice.follow!(user.account)
sign_in user, scope: :user
subject
expect(response).to redirect_to(relationships_path)
end
end
context 'when select parameter is not provided' do
subject { patch :update }
include_examples 'redirects back to followers page'
end
context 'when select parameter is provided' do
<<<<<<< HEAD
subject { patch :update, params: { form_account_batch: { account_ids: [alice.id] }, remove_domains_from_followers: '' } }
=======
subject { patch :update, params: { form_account_batch: { account_ids: [poopfeast.id] }, remove_domains_from_followers: '' } }
>>>>>>> 0dc342df8 (Fix “Remove all followers from the selected domains” being more destructive than it claims (#23805))
it 'soft-blocks followers from selected domains' do
alice.follow!(user.account)
sign_in user, scope: :user
subject
expect(alice.following?(user.account)).to be false
end
it 'does not unfollow users from selected domains' do
user.account.follow!(alice)
sign_in user, scope: :user
subject
expect(user.account.following?(alice)).to be true
end
context 'when not signed in' do
before do
subject
end
it 'redirects when not signed in' do
expect(response).to redirect_to '/auth/sign_in'
end
end
<<<<<<< HEAD
=======
it 'does not unfollow users from selected domains' do
user.account.follow!(poopfeast)
sign_in user, scope: :user
subject
expect(user.account.following?(poopfeast)).to be true
end
include_examples 'authenticate user'
>>>>>>> 0dc342df8 (Fix “Remove all followers from the selected domains” being more destructive than it claims (#23805))
include_examples 'redirects back to followers page'
end
end
end
@@ -0,0 +1,87 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe AccountReachFinder do
let(:account) { Fabricate(:account) }
<<<<<<< HEAD
let(:ap_follower_example_com) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-1', domain: 'example.com') }
let(:ap_follower_example_org) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.org/inbox-2', domain: 'example.org') }
let(:ap_follower_with_shared) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/users/a/inbox', domain: 'foo.bar', shared_inbox_url: 'https://foo.bar/inbox') }
let(:ap_mentioned_with_shared) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/users/b/inbox', domain: 'foo.bar', shared_inbox_url: 'https://foo.bar/inbox') }
let(:ap_mentioned_example_com) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-3', domain: 'example.com') }
let(:ap_mentioned_example_org) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.org/inbox-4', domain: 'example.org') }
let(:unrelated_account) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/unrelated-inbox', domain: 'example.com') }
before do
ap_follower_example_com.follow!(account)
ap_follower_example_org.follow!(account)
ap_follower_with_shared.follow!(account)
Fabricate(:status, account: account).tap do |status|
status.mentions << Mention.new(account: ap_follower_example_com)
status.mentions << Mention.new(account: ap_mentioned_with_shared)
=======
let(:follower1) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-1') }
let(:follower2) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-2') }
let(:follower3) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/users/a/inbox', shared_inbox_url: 'https://foo.bar/inbox') }
let(:mentioned1) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/users/b/inbox', shared_inbox_url: 'https://foo.bar/inbox') }
let(:mentioned2) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-3') }
let(:mentioned3) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-4') }
let(:unrelated_account) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/unrelated-inbox') }
before do
follower1.follow!(account)
follower2.follow!(account)
follower3.follow!(account)
Fabricate(:status, account: account).tap do |status|
status.mentions << Mention.new(account: follower1)
status.mentions << Mention.new(account: mentioned1)
>>>>>>> 99c2bbbec (Change profile updates to be sent to recently-mentioned servers (#24852))
end
Fabricate(:status, account: account)
Fabricate(:status, account: account).tap do |status|
<<<<<<< HEAD
status.mentions << Mention.new(account: ap_mentioned_example_com)
status.mentions << Mention.new(account: ap_mentioned_example_org)
=======
status.mentions << Mention.new(account: mentioned2)
status.mentions << Mention.new(account: mentioned3)
>>>>>>> 99c2bbbec (Change profile updates to be sent to recently-mentioned servers (#24852))
end
Fabricate(:status).tap do |status|
status.mentions << Mention.new(account: unrelated_account)
end
end
describe '#inboxes' do
it 'includes the preferred inbox URL of followers' do
<<<<<<< HEAD
expect(described_class.new(account).inboxes).to include(*[ap_follower_example_com, ap_follower_example_org, ap_follower_with_shared].map(&:preferred_inbox_url))
end
it 'includes the preferred inbox URL of recently-mentioned accounts' do
expect(described_class.new(account).inboxes).to include(*[ap_mentioned_with_shared, ap_mentioned_example_com, ap_mentioned_example_org].map(&:preferred_inbox_url))
=======
expect(described_class.new(account).inboxes).to include(*[follower1, follower2, follower3].map(&:preferred_inbox_url))
end
it 'includes the preferred inbox URL of recently-mentioned accounts' do
expect(described_class.new(account).inboxes).to include(*[mentioned1, mentioned2, mentioned3].map(&:preferred_inbox_url))
>>>>>>> 99c2bbbec (Change profile updates to be sent to recently-mentioned servers (#24852))
end
it 'does not include the inbox of unrelated users' do
expect(described_class.new(account).inboxes).to_not include(unrelated_account.preferred_inbox_url)
end
end
end
+319
View File
@@ -0,0 +1,319 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe MediaAttachment, paperclip_processing: true do
describe 'local?' do
subject { media_attachment.local? }
let(:media_attachment) { described_class.new(remote_url: remote_url) }
context 'when remote_url is blank' do
let(:remote_url) { '' }
it 'returns true' do
expect(subject).to be true
end
end
context 'when remote_url is present' do
let(:remote_url) { 'remote_url' }
it 'returns false' do
expect(subject).to be false
end
end
end
describe 'needs_redownload?' do
subject { media_attachment.needs_redownload? }
let(:media_attachment) { described_class.new(remote_url: remote_url, file: file) }
context 'when file is blank' do
let(:file) { nil }
context 'when remote_url is present' do
let(:remote_url) { 'remote_url' }
it 'returns true' do
expect(subject).to be true
end
end
end
context 'when file is present' do
let(:file) { attachment_fixture('avatar.gif') }
context 'when remote_url is blank' do
let(:remote_url) { '' }
it 'returns false' do
expect(subject).to be false
end
end
context 'when remote_url is present' do
let(:remote_url) { 'remote_url' }
it 'returns true' do
expect(subject).to be false
end
end
end
end
describe '#to_param' do
let(:media_attachment) { Fabricate.build(:media_attachment, shortcode: shortcode, id: id) }
context 'when media attachment has a shortcode' do
let(:shortcode) { 'foo' }
let(:id) { 123 }
it 'returns shortcode' do
expect(media_attachment.to_param).to eq shortcode
end
end
context 'when media attachment does not have a shortcode' do
let(:shortcode) { nil }
let(:id) { 123 }
it 'returns string representation of id' do
expect(media_attachment.to_param).to eq id.to_s
end
end
end
shared_examples 'static 600x400 image' do |content_type, extension|
after do
media.destroy
end
it 'saves media attachment with correct file metadata' do
expect(media.persisted?).to be true
expect(media.file).to_not be_nil
# completes processing
expect(media.processing_complete?).to be true
# sets type
expect(media.type).to eq 'image'
# sets content type
expect(media.file_content_type).to eq content_type
# sets file extension
expect(media.file_file_name).to end_with extension
# Rack::Mime (used by PublicFileServerMiddleware) recognizes file extension
expect(Rack::Mime.mime_type(extension, nil)).to eq content_type
end
it 'saves media attachment with correct size metadata' do
# strips original file name
expect(media.file_file_name).to_not start_with '600x400'
# sets meta for original
expect(media.file.meta['original']['width']).to eq 600
expect(media.file.meta['original']['height']).to eq 400
expect(media.file.meta['original']['aspect']).to eq 1.5
# sets meta for thumbnail
expect(media.file.meta['small']['width']).to eq 588
expect(media.file.meta['small']['height']).to eq 392
expect(media.file.meta['small']['aspect']).to eq 1.5
end
end
describe 'jpeg' do
let(:media) { Fabricate(:media_attachment, file: attachment_fixture('600x400.jpeg')) }
it_behaves_like 'static 600x400 image', 'image/jpeg', '.jpeg'
end
describe 'png' do
let(:media) { Fabricate(:media_attachment, file: attachment_fixture('600x400.png')) }
it_behaves_like 'static 600x400 image', 'image/png', '.png'
end
describe 'webp' do
let(:media) { Fabricate(:media_attachment, file: attachment_fixture('600x400.webp')) }
it_behaves_like 'static 600x400 image', 'image/webp', '.webp'
end
describe 'avif' do
let(:media) { Fabricate(:media_attachment, file: attachment_fixture('600x400.avif')) }
it_behaves_like 'static 600x400 image', 'image/jpeg', '.jpeg'
end
describe 'heic' do
let(:media) { Fabricate(:media_attachment, file: attachment_fixture('600x400.heic')) }
it_behaves_like 'static 600x400 image', 'image/jpeg', '.jpeg'
end
describe 'base64-encoded image' do
let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('600x400.jpeg').read)}" }
let(:media) { Fabricate(:media_attachment, file: base64_attachment) }
it_behaves_like 'static 600x400 image', 'image/jpeg', '.jpeg'
end
describe 'animated gif' do
let(:media) { Fabricate(:media_attachment, file: attachment_fixture('avatar.gif')) }
it 'sets correct file metadata' do
expect(media.type).to eq 'gifv'
expect(media.file_content_type).to eq 'video/mp4'
expect(media.file.meta['original']['width']).to eq 128
expect(media.file.meta['original']['height']).to eq 128
end
end
describe 'static gif' do
fixtures = [
{ filename: 'attachment.gif', width: 600, height: 400, aspect: 1.5 },
{ filename: 'mini-static.gif', width: 32, height: 32, aspect: 1.0 },
]
fixtures.each do |fixture|
context fixture[:filename] do
let(:media) { Fabricate(:media_attachment, file: attachment_fixture(fixture[:filename])) }
it 'sets correct file metadata' do
expect(media.type).to eq 'image'
expect(media.file_content_type).to eq 'image/gif'
expect(media.file.meta['original']['width']).to eq fixture[:width]
expect(media.file.meta['original']['height']).to eq fixture[:height]
expect(media.file.meta['original']['aspect']).to eq fixture[:aspect]
end
end
end
end
describe 'ogg with cover art' do
let(:media) { Fabricate(:media_attachment, file: attachment_fixture('boop.ogg')) }
it 'sets correct file metadata' do
expect(media.type).to eq 'audio'
expect(media.file.meta['original']['duration']).to be_within(0.05).of(0.235102)
expect(media.thumbnail.present?).to be true
expect(media.file.meta['colors']['background']).to eq '#3088d4'
expect(media.file_file_name).to_not eq 'boop.ogg'
end
end
describe 'mp3 with large cover art' do
let(:media) { Fabricate(:media_attachment, file: attachment_fixture('boop.mp3')) }
it 'detects it as an audio file' do
expect(media.type).to eq 'audio'
end
it 'sets meta for the duration' do
expect(media.file.meta['original']['duration']).to be_within(0.05).of(0.235102)
end
it 'extracts thumbnail' do
expect(media.thumbnail.present?).to be true
end
it 'gives the file a random name' do
<<<<<<< HEAD
expect(media.file_file_name).to_not eq 'boop.mp3'
=======
expect(media.file_file_name).to_not eq 'boop.ogg'
end
end
describe 'mp3 with large cover art' do
let(:media) { described_class.create(account: Fabricate(:account), file: attachment_fixture('boop.mp3')) }
it 'detects it as an audio file' do
expect(media.type).to eq 'audio'
end
it 'sets meta for the duration' do
expect(media.file.meta['original']['duration']).to be_within(0.05).of(0.235102)
end
it 'extracts thumbnail' do
expect(media.thumbnail.present?).to be true
end
it 'gives the file a random name' do
expect(media.file_file_name).to_not eq 'boop.mp3'
end
end
describe 'jpeg' do
let(:media) { MediaAttachment.create(account: Fabricate(:account), file: attachment_fixture('attachment.jpg')) }
it 'sets meta for different style' do
expect(media.file.meta["original"]["width"]).to eq 600
expect(media.file.meta["original"]["height"]).to eq 400
expect(media.file.meta["original"]["aspect"]).to eq 1.5
expect(media.file.meta["small"]["width"]).to eq 588
expect(media.file.meta["small"]["height"]).to eq 392
expect(media.file.meta["small"]["aspect"]).to eq 1.5
end
it 'gives the file a random name' do
expect(media.file_file_name).to_not eq 'attachment.jpg'
end
end
describe 'base64-encoded jpeg' do
let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" }
let(:media) { MediaAttachment.create(account: Fabricate(:account), file: base64_attachment) }
it 'saves media attachment' do
expect(media.persisted?).to be true
expect(media.file).to_not be_nil
end
it 'gives the file a file name' do
expect(media.file_file_name).to_not be_blank
>>>>>>> 0aa0b71f2 (Merge pull request from GHSA-9928-3cp5-93fm)
end
end
it 'is invalid without file' do
media = described_class.new
expect(media.valid?).to be false
expect(media).to model_have_error_on_field(:file)
end
describe 'size limit validation' do
it 'rejects video files that are too large' do
stub_const 'MediaAttachment::IMAGE_LIMIT', 100.megabytes
stub_const 'MediaAttachment::VIDEO_LIMIT', 1.kilobyte
expect { Fabricate(:media_attachment, file: attachment_fixture('attachment.webm')) }.to raise_error(ActiveRecord::RecordInvalid)
end
it 'accepts video files that are small enough' do
stub_const 'MediaAttachment::IMAGE_LIMIT', 1.kilobyte
stub_const 'MediaAttachment::VIDEO_LIMIT', 100.megabytes
media = Fabricate(:media_attachment, file: attachment_fixture('attachment.webm'))
expect(media.valid?).to be true
end
it 'rejects image files that are too large' do
stub_const 'MediaAttachment::IMAGE_LIMIT', 1.kilobyte
stub_const 'MediaAttachment::VIDEO_LIMIT', 100.megabytes
expect { Fabricate(:media_attachment, file: attachment_fixture('attachment.jpg')) }.to raise_error(ActiveRecord::RecordInvalid)
end
it 'accepts image files that are small enough' do
stub_const 'MediaAttachment::IMAGE_LIMIT', 100.megabytes
stub_const 'MediaAttachment::VIDEO_LIMIT', 1.kilobyte
media = Fabricate(:media_attachment, file: attachment_fixture('attachment.jpg'))
expect(media.valid?).to be true
end
end
end
@@ -0,0 +1,273 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe FetchLinkCardService, type: :service do
subject { described_class.new }
let(:html) { '<!doctype html><title>Hello world</title>' }
let(:oembed_cache) { nil }
before do
stub_request(:get, 'http://example.com/html').to_return(headers: { 'Content-Type' => 'text/html' }, body: html)
stub_request(:get, 'http://example.com/not-found').to_return(status: 404, headers: { 'Content-Type' => 'text/html' }, body: html)
stub_request(:get, 'http://example.com/text').to_return(status: 404, headers: { 'Content-Type' => 'text/plain' }, body: 'Hello')
stub_request(:get, 'http://example.com/redirect').to_return(status: 302, headers: { 'Location' => 'http://example.com/html' })
stub_request(:get, 'http://example.com/redirect-to-404').to_return(status: 302, headers: { 'Location' => 'http://example.com/not-found' })
stub_request(:get, 'http://example.com/oembed?url=http://example.com/html').to_return(headers: { 'Content-Type' => 'application/json' }, body: '{ "version": "1.0", "type": "link", "title": "oEmbed title" }')
stub_request(:get, 'http://example.com/oembed?format=json&url=http://example.com/html').to_return(headers: { 'Content-Type' => 'application/json' }, body: '{ "version": "1.0", "type": "link", "title": "oEmbed title" }')
stub_request(:get, 'http://example.xn--fiqs8s')
stub_request(:get, 'http://example.com/日本語')
stub_request(:get, 'http://example.com/test?data=file.gpx%5E1')
stub_request(:get, 'http://example.com/test-')
stub_request(:get, 'http://example.com/sjis').to_return(request_fixture('sjis.txt'))
stub_request(:get, 'http://example.com/sjis_with_wrong_charset').to_return(request_fixture('sjis_with_wrong_charset.txt'))
stub_request(:get, 'http://example.com/koi8-r').to_return(request_fixture('koi8-r.txt'))
<<<<<<< HEAD
=======
stub_request(:get, 'http://example.com/日本語').to_return(request_fixture('sjis.txt'))
stub_request(:get, 'https://github.com/qbi/WannaCry').to_return(status: 404)
stub_request(:get, 'http://example.com/test?data=file.gpx%5E1').to_return(status: 200)
stub_request(:get, 'http://example.com/test-').to_return(request_fixture('idn.txt'))
>>>>>>> 8eb1bb8ba (Allow carets in URL search params (#25216))
stub_request(:get, 'http://example.com/windows-1251').to_return(request_fixture('windows-1251.txt'))
Rails.cache.write('oembed_endpoint:example.com', oembed_cache) if oembed_cache
subject.call(status)
end
context 'with a local status' do
context 'with URL of a regular HTML page' do
let(:status) { Fabricate(:status, text: 'http://example.com/html') }
it 'creates preview card' do
expect(status.preview_card).to_not be_nil
expect(status.preview_card.url).to eq 'http://example.com/html'
expect(status.preview_card.title).to eq 'Hello world'
end
end
context 'with URL of a page with no title' do
let(:status) { Fabricate(:status, text: 'http://example.com/html') }
let(:html) { '<!doctype html><title></title>' }
it 'does not create a preview card' do
expect(status.preview_card).to be_nil
end
end
context 'with a URL of a plain-text page' do
let(:status) { Fabricate(:status, text: 'http://example.com/text') }
it 'does not create a preview card' do
expect(status.preview_card).to be_nil
end
end
context 'with multiple URLs' do
let(:status) { Fabricate(:status, text: 'ftp://example.com http://example.com/html http://example.com/text') }
it 'fetches the first valid URL' do
expect(a_request(:get, 'http://example.com/html')).to have_been_made
end
it 'does not fetch the second valid URL' do
expect(a_request(:get, 'http://example.com/text/')).to_not have_been_made
end
end
context 'with a redirect URL' do
let(:status) { Fabricate(:status, text: 'http://example.com/redirect') }
it 'follows redirect' do
expect(a_request(:get, 'http://example.com/redirect')).to have_been_made.once
expect(a_request(:get, 'http://example.com/html')).to have_been_made.once
end
it 'creates preview card' do
expect(status.preview_card).to_not be_nil
expect(status.preview_card.url).to eq 'http://example.com/html'
expect(status.preview_card.title).to eq 'Hello world'
end
end
context 'with a broken redirect URL' do
let(:status) { Fabricate(:status, text: 'http://example.com/redirect-to-404') }
it 'follows redirect' do
expect(a_request(:get, 'http://example.com/redirect-to-404')).to have_been_made.once
expect(a_request(:get, 'http://example.com/not-found')).to have_been_made.once
end
it 'does not create a preview card' do
expect(status.preview_card).to be_nil
end
end
context 'with a 404 URL' do
let(:status) { Fabricate(:status, text: 'http://example.com/not-found') }
it 'does not create a preview card' do
expect(status.preview_card).to be_nil
end
end
context 'with an IDN URL' do
let(:status) { Fabricate(:status, text: 'Check out http://example.中国') }
it 'fetches the URL' do
expect(a_request(:get, 'http://example.xn--fiqs8s/')).to have_been_made.once
end
end
context 'with a URL of a page in Shift JIS encoding' do
let(:status) { Fabricate(:status, text: 'Check out http://example.com/sjis') }
it 'decodes the HTML' do
expect(status.preview_cards.first.title).to eq('SJISのページ')
end
end
context 'with a URL of a page in Shift JIS encoding labeled as UTF-8' do
let(:status) { Fabricate(:status, text: 'Check out http://example.com/sjis_with_wrong_charset') }
it 'decodes the HTML despite the wrong charset header' do
expect(status.preview_cards.first.title).to eq('SJISのページ')
end
end
context 'with a URL of a page in KOI8-R encoding' do
let(:status) { Fabricate(:status, text: 'Check out http://example.com/koi8-r') }
it 'decodes the HTML' do
expect(status.preview_cards.first.title).to eq('Московя начинаетъ только въ XVI ст. привлекать внимане иностранцевъ.')
end
end
context 'with a URL of a page in Windows-1251 encoding' do
let(:status) { Fabricate(:status, text: 'Check out http://example.com/windows-1251') }
it 'decodes the HTML' do
expect(status.preview_cards.first.title).to eq('сэмпл текст')
end
end
context 'with a Japanese path URL' do
let(:status) { Fabricate(:status, text: 'テストhttp://example.com/日本語') }
it 'fetches the URL' do
expect(a_request(:get, 'http://example.com/日本語')).to have_been_made.once
end
end
context 'with a hyphen-suffixed URL' do
let(:status) { Fabricate(:status, text: 'test http://example.com/test-') }
it 'fetches the URL' do
expect(a_request(:get, 'http://example.com/test-')).to have_been_made.once
end
end
context 'with a caret-suffixed URL' do
let(:status) { Fabricate(:status, text: 'test http://example.com/test?data=file.gpx^1') }
it 'fetches the URL' do
expect(a_request(:get, 'http://example.com/test?data=file.gpx%5E1')).to have_been_made.once
end
it 'does not strip the caret before fetching' do
expect(a_request(:get, 'http://example.com/test?data=file.gpx')).to_not have_been_made
end
end
context 'with a non-isolated URL' do
let(:status) { Fabricate(:status, text: 'testhttp://example.com/sjis') }
it 'does not fetch URLs not isolated from their surroundings' do
expect(a_request(:get, 'http://example.com/sjis')).to_not have_been_made
end
end
<<<<<<< HEAD
context 'with a URL of a page with oEmbed support' do
let(:html) { '<!doctype html><title>Hello world</title><link rel="alternate" type="application/json+oembed" href="http://example.com/oembed?url=http://example.com/html">' }
let(:status) { Fabricate(:status, text: 'http://example.com/html') }
it 'fetches the oEmbed URL' do
expect(a_request(:get, 'http://example.com/oembed?url=http://example.com/html')).to have_been_made.once
end
it 'creates preview card' do
expect(status.preview_card).to_not be_nil
expect(status.preview_card.url).to eq 'http://example.com/html'
expect(status.preview_card.title).to eq 'oEmbed title'
end
context 'when oEmbed endpoint cache populated' do
let(:oembed_cache) { { endpoint: 'http://example.com/oembed?format=json&url={url}', format: :json } }
it 'uses the cached oEmbed response' do
expect(a_request(:get, 'http://example.com/oembed?url=http://example.com/html')).to_not have_been_made
expect(a_request(:get, 'http://example.com/oembed?format=json&url=http://example.com/html')).to have_been_made
end
it 'creates preview card' do
expect(status.preview_card).to_not be_nil
expect(status.preview_card.url).to eq 'http://example.com/html'
expect(status.preview_card.title).to eq 'oEmbed title'
end
end
# If the original HTML URL for whatever reason (e.g. DOS protection) redirects to
# an error page, we can still use the cached oEmbed but should not use the
# redirect URL on the card.
context 'when oEmbed endpoint cache populated but page returns 404' do
let(:status) { Fabricate(:status, text: 'http://example.com/redirect-to-404') }
let(:oembed_cache) { { endpoint: 'http://example.com/oembed?url=http://example.com/html', format: :json } }
it 'uses the cached oEmbed response' do
expect(a_request(:get, 'http://example.com/oembed?url=http://example.com/html')).to have_been_made
end
it 'creates preview card' do
expect(status.preview_card).to_not be_nil
expect(status.preview_card.title).to eq 'oEmbed title'
end
it 'uses the original URL' do
expect(status.preview_card&.url).to eq 'http://example.com/redirect-to-404'
end
=======
context do
let(:status) { Fabricate(:status, text: 'test http://example.com/test?data=file.gpx^1') }
it 'does fetch URLs with a caret in search params' do
expect(a_request(:get, 'http://example.com/test?data=file.gpx')).to_not have_been_made
expect(a_request(:get, 'http://example.com/test?data=file.gpx%5E1')).to have_been_made.once
>>>>>>> 8eb1bb8ba (Allow carets in URL search params (#25216))
end
end
end
context 'with a remote status' do
let(:status) do
Fabricate(:status, account: Fabricate(:account, domain: 'example.com'), text: <<-TEXT)
Habt ihr ein paar gute Links zu <a>foo</a>
#<span class="tag"><a href="https://quitter.se/tag/wannacry" target="_blank" rel="tag noopener noreferrer" title="https://quitter.se/tag/wannacry">Wannacry</a></span> herumfliegen?
Ich will mal unter <br> <a href="http://example.com/not-found" target="_blank" rel="noopener noreferrer" title="http://example.com/not-found">http://example.com/not-found</a> was sammeln. !
<a href="http://sn.jonkman.ca/group/416/id" target="_blank" rel="noopener noreferrer" title="http://sn.jonkman.ca/group/416/id">security</a>&nbsp;
TEXT
end
it 'parses out URLs' do
expect(a_request(:get, 'http://example.com/not-found')).to have_been_made.once
end
it 'ignores URLs to hashtags' do
expect(a_request(:get, 'https://quitter.se/tag/wannacry')).to_not have_been_made
end
end
end
@@ -0,0 +1,225 @@
# frozen_string_literal: true
require 'rails_helper'
describe Scheduler::AccountsStatusesCleanupScheduler do
subject { described_class.new }
<<<<<<< HEAD
let!(:account_alice) { Fabricate(:account, domain: nil, username: 'alice') }
let!(:account_bob) { Fabricate(:account, domain: nil, username: 'bob') }
let!(:account_chris) { Fabricate(:account, domain: nil, username: 'chris') }
let!(:account_dave) { Fabricate(:account, domain: nil, username: 'dave') }
let!(:account_erin) { Fabricate(:account, domain: nil, username: 'erin') }
let!(:remote) { Fabricate(:account) }
=======
let!(:account1) { Fabricate(:account, domain: nil) }
let!(:account2) { Fabricate(:account, domain: nil) }
let!(:account3) { Fabricate(:account, domain: nil) }
let!(:account4) { Fabricate(:account, domain: nil) }
let!(:account5) { Fabricate(:account, domain: nil) }
let!(:remote) { Fabricate(:account) }
let!(:policy1) { Fabricate(:account_statuses_cleanup_policy, account: account1) }
let!(:policy2) { Fabricate(:account_statuses_cleanup_policy, account: account3) }
let!(:policy3) { Fabricate(:account_statuses_cleanup_policy, account: account4, enabled: false) }
let!(:policy4) { Fabricate(:account_statuses_cleanup_policy, account: account5) }
>>>>>>> d9e45f2fa (Fix AccountsStatusesCleanupScheduler not spreading deletes across accounts correctly (#24607))
let(:queue_size) { 0 }
let(:queue_latency) { 0 }
let(:process_set_stub) do
[
{
'concurrency' => 2,
'queues' => %w(push default),
},
]
end
before do
queue_stub = instance_double(Sidekiq::Queue, size: queue_size, latency: queue_latency)
allow(Sidekiq::Queue).to receive(:new).and_return(queue_stub)
allow(Sidekiq::ProcessSet).to receive(:new).and_return(process_set_stub)
sidekiq_stats_stub = instance_double(Sidekiq::Stats)
allow(Sidekiq::Stats).to receive(:new).and_return(sidekiq_stats_stub)
<<<<<<< HEAD
=======
# Create a bunch of old statuses
10.times do
Fabricate(:status, account: account1, created_at: 3.years.ago)
Fabricate(:status, account: account2, created_at: 3.years.ago)
Fabricate(:status, account: account3, created_at: 3.years.ago)
Fabricate(:status, account: account4, created_at: 3.years.ago)
Fabricate(:status, account: account5, created_at: 3.years.ago)
Fabricate(:status, account: remote, created_at: 3.years.ago)
end
# Create a bunch of newer statuses
5.times do
Fabricate(:status, account: account1, created_at: 3.minutes.ago)
Fabricate(:status, account: account2, created_at: 3.minutes.ago)
Fabricate(:status, account: account3, created_at: 3.minutes.ago)
Fabricate(:status, account: account4, created_at: 3.minutes.ago)
Fabricate(:status, account: remote, created_at: 3.minutes.ago)
end
>>>>>>> d9e45f2fa (Fix AccountsStatusesCleanupScheduler not spreading deletes across accounts correctly (#24607))
end
describe '#under_load?' do
context 'when nothing is queued' do
it 'returns false' do
expect(subject.under_load?).to be false
end
end
context 'when numerous jobs are queued' do
let(:queue_size) { 5 }
let(:queue_latency) { 120 }
it 'returns true' do
expect(subject.under_load?).to be true
end
end
end
describe '#compute_budget' do
context 'with a single thread' do
let(:process_set_stub) { [{ 'concurrency' => 1, 'queues' => %w(push default) }] }
it 'returns a low value' do
expect(subject.compute_budget).to be < 10
end
end
context 'with a lot of threads' do
let(:process_set_stub) do
[
{ 'concurrency' => 2, 'queues' => %w(push default) },
{ 'concurrency' => 2, 'queues' => ['push'] },
{ 'concurrency' => 2, 'queues' => ['push'] },
{ 'concurrency' => 2, 'queues' => ['push'] },
]
end
it 'returns a larger value' do
expect(subject.compute_budget).to be > 10
end
end
end
describe '#perform' do
around do |example|
Timeout.timeout(30) do
example.run
end
end
before do
# Policies for the accounts
Fabricate(:account_statuses_cleanup_policy, account: account_alice)
Fabricate(:account_statuses_cleanup_policy, account: account_chris)
Fabricate(:account_statuses_cleanup_policy, account: account_dave, enabled: false)
Fabricate(:account_statuses_cleanup_policy, account: account_erin)
# Create a bunch of old statuses
4.times do
Fabricate(:status, account: account_alice, created_at: 3.years.ago)
Fabricate(:status, account: account_bob, created_at: 3.years.ago)
Fabricate(:status, account: account_chris, created_at: 3.years.ago)
Fabricate(:status, account: account_dave, created_at: 3.years.ago)
Fabricate(:status, account: account_erin, created_at: 3.years.ago)
Fabricate(:status, account: remote, created_at: 3.years.ago)
end
# Create a bunch of newer statuses
Fabricate(:status, account: account_alice, created_at: 3.minutes.ago)
Fabricate(:status, account: account_bob, created_at: 3.minutes.ago)
Fabricate(:status, account: account_chris, created_at: 3.minutes.ago)
Fabricate(:status, account: account_dave, created_at: 3.minutes.ago)
Fabricate(:status, account: remote, created_at: 3.minutes.ago)
end
context 'when the budget is lower than the number of toots to delete' do
it 'deletes the appropriate statuses' do
expect(Status.count).to be > (subject.compute_budget) # Data check
expect { subject.perform }
.to change(Status, :count).by(-subject.compute_budget) # Cleanable statuses
.and (not_change { account_bob.statuses.count }) # No cleanup policy for account
.and(not_change { account_dave.statuses.count }) # Disabled cleanup policy
end
it 'eventually deletes every deletable toot given enough runs' do
stub_const 'Scheduler::AccountsStatusesCleanupScheduler::MAX_BUDGET', 4
expect { 3.times { subject.perform } }.to change(Status, :count).by(-cleanable_statuses_count)
end
it 'correctly round-trips between users across several runs' do
stub_const 'Scheduler::AccountsStatusesCleanupScheduler::MAX_BUDGET', 3
stub_const 'Scheduler::AccountsStatusesCleanupScheduler::PER_ACCOUNT_BUDGET', 2
expect { 3.times { subject.perform } }
.to change(Status, :count).by(-3 * 3)
.and change { account_alice.statuses.count }
.and change { account_chris.statuses.count }
.and(change { account_erin.statuses.count })
end
<<<<<<< HEAD
context 'when given a big budget' do
let(:process_set_stub) { [{ 'concurrency' => 400, 'queues' => %w(push default) }] }
before do
stub_const 'Scheduler::AccountsStatusesCleanupScheduler::MAX_BUDGET', 400
end
it 'correctly handles looping in a single run' do
expect(subject.compute_budget).to eq(400)
expect { subject.perform }.to change(Status, :count).by(-cleanable_statuses_count)
end
end
context 'when there is no work to be done' do
let(:process_set_stub) { [{ 'concurrency' => 400, 'queues' => %w(push default) }] }
before do
stub_const 'Scheduler::AccountsStatusesCleanupScheduler::MAX_BUDGET', 400
subject.perform
end
it 'does not get stuck' do
expect(subject.compute_budget).to eq(400)
expect { subject.perform }.to_not change(Status, :count)
end
end
def cleanable_statuses_count
Status
.where(account_id: [account_alice, account_chris, account_erin]) # Accounts with enabled policies
.where('created_at < ?', 2.weeks.ago) # Policy defaults is 2.weeks
.count
=======
it 'eventually deletes every deletable toot given enough runs' do
stub_const 'Scheduler::AccountsStatusesCleanupScheduler::MAX_BUDGET', 4
expect { 10.times { subject.perform } }.to change { Status.count }.by(-30)
end
it 'correctly round-trips between users across several runs' do
stub_const 'Scheduler::AccountsStatusesCleanupScheduler::MAX_BUDGET', 3
stub_const 'Scheduler::AccountsStatusesCleanupScheduler::PER_ACCOUNT_BUDGET', 2
expect { 3.times { subject.perform } }
.to change { Status.count }.by(-3 * 3)
.and change { account1.statuses.count }
.and change { account3.statuses.count }
.and change { account5.statuses.count }
>>>>>>> d9e45f2fa (Fix AccountsStatusesCleanupScheduler not spreading deletes across accounts correctly (#24607))
end
end
end
end