Gab Social. All are welcome.
This commit is contained in:
64
spec/lib/activitypub/activity/accept_spec.rb
Normal file
64
spec/lib/activitypub/activity/accept_spec.rb
Normal file
@@ -0,0 +1,64 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::Accept do
|
||||
let(:sender) { Fabricate(:account) }
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Accept',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: {
|
||||
id: 'bar',
|
||||
type: 'Follow',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(recipient),
|
||||
object: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
},
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
before do
|
||||
Fabricate(:follow_request, account: recipient, target_account: sender)
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'creates a follow relationship' do
|
||||
expect(recipient.following?(sender)).to be true
|
||||
end
|
||||
|
||||
it 'removes the follow request' do
|
||||
expect(recipient.requested?(sender)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'given a relay' do
|
||||
let!(:relay) { Fabricate(:relay, state: :pending, follow_activity_id: 'https://abc-123/456') }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Accept',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: {
|
||||
id: 'https://abc-123/456',
|
||||
type: 'Follow',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(recipient),
|
||||
object: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
},
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
it 'marks the relay as accepted' do
|
||||
subject.perform
|
||||
expect(relay.reload.accepted?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
48
spec/lib/activitypub/activity/add_spec.rb
Normal file
48
spec/lib/activitypub/activity/add_spec.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::Add do
|
||||
let(:sender) { Fabricate(:account, featured_collection_url: 'https://example.com/featured') }
|
||||
let(:status) { Fabricate(:status, account: sender) }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Add',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: ActivityPub::TagManager.instance.uri_for(status),
|
||||
target: sender.featured_collection_url,
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
it 'creates a pin' do
|
||||
subject.perform
|
||||
expect(sender.pinned?(status)).to be true
|
||||
end
|
||||
|
||||
context 'when status was not known before' do
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Add',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: 'https://example.com/unknown',
|
||||
target: sender.featured_collection_url,
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
before do
|
||||
stub_request(:get, 'https://example.com/unknown').to_return(status: 410)
|
||||
end
|
||||
|
||||
it 'fetches the status' do
|
||||
subject.perform
|
||||
expect(a_request(:get, 'https://example.com/unknown')).to have_been_made.at_least_once
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
172
spec/lib/activitypub/activity/announce_spec.rb
Normal file
172
spec/lib/activitypub/activity/announce_spec.rb
Normal file
@@ -0,0 +1,172 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::Announce do
|
||||
let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers', uri: 'https://example.com/actor') }
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
let(:status) { Fabricate(:status, account: recipient) }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Announce',
|
||||
actor: 'https://example.com/actor',
|
||||
object: object_json,
|
||||
to: 'http://example.com/followers',
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
let(:unknown_object_json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'https://example.com/actor/hello-world',
|
||||
type: 'Note',
|
||||
attributedTo: 'https://example.com/actor',
|
||||
content: 'Hello world',
|
||||
to: 'http://example.com/followers',
|
||||
}
|
||||
end
|
||||
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
describe '#perform' do
|
||||
context 'when sender is followed by a local account' do
|
||||
before do
|
||||
Fabricate(:account).follow!(sender)
|
||||
stub_request(:get, 'https://example.com/actor/hello-world').to_return(body: Oj.dump(unknown_object_json))
|
||||
subject.perform
|
||||
end
|
||||
|
||||
context 'a known status' do
|
||||
let(:object_json) do
|
||||
ActivityPub::TagManager.instance.uri_for(status)
|
||||
end
|
||||
|
||||
it 'creates a reblog by sender of status' do
|
||||
expect(sender.reblogged?(status)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'an unknown status' do
|
||||
let(:object_json) { 'https://example.com/actor/hello-world' }
|
||||
|
||||
it 'creates a reblog by sender of status' do
|
||||
reblog = sender.statuses.first
|
||||
|
||||
expect(reblog).to_not be_nil
|
||||
expect(reblog.reblog.text).to eq 'Hello world'
|
||||
end
|
||||
end
|
||||
|
||||
context 'self-repost of a previously unknown status with missing attributedTo' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: 'https://example.com/actor#bar',
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
to: 'http://example.com/followers',
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates a reblog by sender of status' do
|
||||
expect(sender.reblogged?(sender.statuses.first)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'self-repost of a previously unknown status with correct attributedTo' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: 'https://example.com/actor#bar',
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
attributedTo: 'https://example.com/actor',
|
||||
to: 'http://example.com/followers',
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates a reblog by sender of status' do
|
||||
expect(sender.reblogged?(sender.statuses.first)).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the status belongs to a local user' do
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
let(:object_json) do
|
||||
ActivityPub::TagManager.instance.uri_for(status)
|
||||
end
|
||||
|
||||
it 'creates a reblog by sender of status' do
|
||||
expect(sender.reblogged?(status)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the sender is relayed' do
|
||||
let!(:relay_account) { Fabricate(:account, inbox_url: 'https://relay.example.com/inbox') }
|
||||
let!(:relay) { Fabricate(:relay, inbox_url: 'https://relay.example.com/inbox') }
|
||||
|
||||
subject { described_class.new(json, sender, relayed_through_account: relay_account) }
|
||||
|
||||
context 'and the relay is enabled' do
|
||||
before do
|
||||
relay.update(state: :accepted)
|
||||
subject.perform
|
||||
end
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: 'https://example.com/actor#bar',
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
to: 'http://example.com/followers',
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates a reblog by sender of status' do
|
||||
expect(sender.statuses.count).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
context 'and the relay is disabled' do
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: 'https://example.com/actor#bar',
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
to: 'http://example.com/followers',
|
||||
}
|
||||
end
|
||||
|
||||
it 'does not create anything' do
|
||||
expect(sender.statuses.count).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the sender has no relevance to local activity' do
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: 'https://example.com/actor#bar',
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
to: 'http://example.com/followers',
|
||||
}
|
||||
end
|
||||
|
||||
it 'does not create anything' do
|
||||
expect(sender.statuses.count).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
85
spec/lib/activitypub/activity/block_spec.rb
Normal file
85
spec/lib/activitypub/activity/block_spec.rb
Normal file
@@ -0,0 +1,85 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::Block do
|
||||
let(:sender) { Fabricate(:account) }
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Block',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: ActivityPub::TagManager.instance.uri_for(recipient),
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
context 'when the recipient does not follow the sender' do
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'creates a block from sender to recipient' do
|
||||
expect(sender.blocking?(recipient)).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the recipient follows the sender' do
|
||||
before do
|
||||
recipient.follow!(sender)
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'creates a block from sender to recipient' do
|
||||
expect(sender.blocking?(recipient)).to be true
|
||||
end
|
||||
|
||||
it 'ensures recipient is not following sender' do
|
||||
expect(recipient.following?(sender)).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a matching undo has been received first' do
|
||||
let(:undo_json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'bar',
|
||||
type: 'Undo',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: json,
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
before do
|
||||
recipient.follow!(sender)
|
||||
ActivityPub::Activity::Undo.new(undo_json, sender).perform
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'does not create a block from sender to recipient' do
|
||||
expect(sender.blocking?(recipient)).to be false
|
||||
end
|
||||
|
||||
it 'ensures recipient is not following sender' do
|
||||
expect(recipient.following?(sender)).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
631
spec/lib/activitypub/activity/create_spec.rb
Normal file
631
spec/lib/activitypub/activity/create_spec.rb
Normal file
@@ -0,0 +1,631 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::Create do
|
||||
let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers', domain: 'example.com', uri: 'https://example.com/actor') }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#foo'].join,
|
||||
type: 'Create',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: object_json,
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
before do
|
||||
sender.update(uri: ActivityPub::TagManager.instance.uri_for(sender))
|
||||
|
||||
stub_request(:get, 'http://example.com/attachment.png').to_return(request_fixture('avatar.txt'))
|
||||
stub_request(:get, 'http://example.com/emoji.png').to_return(body: attachment_fixture('emojo.png'))
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
context 'when fetching' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
context 'unknown object type' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Banana',
|
||||
content: 'Lorem ipsum',
|
||||
}
|
||||
end
|
||||
|
||||
it 'does not create a status' do
|
||||
expect(sender.statuses.count).to be_zero
|
||||
end
|
||||
end
|
||||
|
||||
context 'standalone' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.text).to eq 'Lorem ipsum'
|
||||
end
|
||||
|
||||
it 'missing to/cc defaults to direct privacy' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.visibility).to eq 'direct'
|
||||
end
|
||||
end
|
||||
|
||||
context 'public' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
to: 'https://www.w3.org/ns/activitystreams#Public',
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.visibility).to eq 'public'
|
||||
end
|
||||
end
|
||||
|
||||
context 'unlisted' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
cc: 'https://www.w3.org/ns/activitystreams#Public',
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.visibility).to eq 'unlisted'
|
||||
end
|
||||
end
|
||||
|
||||
context 'private' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
to: 'http://example.com/followers',
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.visibility).to eq 'private'
|
||||
end
|
||||
end
|
||||
|
||||
context 'limited' do
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
to: ActivityPub::TagManager.instance.uri_for(recipient),
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.visibility).to eq 'limited'
|
||||
end
|
||||
|
||||
it 'creates silent mention' do
|
||||
status = sender.statuses.first
|
||||
expect(status.mentions.first).to be_silent
|
||||
end
|
||||
end
|
||||
|
||||
context 'direct' do
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
to: ActivityPub::TagManager.instance.uri_for(recipient),
|
||||
tag: {
|
||||
type: 'Mention',
|
||||
href: ActivityPub::TagManager.instance.uri_for(recipient),
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.visibility).to eq 'direct'
|
||||
end
|
||||
end
|
||||
|
||||
context 'as a reply' do
|
||||
let(:original_status) { Fabricate(:status) }
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
inReplyTo: ActivityPub::TagManager.instance.uri_for(original_status),
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.thread).to eq original_status
|
||||
expect(status.reply?).to be true
|
||||
expect(status.in_reply_to_account).to eq original_status.account
|
||||
expect(status.conversation).to eq original_status.conversation
|
||||
end
|
||||
end
|
||||
|
||||
context 'with mentions' do
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
tag: [
|
||||
{
|
||||
type: 'Mention',
|
||||
href: ActivityPub::TagManager.instance.uri_for(recipient),
|
||||
},
|
||||
],
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.mentions.map(&:account)).to include(recipient)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with mentions missing href' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
tag: [
|
||||
{
|
||||
type: 'Mention',
|
||||
},
|
||||
],
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
expect(status).to_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with media attachments' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
attachment: [
|
||||
{
|
||||
type: 'Document',
|
||||
mediaType: 'image/png',
|
||||
url: 'http://example.com/attachment.png',
|
||||
},
|
||||
],
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.media_attachments.map(&:remote_url)).to include('http://example.com/attachment.png')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with media attachments with focal points' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
attachment: [
|
||||
{
|
||||
type: 'Document',
|
||||
mediaType: 'image/png',
|
||||
url: 'http://example.com/attachment.png',
|
||||
focalPoint: [0.5, -0.7],
|
||||
},
|
||||
],
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.media_attachments.map(&:focus)).to include('0.5,-0.7')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with media attachments missing url' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
attachment: [
|
||||
{
|
||||
type: 'Document',
|
||||
mediaType: 'image/png',
|
||||
},
|
||||
],
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
expect(status).to_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with hashtags' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
tag: [
|
||||
{
|
||||
type: 'Hashtag',
|
||||
href: 'http://example.com/blah',
|
||||
name: '#test',
|
||||
},
|
||||
],
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.tags.map(&:name)).to include('test')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with hashtags missing name' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
tag: [
|
||||
{
|
||||
type: 'Hashtag',
|
||||
href: 'http://example.com/blah',
|
||||
},
|
||||
],
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
expect(status).to_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with emojis' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum :tinking:',
|
||||
tag: [
|
||||
{
|
||||
type: 'Emoji',
|
||||
icon: {
|
||||
url: 'http://example.com/emoji.png',
|
||||
},
|
||||
name: 'tinking',
|
||||
},
|
||||
],
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.emojis.map(&:shortcode)).to include('tinking')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with emojis missing name' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum :tinking:',
|
||||
tag: [
|
||||
{
|
||||
type: 'Emoji',
|
||||
icon: {
|
||||
url: 'http://example.com/emoji.png',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
expect(status).to_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with emojis missing icon' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum :tinking:',
|
||||
tag: [
|
||||
{
|
||||
type: 'Emoji',
|
||||
name: 'tinking',
|
||||
},
|
||||
],
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
expect(status).to_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with poll' do
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Question',
|
||||
content: 'Which color was the submarine?',
|
||||
oneOf: [
|
||||
{
|
||||
name: 'Yellow',
|
||||
replies: {
|
||||
type: 'Collection',
|
||||
totalItems: 10,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Blue',
|
||||
replies: {
|
||||
type: 'Collection',
|
||||
totalItems: 3,
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
expect(status).to_not be_nil
|
||||
expect(status.poll).to_not be_nil
|
||||
end
|
||||
|
||||
it 'creates a poll' do
|
||||
poll = sender.polls.first
|
||||
expect(poll).to_not be_nil
|
||||
expect(poll.status).to_not be_nil
|
||||
expect(poll.options).to eq %w(Yellow Blue)
|
||||
expect(poll.cached_tallies).to eq [10, 3]
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a vote to a local poll' do
|
||||
let(:poll) { Fabricate(:poll, options: %w(Yellow Blue)) }
|
||||
let!(:local_status) { Fabricate(:status, poll: poll) }
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
name: 'Yellow',
|
||||
inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status)
|
||||
}
|
||||
end
|
||||
|
||||
it 'adds a vote to the poll with correct uri' do
|
||||
vote = poll.votes.first
|
||||
expect(vote).to_not be_nil
|
||||
expect(vote.uri).to eq object_json[:id]
|
||||
expect(poll.reload.cached_tallies).to eq [1, 0]
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a vote to an expired local poll' do
|
||||
let(:poll) do
|
||||
poll = Fabricate.build(:poll, options: %w(Yellow Blue), expires_at: 1.day.ago)
|
||||
poll.save(validate: false)
|
||||
poll
|
||||
end
|
||||
let!(:local_status) { Fabricate(:status, poll: poll) }
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
name: 'Yellow',
|
||||
inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status)
|
||||
}
|
||||
end
|
||||
|
||||
it 'does not add a vote to the poll' do
|
||||
expect(poll.votes.first).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sender is followed by local users' do
|
||||
subject { described_class.new(json, sender, delivery: true) }
|
||||
|
||||
before do
|
||||
Fabricate(:account).follow!(sender)
|
||||
subject.perform
|
||||
end
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.text).to eq 'Lorem ipsum'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sender replies to local status' do
|
||||
let!(:local_status) { Fabricate(:status) }
|
||||
|
||||
subject { described_class.new(json, sender, delivery: true) }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status),
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.text).to eq 'Lorem ipsum'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sender targets a local user' do
|
||||
let!(:local_account) { Fabricate(:account) }
|
||||
|
||||
subject { described_class.new(json, sender, delivery: true) }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
to: ActivityPub::TagManager.instance.uri_for(local_account),
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.text).to eq 'Lorem ipsum'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sender cc\'s a local user' do
|
||||
let!(:local_account) { Fabricate(:account) }
|
||||
|
||||
subject { described_class.new(json, sender, delivery: true) }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
cc: ActivityPub::TagManager.instance.uri_for(local_account),
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates status' do
|
||||
status = sender.statuses.first
|
||||
|
||||
expect(status).to_not be_nil
|
||||
expect(status.text).to eq 'Lorem ipsum'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the sender has no relevance to local activity' do
|
||||
subject { described_class.new(json, sender, delivery: true) }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
}
|
||||
end
|
||||
|
||||
it 'does not create anything' do
|
||||
expect(sender.statuses.count).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
52
spec/lib/activitypub/activity/delete_spec.rb
Normal file
52
spec/lib/activitypub/activity/delete_spec.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::Delete do
|
||||
let(:sender) { Fabricate(:account, domain: 'example.com') }
|
||||
let(:status) { Fabricate(:status, account: sender, uri: 'foobar') }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Delete',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: ActivityPub::TagManager.instance.uri_for(status),
|
||||
signature: 'foo',
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'deletes sender\'s status' do
|
||||
expect(Status.find_by(id: status.id)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the status has been reblogged' do
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
let!(:reblogger) { Fabricate(:account) }
|
||||
let!(:follower) { Fabricate(:account, username: 'follower', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
|
||||
let!(:reblog) { Fabricate(:status, account: reblogger, reblog: status) }
|
||||
|
||||
before do
|
||||
stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
|
||||
follower.follow!(reblogger)
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'deletes sender\'s status' do
|
||||
expect(Status.find_by(id: status.id)).to be_nil
|
||||
end
|
||||
|
||||
it 'sends delete activity to followers of rebloggers' do
|
||||
expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
56
spec/lib/activitypub/activity/flag_spec.rb
Normal file
56
spec/lib/activitypub/activity/flag_spec.rb
Normal file
@@ -0,0 +1,56 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::Flag do
|
||||
let(:sender) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') }
|
||||
let(:flagged) { Fabricate(:account) }
|
||||
let(:status) { Fabricate(:status, account: flagged, uri: 'foobar') }
|
||||
let(:flag_id) { nil }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: flag_id,
|
||||
type: 'Flag',
|
||||
content: 'Boo!!',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: [
|
||||
ActivityPub::TagManager.instance.uri_for(flagged),
|
||||
ActivityPub::TagManager.instance.uri_for(status),
|
||||
],
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'creates a report' do
|
||||
report = Report.find_by(account: sender, target_account: flagged)
|
||||
|
||||
expect(report).to_not be_nil
|
||||
expect(report.comment).to eq 'Boo!!'
|
||||
expect(report.status_ids).to eq [status.id]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform with a defined uri' do
|
||||
subject { described_class.new(json, sender) }
|
||||
let (:flag_id) { 'http://example.com/reports/1' }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'creates a report' do
|
||||
report = Report.find_by(account: sender, target_account: flagged)
|
||||
|
||||
expect(report).to_not be_nil
|
||||
expect(report.comment).to eq 'Boo!!'
|
||||
expect(report.status_ids).to eq [status.id]
|
||||
expect(report.uri).to eq flag_id
|
||||
end
|
||||
end
|
||||
end
|
||||
49
spec/lib/activitypub/activity/follow_spec.rb
Normal file
49
spec/lib/activitypub/activity/follow_spec.rb
Normal file
@@ -0,0 +1,49 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::Follow do
|
||||
let(:sender) { Fabricate(:account) }
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Follow',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: ActivityPub::TagManager.instance.uri_for(recipient),
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
context 'unlocked account' do
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'creates a follow from sender to recipient' do
|
||||
expect(sender.following?(recipient)).to be true
|
||||
end
|
||||
|
||||
it 'does not create a follow request' do
|
||||
expect(sender.requested?(recipient)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'locked account' do
|
||||
before do
|
||||
recipient.update(locked: true)
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'does not create a follow from sender to recipient' do
|
||||
expect(sender.following?(recipient)).to be false
|
||||
end
|
||||
|
||||
it 'creates a follow request' do
|
||||
expect(sender.requested?(recipient)).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
29
spec/lib/activitypub/activity/like_spec.rb
Normal file
29
spec/lib/activitypub/activity/like_spec.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::Like do
|
||||
let(:sender) { Fabricate(:account) }
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
let(:status) { Fabricate(:status, account: recipient) }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Like',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: ActivityPub::TagManager.instance.uri_for(status),
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'creates a favourite from sender to status' do
|
||||
expect(sender.favourited?(status)).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
52
spec/lib/activitypub/activity/move_spec.rb
Normal file
52
spec/lib/activitypub/activity/move_spec.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::Move do
|
||||
let(:follower) { Fabricate(:account) }
|
||||
let(:old_account) { Fabricate(:account) }
|
||||
let(:new_account) { Fabricate(:account) }
|
||||
|
||||
before do
|
||||
follower.follow!(old_account)
|
||||
|
||||
old_account.update!(uri: 'https://example.org/alice', domain: 'example.org', protocol: :activitypub, inbox_url: 'https://example.org/inbox')
|
||||
new_account.update!(uri: 'https://example.com/alice', domain: 'example.com', protocol: :activitypub, inbox_url: 'https://example.com/inbox', also_known_as: [old_account.uri])
|
||||
|
||||
stub_request(:post, 'https://example.org/inbox').to_return(status: 200)
|
||||
stub_request(:post, 'https://example.com/inbox').to_return(status: 200)
|
||||
|
||||
service_stub = double
|
||||
allow(ActivityPub::FetchRemoteAccountService).to receive(:new).and_return(service_stub)
|
||||
allow(service_stub).to receive(:call).and_return(new_account)
|
||||
end
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Move',
|
||||
actor: old_account.uri,
|
||||
object: old_account.uri,
|
||||
target: new_account.uri,
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, old_account) }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'sets moved account on old account' do
|
||||
expect(old_account.reload.moved_to_account_id).to eq new_account.id
|
||||
end
|
||||
|
||||
it 'makes followers unfollow old account' do
|
||||
expect(follower.following?(old_account)).to be false
|
||||
end
|
||||
|
||||
it 'makes followers follow-request the new account' do
|
||||
expect(follower.requested?(new_account)).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
64
spec/lib/activitypub/activity/reject_spec.rb
Normal file
64
spec/lib/activitypub/activity/reject_spec.rb
Normal file
@@ -0,0 +1,64 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::Reject do
|
||||
let(:sender) { Fabricate(:account) }
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Reject',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: {
|
||||
id: 'bar',
|
||||
type: 'Follow',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(recipient),
|
||||
object: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
},
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
before do
|
||||
Fabricate(:follow_request, account: recipient, target_account: sender)
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'does not create a follow relationship' do
|
||||
expect(recipient.following?(sender)).to be false
|
||||
end
|
||||
|
||||
it 'removes the follow request' do
|
||||
expect(recipient.requested?(sender)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'given a relay' do
|
||||
let!(:relay) { Fabricate(:relay, state: :pending, follow_activity_id: 'https://abc-123/456') }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Reject',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: {
|
||||
id: 'https://abc-123/456',
|
||||
type: 'Follow',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(recipient),
|
||||
object: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
},
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
it 'marks the relay as rejected' do
|
||||
subject.perform
|
||||
expect(relay.reload.rejected?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
30
spec/lib/activitypub/activity/remove_spec.rb
Normal file
30
spec/lib/activitypub/activity/remove_spec.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::Remove do
|
||||
let(:sender) { Fabricate(:account, featured_collection_url: 'https://example.com/featured') }
|
||||
let(:status) { Fabricate(:status, account: sender) }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Add',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: ActivityPub::TagManager.instance.uri_for(status),
|
||||
target: sender.featured_collection_url,
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
before do
|
||||
StatusPin.create!(account: sender, status: status)
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'removes a pin' do
|
||||
expect(sender.pinned?(status)).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
147
spec/lib/activitypub/activity/undo_spec.rb
Normal file
147
spec/lib/activitypub/activity/undo_spec.rb
Normal file
@@ -0,0 +1,147 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::Undo do
|
||||
let(:sender) { Fabricate(:account, domain: 'example.com') }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Undo',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: object_json,
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
describe '#perform' do
|
||||
context 'with Announce' do
|
||||
let(:status) { Fabricate(:status) }
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: 'bar',
|
||||
type: 'Announce',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: ActivityPub::TagManager.instance.uri_for(status),
|
||||
atomUri: 'barbar',
|
||||
}
|
||||
end
|
||||
|
||||
context do
|
||||
before do
|
||||
Fabricate(:status, reblog: status, account: sender, uri: 'bar')
|
||||
end
|
||||
|
||||
it 'deletes the reblog' do
|
||||
subject.perform
|
||||
expect(sender.reblogged?(status)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'with atomUri' do
|
||||
before do
|
||||
Fabricate(:status, reblog: status, account: sender, uri: 'barbar')
|
||||
end
|
||||
|
||||
it 'deletes the reblog by atomUri' do
|
||||
subject.perform
|
||||
expect(sender.reblogged?(status)).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with Accept' do
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
let(:object_json) do
|
||||
{
|
||||
id: 'bar',
|
||||
type: 'Accept',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: 'follow-to-revoke',
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
recipient.follow!(sender, uri: 'follow-to-revoke')
|
||||
end
|
||||
|
||||
it 'deletes follow from recipient to sender' do
|
||||
subject.perform
|
||||
expect(recipient.following?(sender)).to be false
|
||||
end
|
||||
|
||||
it 'creates a follow request from recipient to sender' do
|
||||
subject.perform
|
||||
expect(recipient.requested?(sender)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'with Block' do
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: 'bar',
|
||||
type: 'Block',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: ActivityPub::TagManager.instance.uri_for(recipient),
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
sender.block!(recipient)
|
||||
end
|
||||
|
||||
it 'deletes block from sender to recipient' do
|
||||
subject.perform
|
||||
expect(sender.blocking?(recipient)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'with Follow' do
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: 'bar',
|
||||
type: 'Follow',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: ActivityPub::TagManager.instance.uri_for(recipient),
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
sender.follow!(recipient)
|
||||
end
|
||||
|
||||
it 'deletes follow from sender to recipient' do
|
||||
subject.perform
|
||||
expect(sender.following?(recipient)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'with Like' do
|
||||
let(:status) { Fabricate(:status) }
|
||||
|
||||
let(:object_json) do
|
||||
{
|
||||
id: 'bar',
|
||||
type: 'Like',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: ActivityPub::TagManager.instance.uri_for(status),
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
Fabricate(:favourite, account: sender, status: status)
|
||||
end
|
||||
|
||||
it 'deletes favourite from sender to status' do
|
||||
subject.perform
|
||||
expect(sender.favourited?(status)).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
46
spec/lib/activitypub/activity/update_spec.rb
Normal file
46
spec/lib/activitypub/activity/update_spec.rb
Normal file
@@ -0,0 +1,46 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::Update do
|
||||
let!(:sender) { Fabricate(:account) }
|
||||
|
||||
before do
|
||||
stub_request(:get, actor_json[:outbox]).to_return(status: 404)
|
||||
stub_request(:get, actor_json[:followers]).to_return(status: 404)
|
||||
stub_request(:get, actor_json[:following]).to_return(status: 404)
|
||||
stub_request(:get, actor_json[:featured]).to_return(status: 404)
|
||||
|
||||
sender.update!(uri: ActivityPub::TagManager.instance.uri_for(sender))
|
||||
end
|
||||
|
||||
let(:modified_sender) do
|
||||
sender.dup.tap do |modified_sender|
|
||||
modified_sender.display_name = 'Totally modified now'
|
||||
end
|
||||
end
|
||||
|
||||
let(:actor_json) do
|
||||
ActiveModelSerializers::SerializableResource.new(modified_sender, serializer: ActivityPub::ActorSerializer, key_transform: :camel_lower).as_json
|
||||
end
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'foo',
|
||||
type: 'Update',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: actor_json,
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'updates profile' do
|
||||
expect(sender.reload.display_name).to eq 'Totally modified now'
|
||||
end
|
||||
end
|
||||
end
|
||||
88
spec/lib/activitypub/adapter_spec.rb
Normal file
88
spec/lib/activitypub/adapter_spec.rb
Normal file
@@ -0,0 +1,88 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Adapter do
|
||||
class TestObject < ActiveModelSerializers::Model
|
||||
attributes :foo
|
||||
end
|
||||
|
||||
class TestWithBasicContextSerializer < ActivityPub::Serializer
|
||||
attributes :foo
|
||||
end
|
||||
|
||||
class TestWithNamedContextSerializer < ActivityPub::Serializer
|
||||
context :security
|
||||
attributes :foo
|
||||
end
|
||||
|
||||
class TestWithNestedNamedContextSerializer < ActivityPub::Serializer
|
||||
attributes :foo
|
||||
|
||||
has_one :virtual_object, key: :baz, serializer: TestWithNamedContextSerializer
|
||||
|
||||
def virtual_object
|
||||
object
|
||||
end
|
||||
end
|
||||
|
||||
class TestWithContextExtensionSerializer < ActivityPub::Serializer
|
||||
context_extensions :sensitive
|
||||
attributes :foo
|
||||
end
|
||||
|
||||
class TestWithNestedContextExtensionSerializer < ActivityPub::Serializer
|
||||
context_extensions :manually_approves_followers
|
||||
attributes :foo
|
||||
|
||||
has_one :virtual_object, key: :baz, serializer: TestWithContextExtensionSerializer
|
||||
|
||||
def virtual_object
|
||||
object
|
||||
end
|
||||
end
|
||||
|
||||
describe '#serializable_hash' do
|
||||
let(:serializer_class) {}
|
||||
|
||||
subject { ActiveModelSerializers::SerializableResource.new(TestObject.new(foo: 'bar'), serializer: serializer_class, adapter: described_class).as_json }
|
||||
|
||||
context 'when serializer defines no context' do
|
||||
let(:serializer_class) { TestWithBasicContextSerializer }
|
||||
|
||||
it 'renders a basic @context' do
|
||||
expect(subject).to include({ '@context' => 'https://www.w3.org/ns/activitystreams' })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when serializer defines a named context' do
|
||||
let(:serializer_class) { TestWithNamedContextSerializer }
|
||||
|
||||
it 'renders a @context with both items' do
|
||||
expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when serializer has children that define a named context' do
|
||||
let(:serializer_class) { TestWithNestedNamedContextSerializer }
|
||||
|
||||
it 'renders a @context with both items' do
|
||||
expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when serializer defines context extensions' do
|
||||
let(:serializer_class) { TestWithContextExtensionSerializer }
|
||||
|
||||
it 'renders a @context with the extension' do
|
||||
expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', { 'sensitive' => 'as:sensitive' }] })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when serializer has children that define context extensions' do
|
||||
let(:serializer_class) { TestWithNestedContextExtensionSerializer }
|
||||
|
||||
it 'renders a @context with both extensions' do
|
||||
expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'sensitive' => 'as:sensitive' }] })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
86
spec/lib/activitypub/linked_data_signature_spec.rb
Normal file
86
spec/lib/activitypub/linked_data_signature_spec.rb
Normal file
@@ -0,0 +1,86 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::LinkedDataSignature do
|
||||
include JsonLdHelper
|
||||
|
||||
let!(:sender) { Fabricate(:account, uri: 'http://example.com/alice') }
|
||||
|
||||
let(:raw_json) do
|
||||
{
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'id' => 'http://example.com/hello-world',
|
||||
}
|
||||
end
|
||||
|
||||
let(:json) { raw_json.merge('signature' => signature) }
|
||||
|
||||
subject { described_class.new(json) }
|
||||
|
||||
before do
|
||||
stub_jsonld_contexts!
|
||||
end
|
||||
|
||||
describe '#verify_account!' do
|
||||
context 'when signature matches' do
|
||||
let(:raw_signature) do
|
||||
{
|
||||
'creator' => 'http://example.com/alice',
|
||||
'created' => '2017-09-23T20:21:34Z',
|
||||
}
|
||||
end
|
||||
|
||||
let(:signature) { raw_signature.merge('type' => 'RsaSignature2017', 'signatureValue' => sign(sender, raw_signature, raw_json)) }
|
||||
|
||||
it 'returns creator' do
|
||||
expect(subject.verify_account!).to eq sender
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signature is missing' do
|
||||
let(:signature) { nil }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(subject.verify_account!).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signature is tampered' do
|
||||
let(:raw_signature) do
|
||||
{
|
||||
'creator' => 'http://example.com/alice',
|
||||
'created' => '2017-09-23T20:21:34Z',
|
||||
}
|
||||
end
|
||||
|
||||
let(:signature) { raw_signature.merge('type' => 'RsaSignature2017', 'signatureValue' => 's69F3mfddd99dGjmvjdjjs81e12jn121Gkm1') }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(subject.verify_account!).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sign!' do
|
||||
subject { described_class.new(raw_json).sign!(sender) }
|
||||
|
||||
it 'returns a hash' do
|
||||
expect(subject).to be_a Hash
|
||||
end
|
||||
|
||||
it 'contains signature' do
|
||||
expect(subject['signature']).to be_a Hash
|
||||
expect(subject['signature']['signatureValue']).to be_present
|
||||
end
|
||||
|
||||
it 'can be verified again' do
|
||||
expect(described_class.new(subject).verify_account!).to eq sender
|
||||
end
|
||||
end
|
||||
|
||||
def sign(from_account, options, document)
|
||||
options_hash = Digest::SHA256.hexdigest(canonicalize(options.merge('@context' => ActivityPub::LinkedDataSignature::CONTEXT)))
|
||||
document_hash = Digest::SHA256.hexdigest(canonicalize(document))
|
||||
to_be_verified = options_hash + document_hash
|
||||
Base64.strict_encode64(from_account.keypair.sign(OpenSSL::Digest::SHA256.new, to_be_verified))
|
||||
end
|
||||
end
|
||||
157
spec/lib/activitypub/tag_manager_spec.rb
Normal file
157
spec/lib/activitypub/tag_manager_spec.rb
Normal file
@@ -0,0 +1,157 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::TagManager do
|
||||
include RoutingHelper
|
||||
|
||||
subject { described_class.instance }
|
||||
|
||||
describe '#url_for' do
|
||||
it 'returns a string' do
|
||||
account = Fabricate(:account)
|
||||
expect(subject.url_for(account)).to be_a String
|
||||
end
|
||||
end
|
||||
|
||||
describe '#uri_for' do
|
||||
it 'returns a string' do
|
||||
account = Fabricate(:account)
|
||||
expect(subject.uri_for(account)).to be_a String
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to' do
|
||||
it 'returns public collection for public status' do
|
||||
status = Fabricate(:status, visibility: :public)
|
||||
expect(subject.to(status)).to eq ['https://www.w3.org/ns/activitystreams#Public']
|
||||
end
|
||||
|
||||
it 'returns followers collection for unlisted status' do
|
||||
status = Fabricate(:status, visibility: :unlisted)
|
||||
expect(subject.to(status)).to eq [account_followers_url(status.account)]
|
||||
end
|
||||
|
||||
it 'returns followers collection for private status' do
|
||||
status = Fabricate(:status, visibility: :private)
|
||||
expect(subject.to(status)).to eq [account_followers_url(status.account)]
|
||||
end
|
||||
|
||||
it 'returns URIs of mentions for direct status' do
|
||||
status = Fabricate(:status, visibility: :direct)
|
||||
mentioned = Fabricate(:account)
|
||||
status.mentions.create(account: mentioned)
|
||||
expect(subject.to(status)).to eq [subject.uri_for(mentioned)]
|
||||
end
|
||||
|
||||
it "returns URIs of mentions for direct silenced author's status only if they are followers or requesting to be" do
|
||||
bob = Fabricate(:account, username: 'bob')
|
||||
alice = Fabricate(:account, username: 'alice')
|
||||
foo = Fabricate(:account)
|
||||
author = Fabricate(:account, username: 'author', silenced: true)
|
||||
status = Fabricate(:status, visibility: :direct, account: author)
|
||||
bob.follow!(author)
|
||||
FollowRequest.create!(account: foo, target_account: author)
|
||||
status.mentions.create(account: alice)
|
||||
status.mentions.create(account: bob)
|
||||
status.mentions.create(account: foo)
|
||||
expect(subject.to(status)).to include(subject.uri_for(bob))
|
||||
expect(subject.to(status)).to include(subject.uri_for(foo))
|
||||
expect(subject.to(status)).to_not include(subject.uri_for(alice))
|
||||
end
|
||||
end
|
||||
|
||||
describe '#cc' do
|
||||
it 'returns followers collection for public status' do
|
||||
status = Fabricate(:status, visibility: :public)
|
||||
expect(subject.cc(status)).to eq [account_followers_url(status.account)]
|
||||
end
|
||||
|
||||
it 'returns public collection for unlisted status' do
|
||||
status = Fabricate(:status, visibility: :unlisted)
|
||||
expect(subject.cc(status)).to eq ['https://www.w3.org/ns/activitystreams#Public']
|
||||
end
|
||||
|
||||
it 'returns empty array for private status' do
|
||||
status = Fabricate(:status, visibility: :private)
|
||||
expect(subject.cc(status)).to eq []
|
||||
end
|
||||
|
||||
it 'returns empty array for direct status' do
|
||||
status = Fabricate(:status, visibility: :direct)
|
||||
expect(subject.cc(status)).to eq []
|
||||
end
|
||||
|
||||
it 'returns URIs of mentions for non-direct status' do
|
||||
status = Fabricate(:status, visibility: :public)
|
||||
mentioned = Fabricate(:account)
|
||||
status.mentions.create(account: mentioned)
|
||||
expect(subject.cc(status)).to include(subject.uri_for(mentioned))
|
||||
end
|
||||
|
||||
it "returns URIs of mentions for silenced author's non-direct status only if they are followers or requesting to be" do
|
||||
bob = Fabricate(:account, username: 'bob')
|
||||
alice = Fabricate(:account, username: 'alice')
|
||||
foo = Fabricate(:account)
|
||||
author = Fabricate(:account, username: 'author', silenced: true)
|
||||
status = Fabricate(:status, visibility: :public, account: author)
|
||||
bob.follow!(author)
|
||||
FollowRequest.create!(account: foo, target_account: author)
|
||||
status.mentions.create(account: alice)
|
||||
status.mentions.create(account: bob)
|
||||
status.mentions.create(account: foo)
|
||||
expect(subject.cc(status)).to include(subject.uri_for(bob))
|
||||
expect(subject.cc(status)).to include(subject.uri_for(foo))
|
||||
expect(subject.cc(status)).to_not include(subject.uri_for(alice))
|
||||
end
|
||||
end
|
||||
|
||||
describe '#local_uri?' do
|
||||
it 'returns false for non-local URI' do
|
||||
expect(subject.local_uri?('http://example.com/123')).to be false
|
||||
end
|
||||
|
||||
it 'returns true for local URIs' do
|
||||
account = Fabricate(:account)
|
||||
expect(subject.local_uri?(subject.uri_for(account))).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe '#uri_to_local_id' do
|
||||
it 'returns the local ID' do
|
||||
account = Fabricate(:account)
|
||||
expect(subject.uri_to_local_id(subject.uri_for(account), :username)).to eq account.username
|
||||
end
|
||||
end
|
||||
|
||||
describe '#uri_to_resource' do
|
||||
it 'returns the local account' do
|
||||
account = Fabricate(:account)
|
||||
expect(subject.uri_to_resource(subject.uri_for(account), Account)).to eq account
|
||||
end
|
||||
|
||||
it 'returns the remote account by matching URI without fragment part' do
|
||||
account = Fabricate(:account, uri: 'https://example.com/123')
|
||||
expect(subject.uri_to_resource('https://example.com/123#456', Account)).to eq account
|
||||
end
|
||||
|
||||
it 'returns the local status for ActivityPub URI' do
|
||||
status = Fabricate(:status)
|
||||
expect(subject.uri_to_resource(subject.uri_for(status), Status)).to eq status
|
||||
end
|
||||
|
||||
it 'returns the local status for OStatus tag: URI' do
|
||||
status = Fabricate(:status)
|
||||
expect(subject.uri_to_resource(OStatus::TagManager.instance.uri_for(status), Status)).to eq status
|
||||
end
|
||||
|
||||
it 'returns the local status for OStatus StreamEntry URL' do
|
||||
status = Fabricate(:status)
|
||||
stream_entry_url = account_stream_entry_url(status.account, status.stream_entry)
|
||||
expect(subject.uri_to_resource(stream_entry_url, Status)).to eq status
|
||||
end
|
||||
|
||||
it 'returns the remote status by matching URI without fragment part' do
|
||||
status = Fabricate(:status, uri: 'https://example.com/123')
|
||||
expect(subject.uri_to_resource('https://example.com/123#456', Status)).to eq status
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user