ActivityPub delivery (#4566)
* Deliver ActivityPub Like * Deliver ActivityPub Undo-Like * Deliver ActivityPub Create/Announce activities * Deliver ActivityPub creates from mentions * Deliver ActivityPub Block/Undo-Block * Deliver ActivityPub Accept/Reject-Follow * Deliver ActivityPub Undo-Follow * Deliver ActivityPub Follow * Deliver ActivityPub Delete activities Incidentally fix #889 * Adjust BatchedRemoveStatusService for ActivityPub * Add tests for ActivityPub workers * Add tests for FollowService * Add tests for FavouriteService, UnfollowService and PostStatusService * Add tests for ReblogService, BlockService, UnblockService, ProcessMentionsService * Add tests for AuthorizeFollowService, RejectFollowService, RemoveStatusService * Add tests for BatchedRemoveStatusService * Deliver updates to a local account to ActivityPub followers * Minor adjustmentslolsob-rspec
parent
0e2a3049e7
commit
5516767c75
|
@ -10,8 +10,9 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
current_account.update!(account_params)
|
|
||||||
@account = current_account
|
@account = current_account
|
||||||
|
@account.update!(account_params)
|
||||||
|
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||||
render json: @account, serializer: REST::CredentialAccountSerializer
|
render json: @account, serializer: REST::CredentialAccountSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ class Settings::ProfilesController < ApplicationController
|
||||||
|
|
||||||
def update
|
def update
|
||||||
if @account.update(account_params)
|
if @account.update(account_params)
|
||||||
|
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||||
redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg')
|
redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg')
|
||||||
else
|
else
|
||||||
render :show
|
render :show
|
||||||
|
|
|
@ -93,7 +93,7 @@ class ActivityPub::Activity
|
||||||
end
|
end
|
||||||
|
|
||||||
def distribute_to_followers(status)
|
def distribute_to_followers(status)
|
||||||
DistributionWorker.perform_async(status.id)
|
::DistributionWorker.perform_async(status.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_arrived_first?(uri)
|
def delete_arrived_first?(uri)
|
||||||
|
|
|
@ -171,6 +171,10 @@ class Account < ApplicationRecord
|
||||||
reorder(nil).pluck('distinct accounts.domain')
|
reorder(nil).pluck('distinct accounts.domain')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def inboxes
|
||||||
|
reorder(nil).where(protocol: :activitypub).pluck("distinct coalesce(nullif(accounts.shared_inbox_url, ''), accounts.inbox_url)")
|
||||||
|
end
|
||||||
|
|
||||||
def triadic_closures(account, limit: 5, offset: 0)
|
def triadic_closures(account, limit: 5, offset: 0)
|
||||||
sql = <<-SQL.squish
|
sql = <<-SQL.squish
|
||||||
WITH first_degree AS (
|
WITH first_degree AS (
|
||||||
|
|
|
@ -4,11 +4,28 @@ class AuthorizeFollowService < BaseService
|
||||||
def call(source_account, target_account)
|
def call(source_account, target_account)
|
||||||
follow_request = FollowRequest.find_by!(account: source_account, target_account: target_account)
|
follow_request = FollowRequest.find_by!(account: source_account, target_account: target_account)
|
||||||
follow_request.authorize!
|
follow_request.authorize!
|
||||||
NotificationWorker.perform_async(build_xml(follow_request), target_account.id, source_account.id) unless source_account.local?
|
create_notification(follow_request) unless source_account.local?
|
||||||
|
follow_request
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def create_notification(follow_request)
|
||||||
|
if follow_request.account.ostatus?
|
||||||
|
NotificationWorker.perform_async(build_xml(follow_request), follow_request.target_account_id, follow_request.account_id)
|
||||||
|
elsif follow_request.account.activitypub?
|
||||||
|
ActivityPub::DeliveryWorker.perform_async(build_json(follow_request), follow_request.target_account_id, follow_request.account.inbox_url)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_json(follow_request)
|
||||||
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
|
follow_request,
|
||||||
|
serializer: ActivityPub::AcceptFollowSerializer,
|
||||||
|
adapter: ActivityPub::Adapter
|
||||||
|
).to_json
|
||||||
|
end
|
||||||
|
|
||||||
def build_xml(follow_request)
|
def build_xml(follow_request)
|
||||||
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.authorize_follow_request_salmon(follow_request))
|
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.authorize_follow_request_salmon(follow_request))
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,9 +15,11 @@ class BatchedRemoveStatusService < BaseService
|
||||||
@mentions = statuses.map { |s| [s.id, s.mentions.includes(:account).to_a] }.to_h
|
@mentions = statuses.map { |s| [s.id, s.mentions.includes(:account).to_a] }.to_h
|
||||||
@tags = statuses.map { |s| [s.id, s.tags.pluck(:name)] }.to_h
|
@tags = statuses.map { |s| [s.id, s.tags.pluck(:name)] }.to_h
|
||||||
|
|
||||||
@stream_entry_batches = []
|
@stream_entry_batches = []
|
||||||
@salmon_batches = []
|
@salmon_batches = []
|
||||||
@json_payloads = statuses.map { |s| [s.id, Oj.dump(event: :delete, payload: s.id)] }.to_h
|
@activity_json_batches = []
|
||||||
|
@json_payloads = statuses.map { |s| [s.id, Oj.dump(event: :delete, payload: s.id)] }.to_h
|
||||||
|
@activity_json = {}
|
||||||
|
|
||||||
# Ensure that rendered XML reflects destroyed state
|
# Ensure that rendered XML reflects destroyed state
|
||||||
Status.where(id: statuses.map(&:id)).in_batches.destroy_all
|
Status.where(id: statuses.map(&:id)).in_batches.destroy_all
|
||||||
|
@ -27,7 +29,11 @@ class BatchedRemoveStatusService < BaseService
|
||||||
account = account_statuses.first.account
|
account = account_statuses.first.account
|
||||||
|
|
||||||
unpush_from_home_timelines(account_statuses)
|
unpush_from_home_timelines(account_statuses)
|
||||||
batch_stream_entries(account_statuses) if account.local?
|
|
||||||
|
if account.local?
|
||||||
|
batch_stream_entries(account_statuses)
|
||||||
|
batch_activity_json(account, account_statuses)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Cannot be batched
|
# Cannot be batched
|
||||||
|
@ -38,6 +44,7 @@ class BatchedRemoveStatusService < BaseService
|
||||||
|
|
||||||
Pubsubhubbub::DistributionWorker.push_bulk(@stream_entry_batches) { |batch| batch }
|
Pubsubhubbub::DistributionWorker.push_bulk(@stream_entry_batches) { |batch| batch }
|
||||||
NotificationWorker.push_bulk(@salmon_batches) { |batch| batch }
|
NotificationWorker.push_bulk(@salmon_batches) { |batch| batch }
|
||||||
|
ActivityPub::DeliveryWorker.push_bulk(@activity_json_batches) { |batch| batch }
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -50,6 +57,22 @@ class BatchedRemoveStatusService < BaseService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def batch_activity_json(account, statuses)
|
||||||
|
account.followers.inboxes.each do |inbox_url|
|
||||||
|
statuses.each do |status|
|
||||||
|
@activity_json_batches << [build_json(status), account.id, inbox_url]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
statuses.each do |status|
|
||||||
|
other_recipients = (status.mentions + status.reblogs).map(&:account).reject(&:local?).select(&:activitypub?).uniq(&:id)
|
||||||
|
|
||||||
|
other_recipients.each do |target_account|
|
||||||
|
@activity_json_batches << [build_json(status), account.id, target_account.inbox_url]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def unpush_from_home_timelines(statuses)
|
def unpush_from_home_timelines(statuses)
|
||||||
account = statuses.first.account
|
account = statuses.first.account
|
||||||
recipients = account.followers.local.pluck(:id)
|
recipients = account.followers.local.pluck(:id)
|
||||||
|
@ -79,7 +102,7 @@ class BatchedRemoveStatusService < BaseService
|
||||||
return if @mentions[status.id].empty?
|
return if @mentions[status.id].empty?
|
||||||
|
|
||||||
payload = stream_entry_to_xml(status.stream_entry.reload)
|
payload = stream_entry_to_xml(status.stream_entry.reload)
|
||||||
recipients = @mentions[status.id].map(&:account).reject(&:local?).uniq(&:domain).map(&:id)
|
recipients = @mentions[status.id].map(&:account).reject(&:local?).select(&:ostatus?).uniq(&:domain).map(&:id)
|
||||||
|
|
||||||
recipients.each do |recipient_id|
|
recipients.each do |recipient_id|
|
||||||
@salmon_batches << [payload, status.account_id, recipient_id]
|
@salmon_batches << [payload, status.account_id, recipient_id]
|
||||||
|
@ -111,4 +134,14 @@ class BatchedRemoveStatusService < BaseService
|
||||||
def redis
|
def redis
|
||||||
Redis.current
|
Redis.current
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def build_json(status)
|
||||||
|
return @activity_json[status.id] if @activity_json.key?(status.id)
|
||||||
|
|
||||||
|
@activity_json[status.id] = ActiveModelSerializers::SerializableResource.new(
|
||||||
|
status,
|
||||||
|
serializer: ActivityPub::DeleteSerializer,
|
||||||
|
adapter: ActivityPub::Adapter
|
||||||
|
).to_json
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,11 +12,28 @@ class BlockService < BaseService
|
||||||
block = account.block!(target_account)
|
block = account.block!(target_account)
|
||||||
|
|
||||||
BlockWorker.perform_async(account.id, target_account.id)
|
BlockWorker.perform_async(account.id, target_account.id)
|
||||||
NotificationWorker.perform_async(build_xml(block), account.id, target_account.id) unless target_account.local?
|
create_notification(block) unless target_account.local?
|
||||||
|
block
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def create_notification(block)
|
||||||
|
if block.target_account.ostatus?
|
||||||
|
NotificationWorker.perform_async(build_xml(block), block.account_id, block.target_account_id)
|
||||||
|
elsif block.target_account.activitypub?
|
||||||
|
ActivityPub::DeliveryWorker.perform_async(build_json(block), block.account_id, block.target_account.inbox_url)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_json(block)
|
||||||
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
|
block,
|
||||||
|
serializer: ActivityPub::BlockSerializer,
|
||||||
|
adapter: ActivityPub::Adapter
|
||||||
|
).to_json
|
||||||
|
end
|
||||||
|
|
||||||
def build_xml(block)
|
def build_xml(block)
|
||||||
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.block_salmon(block))
|
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.block_salmon(block))
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,18 +15,32 @@ class FavouriteService < BaseService
|
||||||
return favourite unless favourite.nil?
|
return favourite unless favourite.nil?
|
||||||
|
|
||||||
favourite = Favourite.create!(account: account, status: status)
|
favourite = Favourite.create!(account: account, status: status)
|
||||||
|
create_notification(favourite)
|
||||||
if status.local?
|
|
||||||
NotifyService.new.call(favourite.status.account, favourite)
|
|
||||||
else
|
|
||||||
NotificationWorker.perform_async(build_xml(favourite), account.id, status.account_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
favourite
|
favourite
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def create_notification(favourite)
|
||||||
|
status = favourite.status
|
||||||
|
|
||||||
|
if status.account.local?
|
||||||
|
NotifyService.new.call(status.account, favourite)
|
||||||
|
elsif status.account.ostatus?
|
||||||
|
NotificationWorker.perform_async(build_xml(favourite), favourite.account_id, status.account_id)
|
||||||
|
elsif status.account.activitypub?
|
||||||
|
ActivityPub::DeliveryWorker.perform_async(build_json(favourite), favourite.account_id, status.account.inbox_url)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_json(favourite)
|
||||||
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
|
favourite,
|
||||||
|
serializer: ActivityPub::LikeSerializer,
|
||||||
|
adapter: ActivityPub::Adapter
|
||||||
|
).to_json
|
||||||
|
end
|
||||||
|
|
||||||
def build_xml(favourite)
|
def build_xml(favourite)
|
||||||
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.favourite_salmon(favourite))
|
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.favourite_salmon(favourite))
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,7 +14,7 @@ class FollowService < BaseService
|
||||||
|
|
||||||
return if source_account.following?(target_account)
|
return if source_account.following?(target_account)
|
||||||
|
|
||||||
if target_account.locked?
|
if target_account.locked? || target_account.activitypub?
|
||||||
request_follow(source_account, target_account)
|
request_follow(source_account, target_account)
|
||||||
else
|
else
|
||||||
direct_follow(source_account, target_account)
|
direct_follow(source_account, target_account)
|
||||||
|
@ -28,9 +28,11 @@ class FollowService < BaseService
|
||||||
|
|
||||||
if target_account.local?
|
if target_account.local?
|
||||||
NotifyService.new.call(target_account, follow_request)
|
NotifyService.new.call(target_account, follow_request)
|
||||||
else
|
elsif target_account.ostatus?
|
||||||
NotificationWorker.perform_async(build_follow_request_xml(follow_request), source_account.id, target_account.id)
|
NotificationWorker.perform_async(build_follow_request_xml(follow_request), source_account.id, target_account.id)
|
||||||
AfterRemoteFollowRequestWorker.perform_async(follow_request.id)
|
AfterRemoteFollowRequestWorker.perform_async(follow_request.id)
|
||||||
|
elsif target_account.activitypub?
|
||||||
|
ActivityPub::DeliveryWorker.perform_async(build_json(follow_request), source_account.id, target_account.inbox_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
follow_request
|
follow_request
|
||||||
|
@ -63,4 +65,12 @@ class FollowService < BaseService
|
||||||
def build_follow_xml(follow)
|
def build_follow_xml(follow)
|
||||||
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.follow_salmon(follow))
|
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.follow_salmon(follow))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def build_json(follow_request)
|
||||||
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
|
follow_request,
|
||||||
|
serializer: ActivityPub::FollowSerializer,
|
||||||
|
adapter: ActivityPub::Adapter
|
||||||
|
).to_json
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,6 +39,7 @@ class PostStatusService < BaseService
|
||||||
LinkCrawlWorker.perform_async(status.id) unless status.spoiler_text?
|
LinkCrawlWorker.perform_async(status.id) unless status.spoiler_text?
|
||||||
DistributionWorker.perform_async(status.id)
|
DistributionWorker.perform_async(status.id)
|
||||||
Pubsubhubbub::DistributionWorker.perform_async(status.stream_entry.id)
|
Pubsubhubbub::DistributionWorker.perform_async(status.stream_entry.id)
|
||||||
|
ActivityPub::DistributionWorker.perform_async(status.id)
|
||||||
|
|
||||||
if options[:idempotency].present?
|
if options[:idempotency].present?
|
||||||
redis.setex("idempotency:status:#{account.id}:#{options[:idempotency]}", 3_600, status.id)
|
redis.setex("idempotency:status:#{account.id}:#{options[:idempotency]}", 3_600, status.id)
|
||||||
|
|
|
@ -28,18 +28,32 @@ class ProcessMentionsService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
status.mentions.includes(:account).each do |mention|
|
status.mentions.includes(:account).each do |mention|
|
||||||
mentioned_account = mention.account
|
create_notification(status, mention)
|
||||||
|
|
||||||
if mentioned_account.local?
|
|
||||||
NotifyService.new.call(mentioned_account, mention)
|
|
||||||
else
|
|
||||||
NotificationWorker.perform_async(stream_entry_to_xml(status.stream_entry), status.account_id, mentioned_account.id)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def create_notification(status, mention)
|
||||||
|
mentioned_account = mention.account
|
||||||
|
|
||||||
|
if mentioned_account.local?
|
||||||
|
NotifyService.new.call(mentioned_account, mention)
|
||||||
|
elsif mentioned_account.ostatus?
|
||||||
|
NotificationWorker.perform_async(stream_entry_to_xml(status.stream_entry), status.account_id, mentioned_account.id)
|
||||||
|
elsif mentioned_account.activitypub?
|
||||||
|
ActivityPub::DeliveryWorker.perform_async(build_json(mention.status), mention.status.account_id, mentioned_account.inbox_url)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_json(status)
|
||||||
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
|
status,
|
||||||
|
serializer: ActivityPub::ActivitySerializer,
|
||||||
|
adapter: ActivityPub::Adapter
|
||||||
|
).to_json
|
||||||
|
end
|
||||||
|
|
||||||
def follow_remote_account_service
|
def follow_remote_account_service
|
||||||
@follow_remote_account_service ||= ResolveRemoteAccountService.new
|
@follow_remote_account_service ||= ResolveRemoteAccountService.new
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,13 +21,31 @@ class ReblogService < BaseService
|
||||||
|
|
||||||
DistributionWorker.perform_async(reblog.id)
|
DistributionWorker.perform_async(reblog.id)
|
||||||
Pubsubhubbub::DistributionWorker.perform_async(reblog.stream_entry.id)
|
Pubsubhubbub::DistributionWorker.perform_async(reblog.stream_entry.id)
|
||||||
|
ActivityPub::DistributionWorker.perform_async(reblog.id)
|
||||||
|
|
||||||
if reblogged_status.local?
|
create_notification(reblog)
|
||||||
NotifyService.new.call(reblog.reblog.account, reblog)
|
|
||||||
else
|
|
||||||
NotificationWorker.perform_async(stream_entry_to_xml(reblog.stream_entry), account.id, reblog.reblog.account_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
reblog
|
reblog
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def create_notification(reblog)
|
||||||
|
reblogged_status = reblog.reblog
|
||||||
|
|
||||||
|
if reblogged_status.account.local?
|
||||||
|
NotifyService.new.call(reblogged_status.account, reblog)
|
||||||
|
elsif reblogged_status.account.ostatus?
|
||||||
|
NotificationWorker.perform_async(stream_entry_to_xml(reblog.stream_entry), reblog.account_id, reblogged_status.account_id)
|
||||||
|
elsif reblogged_status.account.activitypub?
|
||||||
|
ActivityPub::DeliveryWorker.perform_async(build_json(reblog), reblog.account_id, reblogged_status.account.inbox_url)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_json(reblog)
|
||||||
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
|
reblog,
|
||||||
|
serializer: ActivityPub::ActivitySerializer,
|
||||||
|
adapter: ActivityPub::Adapter
|
||||||
|
).to_json
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,11 +4,28 @@ class RejectFollowService < BaseService
|
||||||
def call(source_account, target_account)
|
def call(source_account, target_account)
|
||||||
follow_request = FollowRequest.find_by!(account: source_account, target_account: target_account)
|
follow_request = FollowRequest.find_by!(account: source_account, target_account: target_account)
|
||||||
follow_request.reject!
|
follow_request.reject!
|
||||||
NotificationWorker.perform_async(build_xml(follow_request), target_account.id, source_account.id) unless source_account.local?
|
create_notification(follow_request) unless source_account.local?
|
||||||
|
follow_request
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def create_notification(follow_request)
|
||||||
|
if follow_request.account.ostatus?
|
||||||
|
NotificationWorker.perform_async(build_xml(follow_request), follow_request.target_account_id, follow_request.account_id)
|
||||||
|
elsif follow_request.account.activitypub?
|
||||||
|
ActivityPub::DeliveryWorker.perform_async(build_json(follow_request), follow_request.target_account_id, follow_request.account.inbox_url)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_json(follow_request)
|
||||||
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
|
follow_request,
|
||||||
|
serializer: ActivityPub::RejectFollowSerializer,
|
||||||
|
adapter: ActivityPub::Adapter
|
||||||
|
).to_json
|
||||||
|
end
|
||||||
|
|
||||||
def build_xml(follow_request)
|
def build_xml(follow_request)
|
||||||
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.reject_follow_request_salmon(follow_request))
|
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.reject_follow_request_salmon(follow_request))
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,8 +22,10 @@ class RemoveStatusService < BaseService
|
||||||
|
|
||||||
return unless @account.local?
|
return unless @account.local?
|
||||||
|
|
||||||
remove_from_mentioned(@stream_entry.reload)
|
@stream_entry = @stream_entry.reload
|
||||||
Pubsubhubbub::DistributionWorker.perform_async(@stream_entry.id)
|
|
||||||
|
remove_from_remote_followers
|
||||||
|
remove_from_remote_affected
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -38,13 +40,46 @@ class RemoveStatusService < BaseService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_from_mentioned(stream_entry)
|
def remove_from_remote_affected
|
||||||
salmon_xml = stream_entry_to_xml(stream_entry)
|
# People who got mentioned in the status, or who
|
||||||
target_accounts = @mentions.map(&:account).reject(&:local?).uniq(&:domain)
|
# reblogged it from someone else might not follow
|
||||||
|
# the author and wouldn't normally receive the
|
||||||
|
# delete notification - so here, we explicitly
|
||||||
|
# send it to them
|
||||||
|
|
||||||
NotificationWorker.push_bulk(target_accounts) do |target_account|
|
target_accounts = (@mentions.map(&:account).reject(&:local?) + @reblogs.map(&:account).reject(&:local?)).uniq(&:id)
|
||||||
[salmon_xml, stream_entry.account_id, target_account.id]
|
|
||||||
|
# Ostatus
|
||||||
|
NotificationWorker.push_bulk(target_accounts.select(&:ostatus?).uniq(&:domain)) do |target_account|
|
||||||
|
[salmon_xml, @account.id, target_account.id]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# ActivityPub
|
||||||
|
ActivityPub::DeliveryWorker.push_bulk(target_accounts.select(&:activitypub?).uniq(&:inbox_url)) do |inbox_url|
|
||||||
|
[activity_json, @account.id, inbox_url]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_from_remote_followers
|
||||||
|
# OStatus
|
||||||
|
Pubsubhubbub::DistributionWorker.perform_async(@stream_entry.id)
|
||||||
|
|
||||||
|
# ActivityPub
|
||||||
|
ActivityPub::DeliveryWorker.push_bulk(@account.followers.inboxes) do |inbox_url|
|
||||||
|
[activity_json, @account.id, inbox_url]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def salmon_xml
|
||||||
|
@salmon_xml ||= stream_entry_to_xml(@stream_entry)
|
||||||
|
end
|
||||||
|
|
||||||
|
def activity_json
|
||||||
|
@activity_json ||= ActiveModelSerializers::SerializableResource.new(
|
||||||
|
@status,
|
||||||
|
serializer: ActivityPub::DeleteSerializer,
|
||||||
|
adapter: ActivityPub::Adapter
|
||||||
|
).to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_reblogs
|
def remove_reblogs
|
||||||
|
|
|
@ -5,11 +5,28 @@ class UnblockService < BaseService
|
||||||
return unless account.blocking?(target_account)
|
return unless account.blocking?(target_account)
|
||||||
|
|
||||||
unblock = account.unblock!(target_account)
|
unblock = account.unblock!(target_account)
|
||||||
NotificationWorker.perform_async(build_xml(unblock), account.id, target_account.id) unless target_account.local?
|
create_notification(unblock) unless target_account.local?
|
||||||
|
unblock
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def create_notification(unblock)
|
||||||
|
if unblock.target_account.ostatus?
|
||||||
|
NotificationWorker.perform_async(build_xml(unblock), unblock.account_id, unblock.target_account_id)
|
||||||
|
elsif unblock.target_account.activitypub?
|
||||||
|
ActivityPub::DeliveryWorker.perform_async(build_json(unblock), unblock.account_id, unblock.target_account.inbox_url)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_json(unblock)
|
||||||
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
|
unblock,
|
||||||
|
serializer: ActivityPub::UndoBlockSerializer,
|
||||||
|
adapter: ActivityPub::Adapter
|
||||||
|
).to_json
|
||||||
|
end
|
||||||
|
|
||||||
def build_xml(block)
|
def build_xml(block)
|
||||||
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.unblock_salmon(block))
|
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.unblock_salmon(block))
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,14 +4,30 @@ class UnfavouriteService < BaseService
|
||||||
def call(account, status)
|
def call(account, status)
|
||||||
favourite = Favourite.find_by!(account: account, status: status)
|
favourite = Favourite.find_by!(account: account, status: status)
|
||||||
favourite.destroy!
|
favourite.destroy!
|
||||||
|
create_notification(favourite) unless status.local?
|
||||||
NotificationWorker.perform_async(build_xml(favourite), account.id, status.account_id) unless status.local?
|
|
||||||
|
|
||||||
favourite
|
favourite
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def create_notification(favourite)
|
||||||
|
status = favourite.status
|
||||||
|
|
||||||
|
if status.account.ostatus?
|
||||||
|
NotificationWorker.perform_async(build_xml(favourite), favourite.account_id, status.account_id)
|
||||||
|
elsif status.account.activitypub?
|
||||||
|
ActivityPub::DeliveryWorker.perform_async(build_json(favourite), favourite.account_id, status.account.inbox_url)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_json(favourite)
|
||||||
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
|
favourite,
|
||||||
|
serializer: ActivityPub::UndoLikeSerializer,
|
||||||
|
adapter: ActivityPub::Adapter
|
||||||
|
).to_json
|
||||||
|
end
|
||||||
|
|
||||||
def build_xml(favourite)
|
def build_xml(favourite)
|
||||||
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.unfavourite_salmon(favourite))
|
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.unfavourite_salmon(favourite))
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,12 +7,29 @@ class UnfollowService < BaseService
|
||||||
def call(source_account, target_account)
|
def call(source_account, target_account)
|
||||||
follow = source_account.unfollow!(target_account)
|
follow = source_account.unfollow!(target_account)
|
||||||
return unless follow
|
return unless follow
|
||||||
NotificationWorker.perform_async(build_xml(follow), source_account.id, target_account.id) unless target_account.local?
|
create_notification(follow) unless target_account.local?
|
||||||
UnmergeWorker.perform_async(target_account.id, source_account.id)
|
UnmergeWorker.perform_async(target_account.id, source_account.id)
|
||||||
|
follow
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def create_notification(follow)
|
||||||
|
if follow.target_account.ostatus?
|
||||||
|
NotificationWorker.perform_async(build_xml(follow), follow.account_id, follow.target_account_id)
|
||||||
|
elsif follow.target_account.activitypub?
|
||||||
|
ActivityPub::DeliveryWorker.perform_async(build_json(follow), follow.account_id, follow.target_account.inbox_url)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_json(follow)
|
||||||
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
|
follow,
|
||||||
|
serializer: ActivityPub::UndoFollowSerializer,
|
||||||
|
adapter: ActivityPub::Adapter
|
||||||
|
).to_json
|
||||||
|
end
|
||||||
|
|
||||||
def build_xml(follow)
|
def build_xml(follow)
|
||||||
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.unfollow_salmon(follow))
|
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.unfollow_salmon(follow))
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ActivityPub::DeliveryWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
|
||||||
|
sidekiq_options queue: 'push', retry: 5, dead: false
|
||||||
|
|
||||||
|
HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze
|
||||||
|
|
||||||
|
def perform(json, source_account_id, inbox_url)
|
||||||
|
@json = json
|
||||||
|
@source_account = Account.find(source_account_id)
|
||||||
|
@inbox_url = inbox_url
|
||||||
|
|
||||||
|
perform_request
|
||||||
|
|
||||||
|
raise Mastodon::UnexpectedResponseError, @response unless response_successful?
|
||||||
|
rescue => e
|
||||||
|
raise e.class, "Delivery failed for #{inbox_url}: #{e.message}"
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def build_request
|
||||||
|
request = Request.new(:post, @inbox_url, body: @json)
|
||||||
|
request.on_behalf_of(@source_account, :uri)
|
||||||
|
request.add_headers(HEADERS)
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform_request
|
||||||
|
@response = build_request.perform
|
||||||
|
end
|
||||||
|
|
||||||
|
def response_successful?
|
||||||
|
@response.code > 199 && @response.code < 300
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,38 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ActivityPub::DistributionWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
|
||||||
|
sidekiq_options queue: 'push'
|
||||||
|
|
||||||
|
def perform(status_id)
|
||||||
|
@status = Status.find(status_id)
|
||||||
|
@account = @status.account
|
||||||
|
|
||||||
|
return if skip_distribution?
|
||||||
|
|
||||||
|
ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
|
||||||
|
[payload, @account.id, inbox_url]
|
||||||
|
end
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def skip_distribution?
|
||||||
|
@status.direct_visibility?
|
||||||
|
end
|
||||||
|
|
||||||
|
def inboxes
|
||||||
|
@inboxes ||= @account.followers.inboxes
|
||||||
|
end
|
||||||
|
|
||||||
|
def payload
|
||||||
|
@payload ||= ActiveModelSerializers::SerializableResource.new(
|
||||||
|
@status,
|
||||||
|
serializer: ActivityPub::ActivitySerializer,
|
||||||
|
adapter: ActivityPub::Adapter
|
||||||
|
).to_json
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,6 +6,6 @@ class ActivityPub::ProcessingWorker
|
||||||
sidekiq_options backtrace: true
|
sidekiq_options backtrace: true
|
||||||
|
|
||||||
def perform(account_id, body)
|
def perform(account_id, body)
|
||||||
ProcessCollectionService.new.call(body, Account.find(account_id))
|
ActivityPub::ProcessCollectionService.new.call(body, Account.find(account_id))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ActivityPub::UpdateDistributionWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
|
||||||
|
sidekiq_options queue: 'push'
|
||||||
|
|
||||||
|
def perform(account_id)
|
||||||
|
@account = Account.find(account_id)
|
||||||
|
|
||||||
|
ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
|
||||||
|
[payload, @account.id, inbox_url]
|
||||||
|
end
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def inboxes
|
||||||
|
@inboxes ||= @account.followers.inboxes
|
||||||
|
end
|
||||||
|
|
||||||
|
def payload
|
||||||
|
@payload ||= ActiveModelSerializers::SerializableResource.new(
|
||||||
|
@account,
|
||||||
|
serializer: ActivityPub::UpdateSerializer,
|
||||||
|
adapter: ActivityPub::Adapter
|
||||||
|
).to_json
|
||||||
|
end
|
||||||
|
end
|
|
@ -20,6 +20,8 @@ describe Api::V1::Accounts::CredentialsController do
|
||||||
describe 'PATCH #update' do
|
describe 'PATCH #update' do
|
||||||
describe 'with valid data' do
|
describe 'with valid data' do
|
||||||
before do
|
before do
|
||||||
|
allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
|
||||||
|
|
||||||
patch :update, params: {
|
patch :update, params: {
|
||||||
display_name: "Alice Isn't Dead",
|
display_name: "Alice Isn't Dead",
|
||||||
note: "Hi!\n\nToot toot!",
|
note: "Hi!\n\nToot toot!",
|
||||||
|
@ -40,6 +42,10 @@ describe Api::V1::Accounts::CredentialsController do
|
||||||
expect(user.account.avatar).to exist
|
expect(user.account.avatar).to exist
|
||||||
expect(user.account.header).to exist
|
expect(user.account.header).to exist
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'queues up an account update distribution' do
|
||||||
|
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(user.account_id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'with invalid data' do
|
describe 'with invalid data' do
|
||||||
|
|
|
@ -17,11 +17,13 @@ RSpec.describe Settings::ProfilesController, type: :controller do
|
||||||
|
|
||||||
describe 'PUT #update' do
|
describe 'PUT #update' do
|
||||||
it 'updates the user profile' do
|
it 'updates the user profile' do
|
||||||
|
allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
|
||||||
account = Fabricate(:account, user: @user, display_name: 'Old name')
|
account = Fabricate(:account, user: @user, display_name: 'Old name')
|
||||||
|
|
||||||
put :update, params: { account: { display_name: 'New name' } }
|
put :update, params: { account: { display_name: 'New name' } }
|
||||||
expect(account.reload.display_name).to eq 'New name'
|
expect(account.reload.display_name).to eq 'New name'
|
||||||
expect(response).to redirect_to(settings_profile_path)
|
expect(response).to redirect_to(settings_profile_path)
|
||||||
|
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,7 +22,7 @@ RSpec.describe AuthorizeFollowService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'remote' do
|
describe 'remote OStatus' do
|
||||||
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
|
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -46,4 +46,26 @@ RSpec.describe AuthorizeFollowService do
|
||||||
}).to have_been_made.once
|
}).to have_been_made.once
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'remote ActivityPub' do
|
||||||
|
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox')).account }
|
||||||
|
|
||||||
|
before do
|
||||||
|
FollowRequest.create(account: bob, target_account: sender)
|
||||||
|
stub_request(:post, bob.inbox_url).to_return(status: 200)
|
||||||
|
subject.call(bob, sender)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes follow request' do
|
||||||
|
expect(bob.requested?(sender)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates follow relation' do
|
||||||
|
expect(bob.following?(sender)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends an accept activity' do
|
||||||
|
expect(a_request(:post, bob.inbox_url)).to have_been_made.once
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ RSpec.describe BatchedRemoveStatusService do
|
||||||
let!(:alice) { Fabricate(:account) }
|
let!(:alice) { Fabricate(:account) }
|
||||||
let!(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://example.com/salmon') }
|
let!(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://example.com/salmon') }
|
||||||
let!(:jeff) { Fabricate(:account) }
|
let!(:jeff) { Fabricate(:account) }
|
||||||
|
let!(:hank) { Fabricate(:account, username: 'hank', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
|
||||||
|
|
||||||
let(:status1) { PostStatusService.new.call(alice, 'Hello @bob@example.com') }
|
let(:status1) { PostStatusService.new.call(alice, 'Hello @bob@example.com') }
|
||||||
let(:status2) { PostStatusService.new.call(alice, 'Another status') }
|
let(:status2) { PostStatusService.new.call(alice, 'Another status') }
|
||||||
|
@ -15,9 +16,11 @@ RSpec.describe BatchedRemoveStatusService do
|
||||||
|
|
||||||
stub_request(:post, 'http://example.com/push').to_return(status: 200, body: '', headers: {})
|
stub_request(:post, 'http://example.com/push').to_return(status: 200, body: '', headers: {})
|
||||||
stub_request(:post, 'http://example.com/salmon').to_return(status: 200, body: '', headers: {})
|
stub_request(:post, 'http://example.com/salmon').to_return(status: 200, body: '', headers: {})
|
||||||
|
stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
|
||||||
|
|
||||||
Fabricate(:subscription, account: alice, callback_url: 'http://example.com/push', confirmed: true, expires_at: 30.days.from_now)
|
Fabricate(:subscription, account: alice, callback_url: 'http://example.com/push', confirmed: true, expires_at: 30.days.from_now)
|
||||||
jeff.follow!(alice)
|
jeff.follow!(alice)
|
||||||
|
hank.follow!(alice)
|
||||||
|
|
||||||
status1
|
status1
|
||||||
status2
|
status2
|
||||||
|
@ -58,4 +61,8 @@ RSpec.describe BatchedRemoveStatusService do
|
||||||
xml.match(TagManager::VERBS[:delete])
|
xml.match(TagManager::VERBS[:delete])
|
||||||
}).to have_been_made.once
|
}).to have_been_made.once
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'sends delete activity to followers' do
|
||||||
|
expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.at_least_once
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,7 +17,7 @@ RSpec.describe BlockService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'remote' do
|
describe 'remote OStatus' do
|
||||||
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
|
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -36,4 +36,21 @@ RSpec.describe BlockService do
|
||||||
}).to have_been_made.once
|
}).to have_been_made.once
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'remote ActivityPub' do
|
||||||
|
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox')).account }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
|
||||||
|
subject.call(sender, bob)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a blocking relation' do
|
||||||
|
expect(sender.blocking?(bob)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a block activity' do
|
||||||
|
expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,8 +18,8 @@ RSpec.describe FavouriteService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'remote' do
|
describe 'remote OStatus' do
|
||||||
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
|
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
|
||||||
let(:status) { Fabricate(:status, account: bob, uri: 'tag:example.com:blahblah') }
|
let(:status) { Fabricate(:status, account: bob, uri: 'tag:example.com:blahblah') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -38,4 +38,22 @@ RSpec.describe FavouriteService do
|
||||||
}).to have_been_made.once
|
}).to have_been_made.once
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'remote ActivityPub' do
|
||||||
|
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, protocol: :activitypub, username: 'bob', domain: 'example.com', inbox_url: 'http://example.com/inbox')).account }
|
||||||
|
let(:status) { Fabricate(:status, account: bob) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:post, "http://example.com/inbox").to_return(:status => 200, :body => "", :headers => {})
|
||||||
|
subject.call(sender, status)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a favourite' do
|
||||||
|
expect(status.favourites.first).to_not be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a like activity' do
|
||||||
|
expect(a_request(:post, "http://example.com/inbox")).to have_been_made.once
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -44,9 +44,9 @@ RSpec.describe FollowService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'remote account' do
|
context 'remote OStatus account' do
|
||||||
describe 'locked account' do
|
describe 'locked account' do
|
||||||
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, locked: true, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
|
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, protocol: :ostatus, locked: true, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:post, "http://salmon.example.com/").to_return(:status => 200, :body => "", :headers => {})
|
stub_request(:post, "http://salmon.example.com/").to_return(:status => 200, :body => "", :headers => {})
|
||||||
|
@ -66,7 +66,7 @@ RSpec.describe FollowService do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'unlocked account' do
|
describe 'unlocked account' do
|
||||||
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com', hub_url: 'http://hub.example.com')).account }
|
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, protocol: :ostatus, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com', hub_url: 'http://hub.example.com')).account }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:post, "http://salmon.example.com/").to_return(:status => 200, :body => "", :headers => {})
|
stub_request(:post, "http://salmon.example.com/").to_return(:status => 200, :body => "", :headers => {})
|
||||||
|
@ -91,7 +91,7 @@ RSpec.describe FollowService do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'already followed account' do
|
describe 'already followed account' do
|
||||||
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com', hub_url: 'http://hub.example.com')).account }
|
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, protocol: :ostatus, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com', hub_url: 'http://hub.example.com')).account }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
sender.follow!(bob)
|
sender.follow!(bob)
|
||||||
|
@ -111,4 +111,21 @@ RSpec.describe FollowService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'remote ActivityPub account' do
|
||||||
|
let(:bob) { Fabricate(:user, account: Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox')).account }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:post, "http://example.com/inbox").to_return(:status => 200, :body => "", :headers => {})
|
||||||
|
subject.call(sender, bob.acct)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates follow request' do
|
||||||
|
expect(FollowRequest.find_by(account: sender, target_account: bob)).to_not be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a follow activity to the inbox' do
|
||||||
|
expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -100,16 +100,18 @@ RSpec.describe PostStatusService do
|
||||||
expect(hashtags_service).to have_received(:call).with(status)
|
expect(hashtags_service).to have_received(:call).with(status)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'pings PuSH hubs' do
|
it 'gets distributed' do
|
||||||
allow(DistributionWorker).to receive(:perform_async)
|
allow(DistributionWorker).to receive(:perform_async)
|
||||||
allow(Pubsubhubbub::DistributionWorker).to receive(:perform_async)
|
allow(Pubsubhubbub::DistributionWorker).to receive(:perform_async)
|
||||||
|
allow(ActivityPub::DistributionWorker).to receive(:perform_async)
|
||||||
|
|
||||||
account = Fabricate(:account)
|
account = Fabricate(:account)
|
||||||
|
|
||||||
status = subject.call(account, "test status update")
|
status = subject.call(account, "test status update")
|
||||||
|
|
||||||
expect(DistributionWorker).to have_received(:perform_async).with(status.id)
|
expect(DistributionWorker).to have_received(:perform_async).with(status.id)
|
||||||
expect(Pubsubhubbub::DistributionWorker).
|
expect(Pubsubhubbub::DistributionWorker).to have_received(:perform_async).with(status.stream_entry.id)
|
||||||
to have_received(:perform_async).with(status.stream_entry.id)
|
expect(ActivityPub::DistributionWorker).to have_received(:perform_async).with(status.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'crawls links' do
|
it 'crawls links' do
|
||||||
|
|
|
@ -1,22 +1,44 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe ProcessMentionsService do
|
RSpec.describe ProcessMentionsService do
|
||||||
let(:account) { Fabricate(:account, username: 'alice') }
|
let(:account) { Fabricate(:account, username: 'alice') }
|
||||||
let(:remote_user) { Fabricate(:account, username: 'remote_user', domain: 'example.com', salmon_url: 'http://salmon.example.com') }
|
let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}") }
|
||||||
let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}") }
|
|
||||||
|
|
||||||
subject { ProcessMentionsService.new }
|
context 'OStatus' do
|
||||||
|
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') }
|
||||||
|
|
||||||
before do
|
subject { ProcessMentionsService.new }
|
||||||
stub_request(:post, remote_user.salmon_url)
|
|
||||||
subject.(status)
|
before do
|
||||||
|
stub_request(:post, remote_user.salmon_url)
|
||||||
|
subject.call(status)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a mention' do
|
||||||
|
expect(remote_user.mentions.where(status: status).count).to eq 1
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'posts to remote user\'s Salmon end point' do
|
||||||
|
expect(a_request(:post, remote_user.salmon_url)).to have_been_made.once
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates a mention' do
|
context 'ActivityPub' do
|
||||||
expect(remote_user.mentions.where(status: status).count).to eq 1
|
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
|
||||||
end
|
|
||||||
|
|
||||||
it 'posts to remote user\'s Salmon end point' do
|
subject { ProcessMentionsService.new }
|
||||||
expect(a_request(:post, remote_user.salmon_url)).to have_been_made
|
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,22 +2,49 @@ require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe ReblogService do
|
RSpec.describe ReblogService do
|
||||||
let(:alice) { Fabricate(:account, username: 'alice') }
|
let(:alice) { Fabricate(:account, username: 'alice') }
|
||||||
let(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com') }
|
|
||||||
let(:status) { Fabricate(:status, account: bob, uri: 'tag:example.com;something:something') }
|
|
||||||
|
|
||||||
subject { ReblogService.new }
|
context 'OStatus' do
|
||||||
|
let(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com') }
|
||||||
|
let(:status) { Fabricate(:status, account: bob, uri: 'tag:example.com;something:something') }
|
||||||
|
|
||||||
before do
|
subject { ReblogService.new }
|
||||||
stub_request(:post, 'http://salmon.example.com')
|
|
||||||
|
|
||||||
subject.(alice, status)
|
before do
|
||||||
|
stub_request(:post, 'http://salmon.example.com')
|
||||||
|
subject.call(alice, status)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a reblog' do
|
||||||
|
expect(status.reblogs.count).to eq 1
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a Salmon slap for a remote reblog' do
|
||||||
|
expect(a_request(:post, 'http://salmon.example.com')).to have_been_made
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates a reblog' do
|
context 'ActivityPub' do
|
||||||
expect(status.reblogs.count).to eq 1
|
let(:bob) { Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
|
||||||
end
|
let(:status) { Fabricate(:status, account: bob) }
|
||||||
|
|
||||||
it 'sends a Salmon slap for a remote reblog' do
|
subject { ReblogService.new }
|
||||||
expect(a_request(:post, 'http://salmon.example.com')).to have_been_made
|
|
||||||
|
before do
|
||||||
|
stub_request(:post, bob.inbox_url)
|
||||||
|
allow(ActivityPub::DistributionWorker).to receive(:perform_async)
|
||||||
|
subject.call(alice, status)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a reblog' do
|
||||||
|
expect(status.reblogs.count).to eq 1
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'distributes to followers' do
|
||||||
|
expect(ActivityPub::DistributionWorker).to have_received(:perform_async)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends an announce activity to the author' do
|
||||||
|
expect(a_request(:post, bob.inbox_url)).to have_been_made.once
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,7 +22,7 @@ RSpec.describe RejectFollowService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'remote' do
|
describe 'remote OStatus' do
|
||||||
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
|
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -46,4 +46,26 @@ RSpec.describe RejectFollowService do
|
||||||
}).to have_been_made.once
|
}).to have_been_made.once
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'remote ActivityPub' do
|
||||||
|
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox')).account }
|
||||||
|
|
||||||
|
before do
|
||||||
|
FollowRequest.create(account: bob, target_account: sender)
|
||||||
|
stub_request(:post, bob.inbox_url).to_return(status: 200)
|
||||||
|
subject.call(bob, sender)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes follow request' do
|
||||||
|
expect(bob.requested?(sender)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not create follow relation' do
|
||||||
|
expect(bob.following?(sender)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a reject activity' do
|
||||||
|
expect(a_request(:post, bob.inbox_url)).to have_been_made.once
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,13 +6,17 @@ RSpec.describe RemoveStatusService do
|
||||||
let!(:alice) { Fabricate(:account) }
|
let!(:alice) { Fabricate(:account) }
|
||||||
let!(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://example.com/salmon') }
|
let!(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://example.com/salmon') }
|
||||||
let!(:jeff) { Fabricate(:account) }
|
let!(:jeff) { Fabricate(:account) }
|
||||||
|
let!(:hank) { Fabricate(:account, username: 'hank', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:post, 'http://example.com/push').to_return(status: 200, body: '', headers: {})
|
stub_request(:post, 'http://example.com/push').to_return(status: 200, body: '', headers: {})
|
||||||
stub_request(:post, 'http://example.com/salmon').to_return(status: 200, body: '', headers: {})
|
stub_request(:post, 'http://example.com/salmon').to_return(status: 200, body: '', headers: {})
|
||||||
|
stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
|
||||||
|
|
||||||
Fabricate(:subscription, account: alice, callback_url: 'http://example.com/push', confirmed: true, expires_at: 30.days.from_now)
|
Fabricate(:subscription, account: alice, callback_url: 'http://example.com/push', confirmed: true, expires_at: 30.days.from_now)
|
||||||
jeff.follow!(alice)
|
jeff.follow!(alice)
|
||||||
|
hank.follow!(alice)
|
||||||
|
|
||||||
@status = PostStatusService.new.call(alice, 'Hello @bob@example.com')
|
@status = PostStatusService.new.call(alice, 'Hello @bob@example.com')
|
||||||
subject.call(@status)
|
subject.call(@status)
|
||||||
end
|
end
|
||||||
|
@ -31,6 +35,10 @@ RSpec.describe RemoveStatusService do
|
||||||
}).to have_been_made
|
}).to have_been_made
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'sends delete activity to followers' do
|
||||||
|
expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.twice
|
||||||
|
end
|
||||||
|
|
||||||
it 'sends Salmon slap to previously mentioned users' do
|
it 'sends Salmon slap to previously mentioned users' do
|
||||||
expect(a_request(:post, "http://example.com/salmon").with { |req|
|
expect(a_request(:post, "http://example.com/salmon").with { |req|
|
||||||
xml = OStatus2::Salmon.new.unpack(req.body)
|
xml = OStatus2::Salmon.new.unpack(req.body)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe ResolveRemoteAccountService do
|
RSpec.describe ResolveRemoteAccountService do
|
||||||
subject { ResolveRemoteAccountService.new }
|
subject { described_class.new }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:get, "https://quitter.no/.well-known/host-meta").to_return(request_fixture('.host-meta.txt'))
|
stub_request(:get, "https://quitter.no/.well-known/host-meta").to_return(request_fixture('.host-meta.txt'))
|
||||||
|
@ -29,29 +29,6 @@ RSpec.describe ResolveRemoteAccountService do
|
||||||
expect(subject.call('catsrgr8@example.com')).to be_nil
|
expect(subject.call('catsrgr8@example.com')).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns an already existing remote account' do
|
|
||||||
old_account = Fabricate(:account, username: 'gargron', domain: 'quitter.no')
|
|
||||||
returned_account = subject.call('gargron@quitter.no')
|
|
||||||
|
|
||||||
expect(old_account.id).to eq returned_account.id
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns a new remote account' do
|
|
||||||
account = subject.call('gargron@quitter.no')
|
|
||||||
|
|
||||||
expect(account.username).to eq 'gargron'
|
|
||||||
expect(account.domain).to eq 'quitter.no'
|
|
||||||
expect(account.remote_url).to eq 'https://quitter.no/api/statuses/user_timeline/7477.atom'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'follows a legitimate account redirection' do
|
|
||||||
account = subject.call('gargron@redirected.com')
|
|
||||||
|
|
||||||
expect(account.username).to eq 'gargron'
|
|
||||||
expect(account.domain).to eq 'quitter.no'
|
|
||||||
expect(account.remote_url).to eq 'https://quitter.no/api/statuses/user_timeline/7477.atom'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'prevents hijacking existing accounts' do
|
it 'prevents hijacking existing accounts' do
|
||||||
account = subject.call('hacker1@redirected.com')
|
account = subject.call('hacker1@redirected.com')
|
||||||
expect(account.salmon_url).to_not eq 'https://hacker.com/main/salmon/user/7477'
|
expect(account.salmon_url).to_not eq 'https://hacker.com/main/salmon/user/7477'
|
||||||
|
@ -61,12 +38,41 @@ RSpec.describe ResolveRemoteAccountService do
|
||||||
expect(subject.call('hacker2@redirected.com')).to be_nil
|
expect(subject.call('hacker2@redirected.com')).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns a new remote account' do
|
context 'with an OStatus account' do
|
||||||
account = subject.call('foo@localdomain.com')
|
it 'returns an already existing remote account' do
|
||||||
|
old_account = Fabricate(:account, username: 'gargron', domain: 'quitter.no')
|
||||||
|
returned_account = subject.call('gargron@quitter.no')
|
||||||
|
|
||||||
expect(account.username).to eq 'foo'
|
expect(old_account.id).to eq returned_account.id
|
||||||
expect(account.domain).to eq 'localdomain.com'
|
end
|
||||||
expect(account.remote_url).to eq 'https://webdomain.com/users/foo.atom'
|
|
||||||
|
it 'returns a new remote account' do
|
||||||
|
account = subject.call('gargron@quitter.no')
|
||||||
|
|
||||||
|
expect(account.username).to eq 'gargron'
|
||||||
|
expect(account.domain).to eq 'quitter.no'
|
||||||
|
expect(account.remote_url).to eq 'https://quitter.no/api/statuses/user_timeline/7477.atom'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'follows a legitimate account redirection' do
|
||||||
|
account = subject.call('gargron@redirected.com')
|
||||||
|
|
||||||
|
expect(account.username).to eq 'gargron'
|
||||||
|
expect(account.domain).to eq 'quitter.no'
|
||||||
|
expect(account.remote_url).to eq 'https://quitter.no/api/statuses/user_timeline/7477.atom'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a new remote account' do
|
||||||
|
account = subject.call('foo@localdomain.com')
|
||||||
|
|
||||||
|
expect(account.username).to eq 'foo'
|
||||||
|
expect(account.domain).to eq 'localdomain.com'
|
||||||
|
expect(account.remote_url).to eq 'https://webdomain.com/users/foo.atom'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with an ActivityPub account' do
|
||||||
|
pending
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'processes one remote account at a time using locks' do
|
it 'processes one remote account at a time using locks' do
|
||||||
|
@ -78,7 +84,7 @@ RSpec.describe ResolveRemoteAccountService do
|
||||||
Thread.new do
|
Thread.new do
|
||||||
true while wait_for_start
|
true while wait_for_start
|
||||||
begin
|
begin
|
||||||
return_values << ResolveRemoteAccountService.new.call('foo@localdomain.com')
|
return_values << described_class.new.call('foo@localdomain.com')
|
||||||
rescue ActiveRecord::RecordNotUnique
|
rescue ActiveRecord::RecordNotUnique
|
||||||
fail_occurred = true
|
fail_occurred = true
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,7 +18,7 @@ RSpec.describe UnblockService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'remote' do
|
describe 'remote OStatus' do
|
||||||
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
|
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -28,7 +28,7 @@ RSpec.describe UnblockService do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'destroys the blocking relation' do
|
it 'destroys the blocking relation' do
|
||||||
expect(sender.following?(bob)).to be false
|
expect(sender.blocking?(bob)).to be false
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sends an unblock salmon slap' do
|
it 'sends an unblock salmon slap' do
|
||||||
|
@ -38,4 +38,22 @@ RSpec.describe UnblockService do
|
||||||
}).to have_been_made.once
|
}).to have_been_made.once
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'remote ActivityPub' do
|
||||||
|
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox')).account }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sender.block!(bob)
|
||||||
|
stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
|
||||||
|
subject.call(sender, bob)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'destroys the blocking relation' do
|
||||||
|
expect(sender.blocking?(bob)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends an unblock activity' do
|
||||||
|
expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,8 +18,8 @@ RSpec.describe UnfollowService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'remote' do
|
describe 'remote OStatus' do
|
||||||
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
|
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
sender.follow!(bob)
|
sender.follow!(bob)
|
||||||
|
@ -38,4 +38,22 @@ RSpec.describe UnfollowService do
|
||||||
}).to have_been_made.once
|
}).to have_been_made.once
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'remote ActivityPub' do
|
||||||
|
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox')).account }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sender.follow!(bob)
|
||||||
|
stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
|
||||||
|
subject.call(sender, bob)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'destroys the following relation' do
|
||||||
|
expect(sender.following?(bob)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends an unfollow activity' do
|
||||||
|
expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe ActivityPub::DeliveryWorker do
|
||||||
|
subject { described_class.new }
|
||||||
|
|
||||||
|
let(:sender) { Fabricate(:account) }
|
||||||
|
let(:payload) { 'test' }
|
||||||
|
|
||||||
|
describe 'perform' do
|
||||||
|
it 'performs a request' do
|
||||||
|
stub_request(:post, 'https://example.com/api').to_return(status: 200)
|
||||||
|
subject.perform(payload, sender.id, 'https://example.com/api')
|
||||||
|
expect(a_request(:post, 'https://example.com/api')).to have_been_made.once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises when request fails' do
|
||||||
|
stub_request(:post, 'https://example.com/api').to_return(status: 500)
|
||||||
|
expect { subject.perform(payload, sender.id, 'https://example.com/api') }.to raise_error Mastodon::UnexpectedResponseError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,48 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe ActivityPub::DistributionWorker do
|
||||||
|
subject { described_class.new }
|
||||||
|
|
||||||
|
let(:status) { Fabricate(:status) }
|
||||||
|
let(:follower) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example.com') }
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
allow(ActivityPub::DeliveryWorker).to receive(:push_bulk)
|
||||||
|
follower.follow!(status.account)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with public status' do
|
||||||
|
before do
|
||||||
|
status.update(visibility: :public)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'delivers to followers' do
|
||||||
|
subject.perform(status.id)
|
||||||
|
expect(ActivityPub::DeliveryWorker).to have_received(:push_bulk).with(['http://example.com'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with private status' do
|
||||||
|
before do
|
||||||
|
status.update(visibility: :private)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'delivers to followers' do
|
||||||
|
subject.perform(status.id)
|
||||||
|
expect(ActivityPub::DeliveryWorker).to have_received(:push_bulk).with(['http://example.com'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with direct status' do
|
||||||
|
before do
|
||||||
|
status.update(visibility: :direct)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does nothing' do
|
||||||
|
subject.perform(status.id)
|
||||||
|
expect(ActivityPub::DeliveryWorker).to_not have_received(:push_bulk)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe ActivityPub::ProcessingWorker do
|
||||||
|
subject { described_class.new }
|
||||||
|
|
||||||
|
let(:account) { Fabricate(:account) }
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
it 'delegates to ActivityPub::ProcessCollectionService' do
|
||||||
|
allow(ActivityPub::ProcessCollectionService).to receive(:new).and_return(double(:service, call: nil))
|
||||||
|
subject.perform(account.id, '')
|
||||||
|
expect(ActivityPub::ProcessCollectionService).to have_received(:new)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,16 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe ActivityPub::ThreadResolveWorker do
|
||||||
|
subject { described_class.new }
|
||||||
|
|
||||||
|
let(:status) { Fabricate(:status) }
|
||||||
|
let(:parent) { Fabricate(:status) }
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
it 'gets parent from ActivityPub::FetchRemoteStatusService and glues them together' do
|
||||||
|
allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(double(:service, call: parent))
|
||||||
|
subject.perform(status.id, 'http://example.com/123')
|
||||||
|
expect(status.reload.in_reply_to_id).to eq parent.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,20 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe ActivityPub::UpdateDistributionWorker do
|
||||||
|
subject { described_class.new }
|
||||||
|
|
||||||
|
let(:account) { Fabricate(:account) }
|
||||||
|
let(:follower) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example.com') }
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
allow(ActivityPub::DeliveryWorker).to receive(:push_bulk)
|
||||||
|
follower.follow!(account)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'delivers to followers' do
|
||||||
|
subject.perform(account.id)
|
||||||
|
expect(ActivityPub::DeliveryWorker).to have_received(:push_bulk).with(['http://example.com'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue