ddd30f331c
* Add silent column to mentions * Save silent mentions in ActivityPub Create handler and optimize it Move networking calls out of the database transaction * Add "limited" visibility level masked as "private" in the API Unlike DMs, limited statuses are pushed into home feeds. The access control rules between direct and limited statuses is almost the same, except for counter and conversation logic * Ensure silent column is non-null, add spec * Ensure filters don't check silent mentions for blocks/mutes As those are "this person is also allowed to see" rather than "this person is involved", therefore does not warrant filtering * Clean up code * Use Status#active_mentions to limit returned mentions * Fix code style issues * Use Status#active_mentions in Notification And remove stream_entry eager-loading from Notification
411 lines
10 KiB
Ruby
411 lines
10 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe ActivityPub::Activity::Create do
|
|
let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers') }
|
|
|
|
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
|
|
|
|
subject { described_class.new(json, sender) }
|
|
|
|
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
|
|
before do
|
|
subject.perform
|
|
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
|
|
end
|
|
end
|