Add support for editing for published statuses (#16697)
* Add support for editing for published statuses * Fix references to stripped-out code * Various fixes and improvements * Further fixes and improvements * Fix updates being potentially sent to unauthorized recipients * Various fixes and improvements * Fix wrong words in test * Fix notifying accounts that were tagged but were not in the audience * Fix mistake
This commit is contained in:
parent
2d1f082bb6
commit
1060666c58
56 changed files with 1415 additions and 574 deletions
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Api::V1::Statuses::HistoriesController do
|
||||
render_views
|
||||
|
||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:statuses', application: app) }
|
||||
|
||||
context 'with an oauth token' do
|
||||
before do
|
||||
allow(controller).to receive(:doorkeeper_token) { token }
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
let(:status) { Fabricate(:status, account: user.account) }
|
||||
|
||||
before do
|
||||
get :show, params: { status_id: status.id }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
29
spec/controllers/api/v1/statuses/sources_controller_spec.rb
Normal file
29
spec/controllers/api/v1/statuses/sources_controller_spec.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Api::V1::Statuses::SourcesController do
|
||||
render_views
|
||||
|
||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:statuses', application: app) }
|
||||
|
||||
context 'with an oauth token' do
|
||||
before do
|
||||
allow(controller).to receive(:doorkeeper_token) { token }
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
let(:status) { Fabricate(:status, account: user.account) }
|
||||
|
||||
before do
|
||||
get :show, params: { status_id: status.id }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
6
spec/fabricators/preview_card_fabricator.rb
Normal file
6
spec/fabricators/preview_card_fabricator.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
Fabricator(:preview_card) do
|
||||
url { Faker::Internet.url }
|
||||
title { Faker::Lorem.sentence }
|
||||
description { Faker::Lorem.paragraph }
|
||||
type 'link'
|
||||
end
|
7
spec/fabricators/status_edit_fabricator.rb
Normal file
7
spec/fabricators/status_edit_fabricator.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
Fabricator(:status_edit) do
|
||||
status nil
|
||||
account nil
|
||||
text "MyText"
|
||||
spoiler_text "MyText"
|
||||
media_attachments_changed false
|
||||
end
|
109
spec/lib/status_reach_finder_spec.rb
Normal file
109
spec/lib/status_reach_finder_spec.rb
Normal file
|
@ -0,0 +1,109 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe StatusReachFinder do
|
||||
describe '#inboxes' do
|
||||
context 'for a local status' do
|
||||
let(:parent_status) { nil }
|
||||
let(:visibility) { :public }
|
||||
let(:alice) { Fabricate(:account, username: 'alice') }
|
||||
let(:status) { Fabricate(:status, account: alice, thread: parent_status, visibility: visibility) }
|
||||
|
||||
subject { described_class.new(status) }
|
||||
|
||||
context 'when it contains mentions of remote accounts' do
|
||||
let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') }
|
||||
|
||||
before do
|
||||
status.mentions.create!(account: bob)
|
||||
end
|
||||
|
||||
it 'includes the inbox of the mentioned account' do
|
||||
expect(subject.inboxes).to include 'https://foo.bar/inbox'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it has been reblogged by a remote account' do
|
||||
let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') }
|
||||
|
||||
before do
|
||||
bob.statuses.create!(reblog: status)
|
||||
end
|
||||
|
||||
it 'includes the inbox of the reblogger' do
|
||||
expect(subject.inboxes).to include 'https://foo.bar/inbox'
|
||||
end
|
||||
|
||||
context 'when status is not public' do
|
||||
let(:visibility) { :private }
|
||||
|
||||
it 'does not include the inbox of the reblogger' do
|
||||
expect(subject.inboxes).to_not include 'https://foo.bar/inbox'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it has been favourited by a remote account' do
|
||||
let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') }
|
||||
|
||||
before do
|
||||
bob.favourites.create!(status: status)
|
||||
end
|
||||
|
||||
it 'includes the inbox of the favouriter' do
|
||||
expect(subject.inboxes).to include 'https://foo.bar/inbox'
|
||||
end
|
||||
|
||||
context 'when status is not public' do
|
||||
let(:visibility) { :private }
|
||||
|
||||
it 'does not include the inbox of the favouriter' do
|
||||
expect(subject.inboxes).to_not include 'https://foo.bar/inbox'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it has been replied to by a remote account' do
|
||||
let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') }
|
||||
|
||||
before do
|
||||
bob.statuses.create!(thread: status, text: 'Hoge')
|
||||
end
|
||||
|
||||
context do
|
||||
it 'includes the inbox of the replier' do
|
||||
expect(subject.inboxes).to include 'https://foo.bar/inbox'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status is not public' do
|
||||
let(:visibility) { :private }
|
||||
|
||||
it 'does not include the inbox of the replier' do
|
||||
expect(subject.inboxes).to_not include 'https://foo.bar/inbox'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is a reply to a remote account' do
|
||||
let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') }
|
||||
let(:parent_status) { Fabricate(:status, account: bob) }
|
||||
|
||||
context do
|
||||
it 'includes the inbox of the replied-to account' do
|
||||
expect(subject.inboxes).to include 'https://foo.bar/inbox'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status is not public and replied-to account is not mentioned' do
|
||||
let(:visibility) { :private }
|
||||
|
||||
it 'does not include the inbox of the replied-to account' do
|
||||
expect(subject.inboxes).to_not include 'https://foo.bar/inbox'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
5
spec/models/status_edit_spec.rb
Normal file
5
spec/models/status_edit_spec.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe StatusEdit, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
|
@ -67,7 +67,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do
|
|||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.url).to eq "https://#{valid_domain}/watch?v=12345"
|
||||
expect(strip_tags(status.text)).to eq "Nyan Cat 10 hours remix https://#{valid_domain}/watch?v=12345"
|
||||
expect(strip_tags(status.text)).to eq "Nyan Cat 10 hours remixhttps://#{valid_domain}/watch?v=12345"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -100,7 +100,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do
|
|||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.url).to eq "https://#{valid_domain}/watch?v=12345"
|
||||
expect(strip_tags(status.text)).to eq "Nyan Cat 10 hours remix https://#{valid_domain}/watch?v=12345"
|
||||
expect(strip_tags(status.text)).to eq "Nyan Cat 10 hours remixhttps://#{valid_domain}/watch?v=12345"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -120,7 +120,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do
|
|||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.url).to eq "https://#{valid_domain}/@foo/1234"
|
||||
expect(strip_tags(status.text)).to eq "Let's change the world https://#{valid_domain}/@foo/1234"
|
||||
expect(strip_tags(status.text)).to eq "Let's change the worldhttps://#{valid_domain}/@foo/1234"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,37 +1,112 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe FanOutOnWriteService, type: :service do
|
||||
let(:author) { Fabricate(:account, username: 'tom') }
|
||||
let(:status) { Fabricate(:status, text: 'Hello @alice #test', account: author) }
|
||||
let(:alice) { Fabricate(:user, account: Fabricate(:account, username: 'alice')).account }
|
||||
let(:follower) { Fabricate(:account, username: 'bob') }
|
||||
let(:last_active_at) { Time.now.utc }
|
||||
|
||||
subject { FanOutOnWriteService.new }
|
||||
let!(:alice) { Fabricate(:user, current_sign_in_at: last_active_at, account: Fabricate(:account, username: 'alice')).account }
|
||||
let!(:bob) { Fabricate(:user, current_sign_in_at: last_active_at, account: Fabricate(:account, username: 'bob')).account }
|
||||
let!(:tom) { Fabricate(:user, current_sign_in_at: last_active_at, account: Fabricate(:account, username: 'tom')).account }
|
||||
|
||||
subject { described_class.new }
|
||||
|
||||
let(:status) { Fabricate(:status, account: alice, visibility: visibility, text: 'Hello @bob #hoge') }
|
||||
|
||||
before do
|
||||
alice
|
||||
follower.follow!(author)
|
||||
bob.follow!(alice)
|
||||
tom.follow!(alice)
|
||||
|
||||
ProcessMentionsService.new.call(status)
|
||||
ProcessHashtagsService.new.call(status)
|
||||
|
||||
allow(Redis.current).to receive(:publish)
|
||||
|
||||
subject.call(status)
|
||||
end
|
||||
|
||||
it 'delivers status to home timeline' do
|
||||
expect(HomeFeed.new(author).get(10).map(&:id)).to include status.id
|
||||
def home_feed_of(account)
|
||||
HomeFeed.new(account).get(10).map(&:id)
|
||||
end
|
||||
|
||||
it 'delivers status to local followers' do
|
||||
pending 'some sort of problem in test environment causes this to sometimes fail'
|
||||
expect(HomeFeed.new(follower).get(10).map(&:id)).to include status.id
|
||||
context 'when status is public' do
|
||||
let(:visibility) { 'public' }
|
||||
|
||||
it 'is added to the home feed of its author' do
|
||||
expect(home_feed_of(alice)).to include status.id
|
||||
end
|
||||
|
||||
it 'is added to the home feed of a follower' do
|
||||
expect(home_feed_of(bob)).to include status.id
|
||||
expect(home_feed_of(tom)).to include status.id
|
||||
end
|
||||
|
||||
it 'is broadcast to the hashtag stream' do
|
||||
expect(Redis.current).to have_received(:publish).with('timeline:hashtag:hoge', anything)
|
||||
expect(Redis.current).to have_received(:publish).with('timeline:hashtag:hoge:local', anything)
|
||||
end
|
||||
|
||||
it 'is broadcast to the public stream' do
|
||||
expect(Redis.current).to have_received(:publish).with('timeline:public', anything)
|
||||
expect(Redis.current).to have_received(:publish).with('timeline:public:local', anything)
|
||||
end
|
||||
end
|
||||
|
||||
it 'delivers status to hashtag' do
|
||||
expect(TagFeed.new(Tag.find_by(name: 'test'), alice).get(20).map(&:id)).to include status.id
|
||||
context 'when status is limited' do
|
||||
let(:visibility) { 'limited' }
|
||||
|
||||
it 'is added to the home feed of its author' do
|
||||
expect(home_feed_of(alice)).to include status.id
|
||||
end
|
||||
|
||||
it 'is added to the home feed of the mentioned follower' do
|
||||
expect(home_feed_of(bob)).to include status.id
|
||||
end
|
||||
|
||||
it 'is not added to the home feed of the other follower' do
|
||||
expect(home_feed_of(tom)).to_not include status.id
|
||||
end
|
||||
|
||||
it 'is not broadcast publicly' do
|
||||
expect(Redis.current).to_not have_received(:publish).with('timeline:hashtag:hoge', anything)
|
||||
expect(Redis.current).to_not have_received(:publish).with('timeline:public', anything)
|
||||
end
|
||||
end
|
||||
|
||||
it 'delivers status to public timeline' do
|
||||
expect(PublicFeed.new(alice).get(20).map(&:id)).to include status.id
|
||||
context 'when status is private' do
|
||||
let(:visibility) { 'private' }
|
||||
|
||||
it 'is added to the home feed of its author' do
|
||||
expect(home_feed_of(alice)).to include status.id
|
||||
end
|
||||
|
||||
it 'is added to the home feed of a follower' do
|
||||
expect(home_feed_of(bob)).to include status.id
|
||||
expect(home_feed_of(tom)).to include status.id
|
||||
end
|
||||
|
||||
it 'is not broadcast publicly' do
|
||||
expect(Redis.current).to_not have_received(:publish).with('timeline:hashtag:hoge', anything)
|
||||
expect(Redis.current).to_not have_received(:publish).with('timeline:public', anything)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status is direct' do
|
||||
let(:visibility) { 'direct' }
|
||||
|
||||
it 'is added to the home feed of its author' do
|
||||
expect(home_feed_of(alice)).to include status.id
|
||||
end
|
||||
|
||||
it 'is added to the home feed of the mentioned follower' do
|
||||
expect(home_feed_of(bob)).to include status.id
|
||||
end
|
||||
|
||||
it 'is not added to the home feed of the other follower' do
|
||||
expect(home_feed_of(tom)).to_not include status.id
|
||||
end
|
||||
|
||||
it 'is not broadcast publicly' do
|
||||
expect(Redis.current).to_not have_received(:publish).with('timeline:hashtag:hoge', anything)
|
||||
expect(Redis.current).to_not have_received(:publish).with('timeline:public', anything)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,75 +9,55 @@ RSpec.describe ProcessMentionsService, type: :service do
|
|||
|
||||
context 'ActivityPub' do
|
||||
context do
|
||||
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
|
||||
let!(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
|
||||
|
||||
before do
|
||||
stub_request(:post, remote_user.inbox_url)
|
||||
subject.call(status)
|
||||
end
|
||||
|
||||
it 'creates a mention' do
|
||||
expect(remote_user.mentions.where(status: status).count).to eq 1
|
||||
end
|
||||
|
||||
it 'sends activity to the inbox' do
|
||||
expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an IDN domain' do
|
||||
let(:remote_user) { Fabricate(:account, username: 'sneak', protocol: :activitypub, domain: 'xn--hresiar-mxa.ch', inbox_url: 'http://example.com/inbox') }
|
||||
let(:status) { Fabricate(:status, account: account, text: "Hello @sneak@hæresiar.ch") }
|
||||
let!(:remote_user) { Fabricate(:account, username: 'sneak', protocol: :activitypub, domain: 'xn--hresiar-mxa.ch', inbox_url: 'http://example.com/inbox') }
|
||||
let!(:status) { Fabricate(:status, account: account, text: "Hello @sneak@hæresiar.ch") }
|
||||
|
||||
before do
|
||||
stub_request(:post, remote_user.inbox_url)
|
||||
subject.call(status)
|
||||
end
|
||||
|
||||
it 'creates a mention' do
|
||||
expect(remote_user.mentions.where(status: status).count).to eq 1
|
||||
end
|
||||
|
||||
it 'sends activity to the inbox' do
|
||||
expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an IDN TLD' do
|
||||
let(:remote_user) { Fabricate(:account, username: 'foo', protocol: :activitypub, domain: 'xn--y9a3aq.xn--y9a3aq', inbox_url: 'http://example.com/inbox') }
|
||||
let(:status) { Fabricate(:status, account: account, text: "Hello @foo@հայ.հայ") }
|
||||
let!(:remote_user) { Fabricate(:account, username: 'foo', protocol: :activitypub, domain: 'xn--y9a3aq.xn--y9a3aq', inbox_url: 'http://example.com/inbox') }
|
||||
let!(:status) { Fabricate(:status, account: account, text: "Hello @foo@հայ.հայ") }
|
||||
|
||||
before do
|
||||
stub_request(:post, remote_user.inbox_url)
|
||||
subject.call(status)
|
||||
end
|
||||
|
||||
it 'creates a mention' do
|
||||
expect(remote_user.mentions.where(status: status).count).to eq 1
|
||||
end
|
||||
|
||||
it 'sends activity to the inbox' do
|
||||
expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'Temporarily-unreachable ActivityPub user' do
|
||||
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox', last_webfingered_at: nil) }
|
||||
let!(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox', last_webfingered_at: nil) }
|
||||
|
||||
before do
|
||||
stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404)
|
||||
stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:remote_user@example.com").to_return(status: 500)
|
||||
stub_request(:post, remote_user.inbox_url)
|
||||
subject.call(status)
|
||||
end
|
||||
|
||||
it 'creates a mention' do
|
||||
expect(remote_user.mentions.where(status: status).count).to eq 1
|
||||
end
|
||||
|
||||
it 'sends activity to the inbox' do
|
||||
expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -35,13 +35,16 @@ describe ActivityPub::DistributionWorker do
|
|||
end
|
||||
|
||||
context 'with direct status' do
|
||||
let(:mentioned_account) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/inbox')}
|
||||
|
||||
before do
|
||||
status.update(visibility: :direct)
|
||||
status.mentions.create!(account: mentioned_account)
|
||||
end
|
||||
|
||||
it 'does nothing' do
|
||||
it 'delivers to mentioned accounts' do
|
||||
subject.perform(status.id)
|
||||
expect(ActivityPub::DeliveryWorker).to_not have_received(:push_bulk)
|
||||
expect(ActivityPub::DeliveryWorker).to have_received(:push_bulk).with(['https://foo.bar/inbox'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -45,7 +45,7 @@ describe FeedInsertWorker do
|
|||
result = subject.perform(status.id, follower.id)
|
||||
|
||||
expect(result).to be_nil
|
||||
expect(instance).to have_received(:push_to_home).with(follower, status)
|
||||
expect(instance).to have_received(:push_to_home).with(follower, status, update: nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue