123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- require 'rails_helper'
- def poll_option_json(name, votes)
- { type: 'Note', name: name, replies: { type: 'Collection', totalItems: votes } }
- end
- RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
- let!(:status) { Fabricate(:status, text: 'Hello world', account: Fabricate(:account, domain: 'example.com')) }
- let(:alice) { Fabricate(:account) }
- let(:bob) { Fabricate(:account) }
- let(:mentions) { [] }
- let(:tags) { [] }
- let(:media_attachments) { [] }
- before do
- mentions.each { |a| Fabricate(:mention, status: status, account: a) }
- tags.each { |t| status.tags << t }
- media_attachments.each { |m| status.media_attachments << m }
- end
- let(:payload) do
- {
- '@context': 'https://www.w3.org/ns/activitystreams',
- id: 'foo',
- type: 'Note',
- summary: 'Show more',
- content: 'Hello universe',
- updated: '2021-09-08T22:39:25Z',
- tag: [
- { type: 'Hashtag', name: 'hoge' },
- { type: 'Mention', href: ActivityPub::TagManager.instance.uri_for(alice) },
- ],
- }
- end
- let(:json) { Oj.load(Oj.dump(payload)) }
- subject { described_class.new }
- describe '#call' do
- it 'updates text' do
- subject.call(status, json)
- expect(status.reload.text).to eq 'Hello universe'
- end
- it 'updates content warning' do
- subject.call(status, json)
- expect(status.reload.spoiler_text).to eq 'Show more'
- end
- context 'when the changes are only in sanitized-out HTML' do
- let!(:status) { Fabricate(:status, text: '<p>Hello world <a href="https://joinmastodon.org" rel="nofollow">joinmastodon.org</a></p>', account: Fabricate(:account, domain: 'example.com')) }
- let(:payload) do
- {
- '@context': 'https://www.w3.org/ns/activitystreams',
- id: 'foo',
- type: 'Note',
- updated: '2021-09-08T22:39:25Z',
- content: '<p>Hello world <a href="https://joinmastodon.org" rel="noreferrer">joinmastodon.org</a></p>',
- }
- end
- before do
- subject.call(status, json)
- end
- it 'does not create any edits' do
- expect(status.reload.edits).to be_empty
- end
- it 'does not mark status as edited' do
- expect(status.edited?).to be false
- end
- end
- context 'when the status has not been explicitly edited' do
- let(:payload) do
- {
- '@context': 'https://www.w3.org/ns/activitystreams',
- id: 'foo',
- type: 'Note',
- content: 'Updated text',
- }
- end
- before do
- subject.call(status, json)
- end
- it 'does not create any edits' do
- expect(status.reload.edits).to be_empty
- end
- it 'does not mark status as edited' do
- expect(status.reload.edited?).to be false
- end
- it 'does not update the text' do
- expect(status.reload.text).to eq 'Hello world'
- end
- end
- context 'when the status has not been explicitly edited and features a poll' do
- let(:account) { Fabricate(:account, domain: 'example.com') }
- let!(:expiration) { 10.days.from_now.utc }
- let!(:status) do
- Fabricate(:status,
- text: 'Hello world',
- account: account,
- poll_attributes: {
- options: %w(Foo Bar),
- account: account,
- multiple: false,
- hide_totals: false,
- expires_at: expiration
- }
- )
- end
- let(:payload) do
- {
- '@context': 'https://www.w3.org/ns/activitystreams',
- id: 'https://example.com/foo',
- type: 'Question',
- content: 'Hello world',
- endTime: expiration.iso8601,
- oneOf: [
- poll_option_json('Foo', 4),
- poll_option_json('Bar', 3),
- ],
- }
- end
- before do
- subject.call(status, json)
- end
- it 'does not create any edits' do
- expect(status.reload.edits).to be_empty
- end
- it 'does not mark status as edited' do
- expect(status.reload.edited?).to be false
- end
- it 'does not update the text' do
- expect(status.reload.text).to eq 'Hello world'
- end
- it 'updates tallies' do
- expect(status.poll.reload.cached_tallies).to eq [4, 3]
- end
- end
- context 'when the status changes a poll despite being not explicitly marked as updated' do
- let(:account) { Fabricate(:account, domain: 'example.com') }
- let!(:expiration) { 10.days.from_now.utc }
- let!(:status) do
- Fabricate(:status,
- text: 'Hello world',
- account: account,
- poll_attributes: {
- options: %w(Foo Bar),
- account: account,
- multiple: false,
- hide_totals: false,
- expires_at: expiration
- }
- )
- end
- let(:payload) do
- {
- '@context': 'https://www.w3.org/ns/activitystreams',
- id: 'https://example.com/foo',
- type: 'Question',
- content: 'Hello world',
- endTime: expiration.iso8601,
- oneOf: [
- poll_option_json('Foo', 4),
- poll_option_json('Bar', 3),
- poll_option_json('Baz', 3),
- ],
- }
- end
- before do
- subject.call(status, json)
- end
- it 'does not create any edits' do
- expect(status.reload.edits).to be_empty
- end
- it 'does not mark status as edited' do
- expect(status.reload.edited?).to be false
- end
- it 'does not update the text' do
- expect(status.reload.text).to eq 'Hello world'
- end
- it 'does not update tallies' do
- expect(status.poll.reload.cached_tallies).to eq [0, 0]
- end
- end
- context 'when receiving an edit older than the latest processed' do
- before do
- status.snapshot!(at_time: status.created_at, rate_limit: false)
- status.update!(text: 'Hello newer world', edited_at: Time.now.utc)
- status.snapshot!(rate_limit: false)
- end
- it 'does not create any edits' do
- expect { subject.call(status, json) }.not_to change { status.reload.edits.pluck(&:id) }
- end
- it 'does not update the text, spoiler_text or edited_at' do
- expect { subject.call(status, json) }.not_to change { s = status.reload; [s.text, s.spoiler_text, s.edited_at] }
- end
- end
- context 'with no changes at all' do
- let(:payload) do
- {
- '@context': 'https://www.w3.org/ns/activitystreams',
- id: 'foo',
- type: 'Note',
- content: 'Hello world',
- }
- end
- before do
- subject.call(status, json)
- end
- it 'does not create any edits' do
- expect(status.reload.edits).to be_empty
- end
- it 'does not mark status as edited' do
- expect(status.edited?).to be false
- end
- end
- context 'with no changes and originally with no ordered_media_attachment_ids' do
- let(:payload) do
- {
- '@context': 'https://www.w3.org/ns/activitystreams',
- id: 'foo',
- type: 'Note',
- content: 'Hello world',
- }
- end
- before do
- status.update(ordered_media_attachment_ids: nil)
- subject.call(status, json)
- end
- it 'does not create any edits' do
- expect(status.reload.edits).to be_empty
- end
- it 'does not mark status as edited' do
- expect(status.edited?).to be false
- end
- end
- context 'originally without tags' do
- before do
- subject.call(status, json)
- end
- it 'updates tags' do
- expect(status.tags.reload.map(&:name)).to eq %w(hoge)
- end
- end
- context 'originally with tags' do
- let(:tags) { [Fabricate(:tag, name: 'test'), Fabricate(:tag, name: 'foo')] }
- let(:payload) do
- {
- '@context': 'https://www.w3.org/ns/activitystreams',
- id: 'foo',
- type: 'Note',
- summary: 'Show more',
- content: 'Hello universe',
- updated: '2021-09-08T22:39:25Z',
- tag: [
- { type: 'Hashtag', name: 'foo' },
- ],
- }
- end
- before do
- subject.call(status, json)
- end
- it 'updates tags' do
- expect(status.tags.reload.map(&:name)).to eq %w(foo)
- end
- end
- context 'originally without mentions' do
- before do
- subject.call(status, json)
- end
- it 'updates mentions' do
- expect(status.active_mentions.reload.map(&:account_id)).to eq [alice.id]
- end
- end
- context 'originally with mentions' do
- let(:mentions) { [alice, bob] }
- before do
- subject.call(status, json)
- end
- it 'updates mentions' do
- expect(status.active_mentions.reload.map(&:account_id)).to eq [alice.id]
- end
- end
- context 'originally without media attachments' do
- before do
- allow(RedownloadMediaWorker).to receive(:perform_async)
- subject.call(status, json)
- end
- let(:payload) do
- {
- '@context': 'https://www.w3.org/ns/activitystreams',
- id: 'foo',
- type: 'Note',
- content: 'Hello universe',
- updated: '2021-09-08T22:39:25Z',
- attachment: [
- { type: 'Image', mediaType: 'image/png', url: 'https://example.com/foo.png' },
- ]
- }
- end
- it 'updates media attachments' do
- media_attachment = status.reload.ordered_media_attachments.first
- expect(media_attachment).to_not be_nil
- expect(media_attachment.remote_url).to eq 'https://example.com/foo.png'
- end
- it 'queues download of media attachments' do
- expect(RedownloadMediaWorker).to have_received(:perform_async)
- end
- it 'records media change in edit' do
- expect(status.edits.reload.last.ordered_media_attachment_ids).to_not be_empty
- end
- end
- context 'originally with media attachments' do
- let(:media_attachments) { [Fabricate(:media_attachment, remote_url: 'https://example.com/foo.png'), Fabricate(:media_attachment, remote_url: 'https://example.com/unused.png')] }
- let(:payload) do
- {
- '@context': 'https://www.w3.org/ns/activitystreams',
- id: 'foo',
- type: 'Note',
- content: 'Hello universe',
- updated: '2021-09-08T22:39:25Z',
- attachment: [
- { type: 'Image', mediaType: 'image/png', url: 'https://example.com/foo.png', name: 'A picture' },
- ]
- }
- end
- before do
- allow(RedownloadMediaWorker).to receive(:perform_async)
- subject.call(status, json)
- end
- it 'updates the existing media attachment in-place' do
- media_attachment = status.media_attachments.reload.first
- expect(media_attachment).to_not be_nil
- expect(media_attachment.remote_url).to eq 'https://example.com/foo.png'
- expect(media_attachment.description).to eq 'A picture'
- end
- it 'does not queue redownload for the existing media attachment' do
- expect(RedownloadMediaWorker).to_not have_received(:perform_async)
- end
- it 'updates media attachments' do
- expect(status.ordered_media_attachments.map(&:remote_url)).to eq %w(https://example.com/foo.png)
- end
- it 'records media change in edit' do
- expect(status.edits.reload.last.ordered_media_attachment_ids).to_not be_empty
- end
- end
- context 'originally with a poll' do
- before do
- poll = Fabricate(:poll, status: status)
- status.update(preloadable_poll: poll)
- subject.call(status, json)
- end
- it 'removes poll' do
- expect(status.reload.poll).to eq nil
- end
- it 'records media change in edit' do
- expect(status.edits.reload.last.poll_options).to be_nil
- end
- end
- context 'originally without a poll' do
- let(:payload) do
- {
- '@context': 'https://www.w3.org/ns/activitystreams',
- id: 'foo',
- type: 'Question',
- content: 'Hello universe',
- updated: '2021-09-08T22:39:25Z',
- closed: true,
- oneOf: [
- { type: 'Note', name: 'Foo' },
- { type: 'Note', name: 'Bar' },
- { type: 'Note', name: 'Baz' },
- ],
- }
- end
- before do
- subject.call(status, json)
- end
- it 'creates a poll' do
- poll = status.reload.poll
- expect(poll).to_not be_nil
- expect(poll.options).to eq %w(Foo Bar Baz)
- end
- it 'records media change in edit' do
- expect(status.edits.reload.last.poll_options).to eq %w(Foo Bar Baz)
- end
- end
- it 'creates edit history' do
- subject.call(status, json)
- expect(status.edits.reload.map(&:text)).to eq ['Hello world', 'Hello universe']
- end
- it 'sets edited timestamp' do
- subject.call(status, json)
- expect(status.reload.edited_at.to_s).to eq '2021-09-08 22:39:25 UTC'
- end
- end
- end
|