Adding a test for ReblogService, fixing mentions for remote statuses
This commit is contained in:
parent
47d50b0e39
commit
11ff92c9d7
1
Gemfile
1
Gemfile
|
@ -40,6 +40,7 @@ end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'simplecov', require: false
|
gem 'simplecov', require: false
|
||||||
|
gem 'webmock'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
|
|
|
@ -65,6 +65,8 @@ GEM
|
||||||
execjs
|
execjs
|
||||||
coffee-script-source (1.10.0)
|
coffee-script-source (1.10.0)
|
||||||
concurrent-ruby (1.0.0)
|
concurrent-ruby (1.0.0)
|
||||||
|
crack (0.4.3)
|
||||||
|
safe_yaml (~> 1.0.0)
|
||||||
debug_inspector (0.0.2)
|
debug_inspector (0.0.2)
|
||||||
descendants_tracker (0.0.4)
|
descendants_tracker (0.0.4)
|
||||||
thread_safe (~> 0.3, >= 0.3.1)
|
thread_safe (~> 0.3, >= 0.3.1)
|
||||||
|
@ -116,6 +118,7 @@ GEM
|
||||||
haml (>= 4.0.6, < 5.0)
|
haml (>= 4.0.6, < 5.0)
|
||||||
html2haml (>= 1.0.1)
|
html2haml (>= 1.0.1)
|
||||||
railties (>= 4.0.1)
|
railties (>= 4.0.1)
|
||||||
|
hashdiff (0.3.0)
|
||||||
hashie (3.4.3)
|
hashie (3.4.3)
|
||||||
hashie-forbidden_attributes (0.1.1)
|
hashie-forbidden_attributes (0.1.1)
|
||||||
hashie (>= 3.0)
|
hashie (>= 3.0)
|
||||||
|
@ -255,6 +258,7 @@ GEM
|
||||||
ruby-progressbar (1.7.5)
|
ruby-progressbar (1.7.5)
|
||||||
ruby_parser (3.8.1)
|
ruby_parser (3.8.1)
|
||||||
sexp_processor (~> 4.1)
|
sexp_processor (~> 4.1)
|
||||||
|
safe_yaml (1.0.4)
|
||||||
sass (3.4.21)
|
sass (3.4.21)
|
||||||
sass-rails (5.0.4)
|
sass-rails (5.0.4)
|
||||||
railties (>= 4.0.0, < 5.0)
|
railties (>= 4.0.0, < 5.0)
|
||||||
|
@ -306,6 +310,10 @@ GEM
|
||||||
binding_of_caller (>= 0.7.2)
|
binding_of_caller (>= 0.7.2)
|
||||||
railties (>= 4.0)
|
railties (>= 4.0)
|
||||||
sprockets-rails (>= 2.0, < 4.0)
|
sprockets-rails (>= 2.0, < 4.0)
|
||||||
|
webmock (1.24.1)
|
||||||
|
addressable (>= 2.3.6)
|
||||||
|
crack (>= 0.3.2)
|
||||||
|
hashdiff
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
@ -349,6 +357,7 @@ DEPENDENCIES
|
||||||
therubyracer
|
therubyracer
|
||||||
uglifier (>= 1.3.0)
|
uglifier (>= 1.3.0)
|
||||||
web-console (~> 2.0)
|
web-console (~> 2.0)
|
||||||
|
webmock
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.11.2
|
1.11.2
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
module AtomHelper
|
module AtomHelper
|
||||||
def stream_updated_at
|
def stream_updated_at
|
||||||
@account.stream_entries.last ? @account.stream_entries.last.created_at : @account.updated_at
|
@account.stream_entries.last ? (@account.updated_at > @account.stream_entries.last.created_at ? @account.updated_at : @account.stream_entries.last.created_at) : @account.updated_at
|
||||||
end
|
end
|
||||||
|
|
||||||
def entry(xml, is_root, &block)
|
def entry(xml, is_root, &block)
|
||||||
|
@ -126,7 +126,9 @@ module AtomHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def link_avatar(xml, account)
|
def link_avatar(xml, account)
|
||||||
xml.link(rel: 'avatar', type: account.avatar_content_type, href: asset_url(account.avatar.url(:large)))
|
xml.link('rel' => 'avatar', 'type' => account.avatar_content_type, 'media:width' => '300', 'media:height' =>'300', 'href' => asset_url(account.avatar.url(:large)))
|
||||||
|
xml.link('rel' => 'avatar', 'type' => account.avatar_content_type, 'media:width' => '96', 'media:height' =>'96', 'href' => asset_url(account.avatar.url(:medium)))
|
||||||
|
xml.link('rel' => 'avatar', 'type' => account.avatar_content_type, 'media:width' => '48', 'media:height' =>'48', 'href' => asset_url(account.avatar.url(:small)))
|
||||||
end
|
end
|
||||||
|
|
||||||
def logo(xml, url)
|
def logo(xml, url)
|
||||||
|
|
|
@ -11,6 +11,7 @@ class Account < ActiveRecord::Base
|
||||||
has_many :stream_entries, inverse_of: :account
|
has_many :stream_entries, inverse_of: :account
|
||||||
has_many :statuses, inverse_of: :account
|
has_many :statuses, inverse_of: :account
|
||||||
has_many :favourites, inverse_of: :account
|
has_many :favourites, inverse_of: :account
|
||||||
|
has_many :mentions, inverse_of: :account
|
||||||
|
|
||||||
# Follow relations
|
# Follow relations
|
||||||
has_many :active_relationships, class_name: 'Follow', foreign_key: 'account_id', dependent: :destroy
|
has_many :active_relationships, class_name: 'Follow', foreign_key: 'account_id', dependent: :destroy
|
||||||
|
@ -77,7 +78,7 @@ class Account < ActiveRecord::Base
|
||||||
|
|
||||||
before_create do
|
before_create do
|
||||||
if local?
|
if local?
|
||||||
keypair = OpenSSL::PKey::RSA.new(Rails.env.test? ? 48 : 2048)
|
keypair = OpenSSL::PKey::RSA.new(Rails.env.test? ? 1024 : 2048)
|
||||||
self.private_key = keypair.to_pem
|
self.private_key = keypair.to_pem
|
||||||
self.public_key = keypair.public_key.to_pem
|
self.public_key = keypair.public_key.to_pem
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
class Mention < ActiveRecord::Base
|
class Mention < ActiveRecord::Base
|
||||||
belongs_to :account, inverse_of: :mentions
|
belongs_to :account, inverse_of: :mentions
|
||||||
belongs_to :status, inverse_of: :mentions
|
belongs_to :status
|
||||||
|
|
||||||
validates :account, :status, presence: true
|
validates :account, :status, presence: true
|
||||||
validates :account, uniqueness: { scope: :status }
|
validates :account, uniqueness: { scope: :status }
|
||||||
|
|
|
@ -5,10 +5,12 @@ class ProcessFeedService < BaseService
|
||||||
def call(body, account)
|
def call(body, account)
|
||||||
xml = Nokogiri::XML(body)
|
xml = Nokogiri::XML(body)
|
||||||
|
|
||||||
|
# If we got a full feed, make sure the account's profile is up to date
|
||||||
unless xml.at_xpath('/xmlns:feed').nil?
|
unless xml.at_xpath('/xmlns:feed').nil?
|
||||||
update_remote_profile_service.(xml.at_xpath('/xmlns:feed/xmlns:author'), account)
|
update_remote_profile_service.(xml.at_xpath('/xmlns:feed/xmlns:author'), account)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Process entries
|
||||||
xml.xpath('//xmlns:entry').each do |entry|
|
xml.xpath('//xmlns:entry').each do |entry|
|
||||||
next unless [:note, :comment, :activity].include? object_type(entry)
|
next unless [:note, :comment, :activity].include? object_type(entry)
|
||||||
|
|
||||||
|
@ -16,7 +18,7 @@ class ProcessFeedService < BaseService
|
||||||
|
|
||||||
next unless status.nil?
|
next unless status.nil?
|
||||||
|
|
||||||
status = Status.new(uri: activity_id(entry), account: account, text: content(entry), created_at: published(entry), updated_at: updated(entry))
|
status = Status.new(uri: activity_id(entry), url: activity_link(entry), account: account, text: content(entry), created_at: published(entry), updated_at: updated(entry))
|
||||||
|
|
||||||
if object_type(entry) == :comment
|
if object_type(entry) == :comment
|
||||||
add_reply!(entry, status)
|
add_reply!(entry, status)
|
||||||
|
@ -26,7 +28,25 @@ class ProcessFeedService < BaseService
|
||||||
add_post!(entry, status)
|
add_post!(entry, status)
|
||||||
end
|
end
|
||||||
|
|
||||||
process_mentions_service.(status) unless status.new_record?
|
# If we added a status, go through accounts it mentions and create respective relations
|
||||||
|
unless status.new_record?
|
||||||
|
entry.xpath('./xmlns:link[@rel="mentioned"]').each do |mention_link|
|
||||||
|
# Here we have to do a reverse lookup of local accounts by their URL!
|
||||||
|
# It's not pretty at all! I really wish all these protocols sticked to
|
||||||
|
# using acct:username@domain only! It would make things so much easier
|
||||||
|
# and tidier
|
||||||
|
|
||||||
|
href = Addressable::URI.parse(mention_link.attribute('href').value)
|
||||||
|
|
||||||
|
if href.host == LOCAL_DOMAIN
|
||||||
|
mentioned_account = Account.find_by(username: href.path.gsub('/users/', ''), domain: nil)
|
||||||
|
|
||||||
|
unless mentioned_account.nil?
|
||||||
|
mentioned_account.mentions.first_or_create(status: status)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -103,12 +123,18 @@ class ProcessFeedService < BaseService
|
||||||
xml.at_xpath('./xmlns:id').content
|
xml.at_xpath('./xmlns:id').content
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def activity_link(xml)
|
||||||
|
xml.at_xpath('./xmlns:link[@rel="alternate"]').attribute('href').value
|
||||||
|
rescue
|
||||||
|
''
|
||||||
|
end
|
||||||
|
|
||||||
def target_content(xml)
|
def target_content(xml)
|
||||||
xml.at_xpath('.//activity:object/xmlns:content').content
|
xml.at_xpath('.//activity:object/xmlns:content').content
|
||||||
end
|
end
|
||||||
|
|
||||||
def target_url(xml)
|
def target_url(xml)
|
||||||
xml.at_xpath('.//activity:object/xmlns:link[@rel=alternate]').attribute('href').value
|
xml.at_xpath('.//activity:object/xmlns:link[@rel="alternate"]').attribute('href').value
|
||||||
end
|
end
|
||||||
|
|
||||||
def object_type(xml)
|
def object_type(xml)
|
||||||
|
@ -127,10 +153,6 @@ class ProcessFeedService < BaseService
|
||||||
@follow_remote_account_service ||= FollowRemoteAccountService.new
|
@follow_remote_account_service ||= FollowRemoteAccountService.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_mentions_service
|
|
||||||
@process_mentions_service ||= ProcessMentionsService.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_remote_profile_service
|
def update_remote_profile_service
|
||||||
@update_remote_profile_service ||= UpdateRemoteProfileService.new
|
@update_remote_profile_service ||= UpdateRemoteProfileService.new
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,15 +4,17 @@ class ProcessMentionsService < BaseService
|
||||||
# remote users
|
# remote users
|
||||||
# @param [Status] status
|
# @param [Status] status
|
||||||
def call(status)
|
def call(status)
|
||||||
status.text.scan(Account::MENTION_RE).each do |match|
|
return unless status.local?
|
||||||
username, domain = match.first.split('@')
|
|
||||||
local_account = Account.find_by(username: username, domain: domain)
|
|
||||||
|
|
||||||
if local_account.nil?
|
status.text.scan(Account::MENTION_RE).each do |match|
|
||||||
local_account = follow_remote_account_service.("acct:#{match.first}")
|
username, domain = match.first.split('@')
|
||||||
|
mentioned_account = Account.find_by(username: username, domain: domain)
|
||||||
|
|
||||||
|
if mentioned_account.nil?
|
||||||
|
mentioned_account = follow_remote_account_service.("acct:#{match.first}")
|
||||||
end
|
end
|
||||||
|
|
||||||
local_account.mentions.first_or_create(status: status)
|
mentioned_account.mentions.first_or_create(status: status)
|
||||||
end
|
end
|
||||||
|
|
||||||
status.mentions.each do |mentioned_account|
|
status.mentions.each do |mentioned_account|
|
||||||
|
|
|
@ -1,10 +1,2 @@
|
||||||
.counter.counter-reblogs
|
|
||||||
%i.fa.fa-retweet
|
|
||||||
%span.num= status.reblogs.count
|
|
||||||
|
|
||||||
.counter.counter-favourites
|
|
||||||
%i.fa.fa-star
|
|
||||||
%span.num= status.favourites.count
|
|
||||||
|
|
||||||
- if status.reply?
|
- if status.reply?
|
||||||
= link_to "In response to #{status.thread.account.acct}", status_url(status.thread), class: 'conversation-link'
|
= link_to "In response to #{status.thread.account.acct}", status_url(status.thread), class: 'conversation-link'
|
||||||
|
|
|
@ -5,8 +5,10 @@ abort("The Rails environment is running in production mode!") if Rails.env.produ
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
require 'rspec/rails'
|
require 'rspec/rails'
|
||||||
|
require 'webmock/rspec'
|
||||||
|
|
||||||
ActiveRecord::Migration.maintain_test_schema!
|
ActiveRecord::Migration.maintain_test_schema!
|
||||||
|
WebMock.disable_net_connect!
|
||||||
|
|
||||||
RSpec.configure do |config|
|
RSpec.configure do |config|
|
||||||
config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
||||||
|
|
|
@ -1,5 +1,30 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe ReblogService do
|
RSpec.describe ReblogService do
|
||||||
pending
|
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 }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_const('HUB_URL', 'http://hub.example.com')
|
||||||
|
|
||||||
|
stub_request(:post, 'http://hub.example.com')
|
||||||
|
stub_request(:post, 'http://salmon.example.com')
|
||||||
|
|
||||||
|
subject.(alice, status)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a reblog' do
|
||||||
|
expect(status.reblogs.count).to eq 1
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'pings PubSubHubbub hubs' do
|
||||||
|
expect(a_request(:post, 'http://hub.example.com')).to have_been_made
|
||||||
|
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
|
||||||
|
|
Reference in a new issue