Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge remote-tracking branch 'origin/issue_698'

Conflicts:
	test/lib/converts_subscriber_to_feed_data_test.rb
	test/lib/finds_or_creates_feeds_test.rb
	test/lib/finger_test.rb
  • Loading branch information...
commit 066143ef56f335acf05d2eb70518430c9fe6d8ac 2 parents 2120fb6 + c4eba18
@carols10cents carols10cents authored
Showing with 640 additions and 388 deletions.
  1. +24 −28 app/controllers/subscriptions_controller.rb
  2. +7 −9 app/models/feed.rb
  3. +1 −0  app/models/feed_data.rb
  4. +29 −0 app/models/finger_data.rb
  5. +11 −12 app/models/user.rb
  6. +44 −0 app/services/feed_service.rb
  7. +23 −0 app/services/finger_service.rb
  8. +49 −0 app/services/subscriber_to_feed_data_converter.rb
  9. +3 −0  app/views/users/_follow_user.html.haml
  10. +1 −3 app/views/users/_list.haml
  11. +1 −3 app/views/users/show.haml
  12. +0 −39 lib/converts_subscriber_to_feed_data.rb
  13. +0 −15 lib/finds_or_creates_feeds.rb
  14. +0 −18 lib/finger_data.rb
  15. +0 −9 lib/queries_web_finger.rb
  16. +3 −1 test/acceptance/acceptance_helper.rb
  17. +40 −24 test/acceptance/following_remote_users_test.rb
  18. +0 −72 test/lib/converts_subscriber_to_feed_data_test.rb
  19. +0 −59 test/lib/finds_or_creates_feeds_test.rb
  20. +0 −52 test/lib/finger_test.rb
  21. +76 −44 test/models/feed_test.rb
  22. +86 −0 test/models/finger_data_test.rb
  23. +87 −0 test/services/feed_service_test.rb
  24. +76 −0 test/services/finger_service_test.rb
  25. +79 −0 test/services/subscriber_to_feed_data_converter_test.rb
View
52 app/controllers/subscriptions_controller.rb
@@ -60,43 +60,39 @@ def post_update
def create
require_login! :return => request.referrer and return
- # Find or create the Feed
- begin
- subscribe_to_feed = Feed.find_or_create(params[:subscribe_to])
- rescue RstatUs::InvalidSubscribeTo => e
- # This means the user's entry was neither a webfinger identifier
- # nor a feed URL, and calling `open` on it did not return anything.
- flash[:error] = "There was a problem following #{params[:subscribe_to]}. Please specify the whole ID for the person you would like to follow, including both their username and the domain of the site they're on. It should look like an email address-- for example, username@status.net"
- redirect_to request.referrer
- return
- end
+ target = FeedService.new(params[:subscribe_to], root_url).find_or_create!
- # Stop and return a nice message if already following this feed
- if current_user.following_feed? subscribe_to_feed
- flash[:notice] = "You're already following #{subscribe_to_feed.author.username}."
+ if current_user.following_feed? target
+ # Stop and return a nice message if already following this feed
+ flash[:notice] = "You're already following #{target.author.username}."
redirect_to request.referrer
return
- end
+ else
+ # Actually follow!
+ target_feed = current_user.follow! target
- # Actually follow!
- f = current_user.follow! subscribe_to_feed
+ if target_feed
+ # Attempt to inform the hub for remote feeds
+ if target_feed.remote? && target_feed.hubs.any?
+ hub_url = target_feed.hubs.first
- unless f
- flash[:error] = "There was a problem following #{params[:subscribe_to]}."
- redirect_to request.referrer
- return
- end
-
- # Attempt to inform the hub for remote feeds
- unless f.local? || f.hubs.empty?
- hub_url = f.hubs.first
+ sub = OSub::Subscription.new(subscription_url(target_feed.id, :format => "atom"), target_feed.url, target_feed.secret)
+ sub.subscribe(hub_url, true, target_feed.verify_token)
+ end
- sub = OSub::Subscription.new(subscription_url(f.id, :format => "atom"), f.url, f.secret)
- sub.subscribe(hub_url, true, f.verify_token)
+ flash[:notice] = "Now following #{target_feed.author.username}."
+ redirect_to request.referrer
+ else
+ raise RstatUs::InvalidSubscribeTo
+ end
end
- flash[:notice] = "Now following #{f.author.username}."
+ rescue RstatUs::InvalidSubscribeTo => e
+ # This means the user's entry was neither a webfinger identifier
+ # nor a feed URL, and calling `open` on it did not return anything.
+ flash[:error] = "There was a problem following #{params[:subscribe_to]}. Please specify the whole ID for the person you would like to follow, including both their username and the domain of the site they're on. It should look like an email address-- for example, username@status.net"
redirect_to request.referrer
+ return
end
private
View
16 app/models/feed.rb
@@ -1,5 +1,3 @@
-require_relative '../../lib/finds_or_creates_feeds'
-
# Feeds are pretty central to everything. They're a representation of a PuSH
# enabled Atom feed. Every user has a feed of their updates, we keep feeds
# for remote users that our users are subscribed to, and maybe even other
@@ -31,17 +29,13 @@ class Feed
after_create :default_hubs
- def self.find_or_create(subscribe_to)
- FindsOrCreatesFeeds.find_or_create(subscribe_to)
- end
-
- def self.create_from_feed_data(feed_data)
- feed = Feed.create(:remote_url => feed_data.url)
+ def self.create_and_populate!(feed_data)
+ feed = create(:remote_url => feed_data.url)
feed.populate(feed_data.finger_data)
feed
end
- # This is because sometimes the mongomapper association returns nil
+ # This is because sometimes the mongomapper association returns nil
# even though there is an author_id and the Author exists; see Issue #421
def author
Author.find(author_id)
@@ -127,6 +121,10 @@ def local?
remote_url.nil?
end
+ def remote?
+ !local?
+ end
+
def url(params = {})
atom_format = params.fetch(:format, false) == :atom
View
1  app/models/feed_data.rb
@@ -0,0 +1 @@
+class FeedData < Struct.new(:url, :finger_data); end
View
29 app/models/finger_data.rb
@@ -0,0 +1,29 @@
+class FingerData
+ def initialize(xrd)
+ @xrd = xrd
+ end
+
+ def url
+ find('http://schemas.google.com/g/2010#updates-from')
+ end
+
+ def public_key
+ public_key = find('magic-public-key')
+ public_key.split(",")[1] || ""
+ end
+
+ def salmon_url
+ find('salmon')
+ end
+
+ private
+
+ def find(rel)
+ element_hash = links.find { |link| link['rel'].downcase == rel } || {}
+ element_hash.fetch("href") { "" }
+ end
+
+ def links
+ @xrd.links || []
+ end
+end
View
23 app/models/user.rb
@@ -150,24 +150,22 @@ def unfollowed_by!(f)
end
# Follow a particular feed
- def follow!(f)
- # can't follow yourself
- if f == self.feed
- return
- end
+ def follow!(target_feed)
+ return false if target_feed == self.feed # can't follow yourself
- following << f
- save
+ self.following << target_feed
+ self.save
- if f.local?
+ if target_feed.local?
# Add the inverse relationship
- followee = User.first(:author_id => f.author.id)
+ followee = User.first(:author_id => target_feed.author.id)
followee.followed_by! self.feed
else
# Queue a notification job
- self.delay.send_follow_notification(f.id)
+ self.delay.send_follow_notification(target_feed.id)
end
- f
+
+ target_feed
end
# Send Salmon notification so that the remote user
@@ -368,7 +366,8 @@ def edit_user_profile(params)
# A better name would be very welcome.
def self.find_by_case_insensitive_username(username)
- User.first(:username => /^#{Regexp.escape(username)}$/i)
+ username = Regexp.escape(username)
+ User.first(:username => /^#{username}$/i)
end
def token_expired?
View
44 app/services/feed_service.rb
@@ -0,0 +1,44 @@
+require 'uri'
+
+class FeedService
+ def initialize(target_feed, current_node_domain = nil)
+ @target_feed = target_feed
+ @current_node_domain = current_node_domain
+ end
+
+ def find_or_create!
+ find_feed_by_id ||
+ find_feed_by_username ||
+ find_feed_by_remote_url ||
+ create_feed_from_feed_data
+ end
+
+ private
+
+ def find_feed_by_id
+ Feed.first(:id => @target_feed)
+ end
+
+ def find_feed_by_username
+ username, domain = @target_feed.split /@/
+
+ if @current_node_domain && domain == URI(@current_node_domain).host
+ u = User.find_by_case_insensitive_username(username)
+ u && u.author.feed
+ end
+ end
+
+ def find_feed_by_remote_url
+ feed_data = get_feed_data_for_target
+ Feed.first(:remote_url => feed_data.url)
+ end
+
+ def create_feed_from_feed_data
+ feed_data = get_feed_data_for_target
+ Feed.create_and_populate!(feed_data)
+ end
+
+ def get_feed_data_for_target
+ SubscriberToFeedDataConverter.new(@target_feed).get_feed_data!
+ end
+end
View
23 app/services/finger_service.rb
@@ -0,0 +1,23 @@
+class FingerService
+
+ attr_reader :feed_data
+
+ def initialize(target)
+ @target = target
+ @feed_data = FeedData.new
+ end
+
+ def finger!
+ # TODO: ensure caching of finger lookup.
+ data = FingerData.new(Redfinger.finger(@target))
+ @feed_data.url = data.url
+ @feed_data.finger_data = data
+ @feed_data
+ # TODO: other exceptions to rescue; check what Redfinger raises
+ rescue RestClient::ResourceNotFound
+ raise RstatUs::InvalidSubscribeTo
+ rescue SocketError
+ raise RstatUs::InvalidSubscribeTo
+ end
+
+end
View
49 app/services/subscriber_to_feed_data_converter.rb
@@ -0,0 +1,49 @@
+class SubscriberToFeedDataConverter
+
+ def initialize(subscriber_url)
+ @subscriber_url = subscriber_url
+ @feed_data = FeedData.new
+ end
+
+ def get_feed_data!
+ case @subscriber_url
+ when /^feed:\/\//
+ convert_safari_scheme
+ when /@/
+ query_web_finger
+ when /^https?:\/\//
+ http_https_subscriber
+ else
+ raise RstatUs::InvalidSubscribeTo
+ end
+
+ @feed_data
+
+ rescue StandardError
+ # TODO Bubble up a better description of what went wrong.
+ #
+ # We could see any one of the following here:
+ #
+ # Redfinger::ResourceNotFound
+ # Nokogiri::SyntaxError (Bad XML parsed by Redfinger)
+ # SocketError (DNS resolution or connect failure)
+ # ??? more ???
+ raise RstatUs::InvalidSubscribeTo
+ end
+
+ private
+
+ # replace Safari feed:// scheme with http://
+ def convert_safari_scheme
+ @feed_data.url = "http" + @subscriber_url[4..-1]
+ end
+
+ def query_web_finger
+ @feed_data = FingerService.new(@subscriber_url).finger!
+ end
+
+ def http_https_subscriber
+ @feed_data.url = @subscriber_url
+ end
+
+end
View
3  app/views/users/_follow_user.html.haml
@@ -0,0 +1,3 @@
+= form_tag "/subscriptions" do
+ %input{:type => "hidden", :name => "subscribe_to", :value => feed_id}
+ %input.button.follow{:type => "submit", :value => "Follow", :id => "follow-#{feed_id}"}
View
4 app/views/users/_list.haml
@@ -24,6 +24,4 @@
%input{:type => "hidden", :name => "_method", :value => "delete"}
= submit_tag "Unfollow", :class => "button unfollow", :id => "unfollow-#{author.feed.id}", :confirm => t(:unfollow, :scope => :confirms)
- else
- = form_tag "/subscriptions" do
- %input{:type => "hidden", :name => "subscribe_to", :value => author.feed.id}
- %input.button.follow{:type => "submit", :value => "Follow", :id => "follow-#{author.feed.id}"}
+ = render :partial => "follow_user", :locals => { :feed_id => author.feed.id }
View
4 app/views/users/show.haml
@@ -24,9 +24,7 @@
%input{:type => "hidden", :name => "_method", :value => "delete"}
%input.button.unfollow{:type => "submit", :value => "Unfollow", :id => "unfollow-#{@author.feed.id}"}
- elsif current_user
- = form_tag "/subscriptions" do
- %input{:type => "hidden", :name => "subscribe_to", :value => @author.feed.id}
- %input.button.follow{:type => "submit", :value => "Follow", :id => "follow-#{@author.feed.id}"}
+ = render :partial => "follow_user", :locals => { :feed_id => @author.feed.id }
- if @author.user == current_user
.edit
View
39 lib/converts_subscriber_to_feed_data.rb
@@ -1,39 +0,0 @@
-require_relative "queries_web_finger"
-
-FeedData = Struct.new(:url, :finger_data)
-
-class ConvertsSubscriberToFeedData
- def self.get_feed_data(subscriber_url)
- feed_data = FeedData.new
-
- case subscriber_url
- when /^feed:\/\//
- # SAFARI!!!!1 /me shakes his first at the sky
- feed_data.url = "http" + subscriber_url[4..-1]
- when /@/
- begin
- finger_data = QueriesWebFinger.query(subscriber_url)
- rescue StandardError
- #
- # TODO Bubble up a better description of what went wrong.
- #
- # We could see any one of the following here:
- #
- # Redfinger::ResourceNotFound
- # Nokogiri::SyntaxError (Bad XML parsed by Redfinger)
- # SocketError (DNS resolution or connect failure)
- # ??? more ???
- #
- raise RstatUs::InvalidSubscribeTo
- end
- feed_data.url = finger_data.url
- feed_data.finger_data = finger_data
- when /^https?:\/\//
- feed_data.url = subscriber_url
- else
- raise RstatUs::InvalidSubscribeTo
- end
-
- feed_data
- end
-end
View
15 lib/finds_or_creates_feeds.rb
@@ -1,15 +0,0 @@
-require_relative 'converts_subscriber_to_feed_data'
-
-class FindsOrCreatesFeeds
- def self.find_or_create(subscribe_to)
- feed = Feed.first(:id => subscribe_to)
-
- unless feed
- feed_data = ConvertsSubscriberToFeedData.get_feed_data(subscribe_to)
- feed = Feed.first(:remote_url => feed_data.url) || Feed.create_from_feed_data(feed_data)
- end
-
- feed
- end
-end
-
View
18 lib/finger_data.rb
@@ -1,18 +0,0 @@
-class FingerData
- def initialize(xrd)
- @xrd = xrd
- end
-
- def url
- @xrd.links.find { |l| l['rel'] == 'http://schemas.google.com/g/2010#updates-from' }.to_s
- end
-
- def public_key
- public_key_href = @xrd.links.find { |l| l['rel'].downcase == 'magic-public-key' }.href
- public_key_href[/^.*?,(.*)$/,1]
- end
-
- def salmon_url
- @xrd.links.find { |l| l['rel'].downcase == 'salmon' }.href
- end
-end
View
9 lib/queries_web_finger.rb
@@ -1,9 +0,0 @@
-require_relative "finger_data"
-
-class QueriesWebFinger
- def self.query(email)
- # XXX: ensure caching of finger lookup.
- xrd = Redfinger.finger(email)
- FingerData.new(xrd)
- end
-end
View
4 test/acceptance/acceptance_helper.rb
@@ -14,7 +14,9 @@ module AcceptanceHelper
ActionController::Base.allow_forgery_protection = true
if ENV["ENABLE_HTTPS"] == "yes"
- Capybara.app_host = 'https://www.example.com'
+ Capybara.app_host = 'https://example.com'
+ else
+ Capybara.app_host = 'http://example.com'
end
def app
View
64 test/acceptance/following_remote_users_test.rb
@@ -1,63 +1,80 @@
require 'require_relative' if RUBY_VERSION[0,3] == '1.8'
require_relative 'acceptance_helper'
+require 'uri'
describe "following remote users" do
include AcceptanceHelper
+ def follow_remote_user!(webfinger_id = "steveklabnik@identi.ca")
+ visit "/"
+ click_link "Follow Remote User"
+
+ VCR.use_cassette('subscribe_remote') do
+ fill_in 'subscribe_to', :with => webfinger_id
+ click_button "Follow"
+ end
+ end
+
describe "success" do
before do
log_in_as_some_user
- visit "/"
- click_link "Follow Remote User"
-
- VCR.use_cassette('subscribe_remote') do
- fill_in 'subscribe_to', :with => "steveklabnik@identi.ca"
- click_button "Follow"
- end
end
it "follows users on other sites" do
- assert_match "Now following steveklabnik.", page.body
+ follow_remote_user!
assert "/", current_path
+ within flash do
+ assert has_content? "Now following steveklabnik."
+ end
end
it "has users on other sites on /following" do
+ follow_remote_user!
visit "/users/#{@u.username}/following"
-
- assert_match "steveklabnik", page.body
+ within "#content" do
+ assert has_content? "steveklabnik"
+ end
end
it "unfollows users from other sites" do
+ follow_remote_user!
visit "/users/#{@u.username}/following"
VCR.use_cassette('unsubscribe_remote') do
click_button "Unfollow"
end
- assert_match "No longer following steveklabnik", page.body
+ within flash do
+ assert has_content? "No longer following steveklabnik"
+ end
end
- it "only creates one Feed per remote_url" do
- log_in_as_some_user
- visit "/"
- click_link "Follow Remote User"
+ it "doesn't follow those you already follow, and reports an error" do
+ follow_remote_user!
+ follow_remote_user!
- assert_match "OStatus Sites", page.body
-
- VCR.use_cassette('subscribe_remote') do
- fill_in 'subscribe_to', :with => "steveklabnik@identi.ca"
- click_button "Follow"
+ within flash do
+ assert has_content? "You're already following steveklabnik."
end
+ end
- visit "/users/#{@u.username}/following"
+ it "follows users on the current node even if you try to follow them like remote users" do
+ local_user = Fabricate(:user)
- assert_match "Unfollow", page.body
+ follow_remote_user!("#{local_user.username}@example.com")
+
+ within flash do
+ assert has_content? "Now following #{local_user.username}."
+ end
end
end
describe "failure" do
- it "doesn't look up something that doesn't look like either a webfinger id or a URL" do
+ before do
log_in_as_some_user
+ end
+
+ it "doesn't look up something that doesn't look like either a webfinger id or a URL" do
visit "/"
click_link "Follow Remote User"
@@ -75,7 +92,6 @@
end
it "especially doesn't look up something that looks like a local file" do
- log_in_as_some_user
visit "/"
click_link "Follow Remote User"
View
72 test/lib/converts_subscriber_to_feed_data_test.rb
@@ -1,72 +0,0 @@
-require 'minitest/autorun'
-require 'mocha/setup'
-require 'socket'
-
-require_relative '../../lib/finds_or_creates_feeds'
-
-FakeFingerData = Struct.new(:url)
-
-module RstatUs
- class InvalidSubscribeTo < StandardError; end
-end
-
-describe "converting subscriber to feed data" do
- describe "when the subscriber info has feed in it" do
- it "should replace the feed with http" do
- feed_data = ConvertsSubscriberToFeedData.get_feed_data("feed://stuff")
-
- assert_equal "http://stuff", feed_data.url
- end
- end
-
- describe "when the subscriber info is an email address" do
- it "should finger the user" do
- email = "somebody@somewhere.com"
- finger_data = FakeFingerData.new("url")
- QueriesWebFinger.expects(:query).with(email).returns(finger_data)
-
- new_feed_data = ConvertsSubscriberToFeedData.get_feed_data(email)
-
- assert_equal "url", new_feed_data.url
- assert_equal finger_data, new_feed_data.finger_data
- end
- end
-
- describe "when the subscriber info is an http url" do
- it "should use the subscriber url as the feed url" do
- feed_url = "http://feed.me"
-
- feed_data = ConvertsSubscriberToFeedData.get_feed_data(feed_url)
-
- assert_equal feed_url, feed_data.url
- end
-
- it "should use an https subscriber url as the feed url" do
- feed_url = "https://feed.me"
-
- feed_data = ConvertsSubscriberToFeedData.get_feed_data(feed_url)
-
- assert_equal feed_url, feed_data.url
- end
- end
-
- describe "when the subscriber info is neither an email address nor an http url" do
- it "should raise an exception so that we dont try and look it up as a file" do
- feed_url = "Gemfile.lock"
-
- lambda {
- ConvertsSubscriberToFeedData.get_feed_data(feed_url)
- }.must_raise(RstatUs::InvalidSubscribeTo)
- end
- end
-
- describe "when a network error occurs retrieving the subscriber info" do
- it "should not raise a socket error" do
- email = "ladygaga@twitter"
- QueriesWebFinger.expects(:query).with(email).throws(SocketError)
- lambda {
- ConvertsSubscriberToFeedData.get_feed_data(email)
- }.must_raise(RstatUs::InvalidSubscribeTo)
- end
- end
-end
View
59 test/lib/finds_or_creates_feeds_test.rb
@@ -1,59 +0,0 @@
-require 'minitest/autorun'
-require 'mocha/setup'
-
-require_relative '../../lib/finds_or_creates_feeds'
-
-class Feed
-end
-
-describe "finding or creating a new feed" do
- before do
- @feed = Feed.new
- @subscriber_id = "id"
- end
-
- describe "when feed exists with the subscriber id" do
- it "should return the feed" do
- Feed.expects(:first).with(:id => @subscriber_id).returns(@feed)
-
- feed = FindsOrCreatesFeeds.find_or_create(@subscriber_id)
-
- assert_equal @feed, feed
- end
- end
-
- describe "when feed does not have the subscriber id" do
-
- before do
- Feed.expects(:first).with(:id => @subscriber_id).returns(nil)
-
- @feed_url = "http://some.url"
- @feed_data = FeedData.new(@feed_url, nil)
-
- ConvertsSubscriberToFeedData.expects(:get_feed_data)
- .with(@subscriber_id)
- .returns(@feed_data)
- end
-
- describe "when a feed exists with the remote url" do
- it "should return the feed with the remote url " do
- Feed.expects(:first).with(:remote_url => @feed_url).returns(@feed)
-
- feed = FindsOrCreatesFeeds.find_or_create(@subscriber_id)
-
- assert_equal @feed, feed
- end
- end
-
- describe "when a feed does not exist with the remote url" do
- it "should create a new feed from the data" do
- Feed.expects(:first).with(:remote_url => @feed_url).returns(nil)
- Feed.expects(:create_from_feed_data).with(@feed_data).returns(@feed)
-
- feed = FindsOrCreatesFeeds.find_or_create(@subscriber_id)
-
- assert_equal @feed, feed
- end
- end
- end
-end
View
52 test/lib/finger_test.rb
@@ -1,52 +0,0 @@
-require 'minitest/autorun'
-require 'mocha/setup'
-
-require_relative '../../lib/finger_data'
-
-describe "when querying web finger" do
-
- Xrd = Struct.new(:links)
-
- class Link < Hash
- def initialize(rel, href)
- self['rel'] = rel
- self['href'] = href
- end
-
- def to_s
- self['href']
- end
-
- def href
- self['href']
- end
- end
-
- module Redfinger
- end
-
- before do
- url_link = Link.new('http://schemas.google.com/g/2010#updates-from', 'http://feed.url')
- public_key_link = Link.new('magic-public-key', 'ignored,key')
- salmon_link = Link.new('salmon', 'http://salmon.url')
- xrd = Xrd.new([ url_link, public_key_link, salmon_link ])
-
- @email = "someone@somewhere.com"
-
- Redfinger.expects(:finger).with(@email).returns(xrd)
-
- @finger_data = QueriesWebFinger.query(@email)
- end
-
- it "should get the remote url" do
- assert_equal "http://feed.url", @finger_data.url
- end
-
- it "should get public key " do
- assert_equal "key", @finger_data.public_key
- end
-
- it "should get salmon url" do
- assert_equal "http://salmon.url", @finger_data.salmon_url
- end
-end
View
120 test/models/feed_test.rb
@@ -3,58 +3,94 @@
describe Feed do
include TestHelper
+ def setup
+ @feed = Fabricate(:feed)
+ end
+
+ describe ".create_and_populate!" do
+ let(:feed_data) { mock }
+ let(:feed_data_url) { mock }
+ let(:feed_data_finger_data) { mock }
+ let(:feed) { mock }
+
+ before do
+ feed_data.stubs(:url).returns(feed_data_url)
+ Feed.stubs(:create).returns(feed)
+ feed_data.stubs(:finger_data).returns(feed_data_finger_data)
+ feed.stubs(:populate)
+ end
+
+ subject { Feed.create_and_populate!(feed_data) }
+
+ it "gets the url from the feed data" do
+ feed_data.expects(:url).returns(feed_data_url)
+ subject
+ end
+
+ it "creates a feed from the feed data" do
+ Feed.expects(:create).with(:remote_url => feed_data_url).returns(feed)
+ subject
+ end
+
+ it "gets the finger_data from the feed data" do
+ feed_data.expects(:finger_data).returns(feed_data_finger_data)
+ subject
+ end
+
+ it "populates the feed with the finger data" do
+ feed.expects(:populate).with(feed_data_finger_data)
+ subject
+ end
+ end
+
describe "#populate_entries" do
describe "new update" do
it "creates a new update" do
- f = Fabricate(:feed)
- updates_before = f.updates.length
+ updates_before = @feed.updates.length
- f.populate_entries([
+ @feed.populate_entries([
stub_everything(
:url => "http://foo.com",
:content => "I will take care of Yoshi"
)
])
- f.updates.size.must_equal(updates_before + 1)
+ @feed.updates.size.must_equal(updates_before + 1)
end
it "does nothing if the entry's URL is nil (just going to toss these until ostatus bug #4 is fixed)" do
- f = Fabricate(:feed)
- Fabricate(:update, :feed => f, :text => "Nacho update")
- updates_before = f.updates.length
+ Fabricate(:update, :feed => @feed, :text => "Nacho update")
+ updates_before = @feed.updates.length
- f.populate_entries([
+ @feed.populate_entries([
stub_everything(:url => nil, :content => "I do not smirk.")
])
- f.updates.size.must_equal(updates_before)
- f.updates.last.text.must_equal("Nacho update")
+ @feed.updates.size.must_equal(updates_before)
+ @feed.updates.last.text.must_equal("Nacho update")
end
end
describe "existing update" do
it "does not create a new update if this update already exists" do
- f = Fabricate(:feed)
- u = Fabricate(:update, :feed => f, :remote_url => "http://a.b/1")
+ u = Fabricate(:update, :feed => @feed, :remote_url => "http://a.b/1")
update_text_before = u.text
- updates_before = f.updates.length
+ updates_before = @feed.updates.length
- f.populate_entries([
+ @feed.populate_entries([
stub_everything(:url => u.remote_url, :content => "I do not smirk.")
])
- f.updates.size.must_equal(updates_before)
- f.updates.last.text.must_equal(update_text_before)
+ @feed.updates.size.must_equal(updates_before)
+ @feed.updates.last.text.must_equal(update_text_before)
end
it "does not change the existing update if the verb isn't update" do
- f = Fabricate(:feed)
- u = Fabricate(:update, :feed => f, :remote_url => "http://a.b/1")
+ u = Fabricate(:update, :feed => @feed, :remote_url => "http://a.b/1")
update_text_before = u.text
- updates_before = f.updates.length
+ updates_before = @feed.updates.length
- f.populate_entries([
+ @feed.populate_entries([
stub_everything(
:url => u.remote_url,
:content => "I do not smirk.",
@@ -62,17 +98,16 @@
)
])
- f.updates.size.must_equal(updates_before)
- f.updates.last.text.must_equal(update_text_before)
+ @feed.updates.size.must_equal(updates_before)
+ @feed.updates.last.text.must_equal(update_text_before)
end
it "changes the existing update if the verb is update" do
- f = Fabricate(:feed)
- u = Fabricate(:update, :feed => f, :remote_url => "http://a.b/1")
+ u = Fabricate(:update, :feed => @feed, :remote_url => "http://a.b/1")
update_text_before = u.text
- updates_before = f.updates.length
+ updates_before = @feed.updates.length
- f.populate_entries([
+ @feed.populate_entries([
stub_everything(
:url => u.remote_url,
:content => "I do not smirk.",
@@ -80,21 +115,20 @@
)
])
- f.updates.size.must_equal(updates_before)
- f.updates.last.text.must_equal("I do not smirk.")
+ @feed.updates.size.must_equal(updates_before)
+ @feed.updates.last.text.must_equal("I do not smirk.")
end
end
end
describe "#atom" do
before do
- @f = Fabricate(:feed)
- @later = Fabricate(:update, :feed => @f, :created_at => 1.day.ago)
- @earlier = Fabricate(:update, :feed => @f, :created_at => 2.days.ago)
+ @later = Fabricate(:update, :feed => @feed, :created_at => 1.day.ago)
+ @earlier = Fabricate(:update, :feed => @feed, :created_at => 2.days.ago)
end
it "sorts updates in reverse chronological order by created_at" do
- atom = @f.atom("http://example.com")
+ atom = @feed.atom("http://example.com")
xml = Nokogiri.XML(atom)
entries = xml.xpath("//xmlns:entry")
@@ -102,8 +136,8 @@
entries.last.at_xpath("xmlns:id").content.must_match(/#{@earlier.id}/)
end
- it "can limit the number of entries returned" do
- atom = @f.atom("http://example.com", :num => 1)
+ it "limits the number of entries returned" do
+ atom = @feed.atom("http://example.com", :num => 1)
xml = Nokogiri.XML(atom)
entries = xml.xpath("//xmlns:entry")
@@ -111,8 +145,8 @@
entries.first.at_xpath("xmlns:id").content.must_match(/#{@later.id}/)
end
- it "can limit the entries returned by date" do
- atom = @f.atom("http://example.com", :since => 36.hours.ago)
+ it "limits the entries returned by date" do
+ atom = @feed.atom("http://example.com", :since => 36.hours.ago)
xml = Nokogiri.XML(atom)
entries = xml.xpath("//xmlns:entry")
@@ -123,23 +157,21 @@
describe "#last_update" do
it "returns the most recently created update" do
- f = Fabricate(:feed)
- later = Fabricate(:update, :feed => f, :created_at => 1.day.ago)
- earlier = Fabricate(:update, :feed => f, :created_at => 2.days.ago)
- f.last_update.must_equal(later)
+ later = Fabricate(:update, :feed => @feed, :created_at => 1.day.ago)
+ earlier = Fabricate(:update, :feed => @feed, :created_at => 2.days.ago)
+
+ @feed.last_update.must_equal(later)
end
end
describe "#url" do
it "does not end in .atom by default" do
- f = Fabricate(:feed)
- f.url.wont_match(/\.atom$/)
+ @feed.url.wont_match(/\.atom$/)
end
it "does end in .atom if we ask it to" do
- f = Fabricate(:feed)
- f.url(:format => :atom).must_match(/\.atom$/)
+ @feed.url(:format => :atom).must_match(/\.atom$/)
end
end
end
View
86 test/models/finger_data_test.rb
@@ -0,0 +1,86 @@
+require 'minitest/autorun'
+require 'mocha'
+
+require_relative '../../app/models/finger_data'
+
+describe FingerData do
+ describe ".initialize" do
+ let(:xrd) { mock }
+
+ it "assigns the extensible resource descriptor (XRD)" do
+ FingerData.new(xrd).instance_variable_get("@xrd").must_equal xrd
+ end
+ end
+
+ describe "#url" do
+ let(:xrd) { mock }
+ describe "when data is present" do
+ before do
+ xrd.stubs(:links).returns([{"rel" => "http://schemas.google.com/g/2010#updates-from",
+ "href" => "https://rstat.us/feeds/505...22c.atom"}])
+ end
+
+ it "gets the updates-from google finger data" do
+ FingerData.new(xrd).url.must_equal "https://rstat.us/feeds/505...22c.atom"
+ end
+ end
+
+ describe "when data cannot be found" do
+ before do
+ xrd.stubs(:links).returns([])
+ end
+
+ it "returns a blank string" do
+ FingerData.new(xrd).url.must_equal ""
+ end
+ end
+ end
+
+ describe "#public_key" do
+ let(:xrd) { mock }
+ describe "when data is present" do
+ before do
+ xrd.stubs(:links).returns([{"rel" => "magic-public-key",
+ "href" => "data:application/magic-public-key,RSA.qHXlBk2so..."}])
+ end
+
+ it "gets the magic-public-key data" do
+ FingerData.new(xrd).public_key.must_equal "RSA.qHXlBk2so..."
+ end
+ end
+
+ describe "when data cannot be found" do
+ before do
+ xrd.stubs(:links).returns([])
+ end
+
+ it "returns a blank string" do
+ FingerData.new(xrd).public_key.must_equal ""
+ end
+ end
+ end
+
+ describe "#salmon_url" do
+ let(:xrd) { mock }
+ describe "when data is present" do
+ before do
+ xrd.stubs(:links).returns([{"rel" => "salmon",
+ "href" => "https://rstat.us/feeds/505...22c/salmon"}])
+ end
+
+ it "gets the salmon data" do
+ FingerData.new(xrd).salmon_url.must_equal "https://rstat.us/feeds/505...22c/salmon"
+ end
+ end
+
+ describe "when data cannot be found" do
+ before do
+ xrd.stubs(:links).returns([])
+ end
+
+ it "returns a blank string" do
+ FingerData.new(xrd).salmon_url.must_equal ""
+ end
+ end
+ end
+end
View
87 test/services/feed_service_test.rb
@@ -0,0 +1,87 @@
+require_relative '../test_helper'
+
+require 'mocha'
+
+describe FeedService do
+ include TestHelper
+
+ describe "#find_or_create!" do
+ let(:service) { FeedService.new(target_feed) }
+ let(:existing_feed) { mock }
+ let(:new_feed) { mock }
+
+ describe "when the feed can be found by ID" do
+ # the BSON ID of user 'gavinlaking@rstat.us' (follow me!)
+ let(:target_feed) { "505cc1beb4f2cd000200022c" }
+
+ before do
+ service.stubs(:find_feed_by_id).returns(existing_feed)
+ end
+
+ it "returns the feed" do
+ service.find_or_create!.must_equal existing_feed
+ end
+ end
+
+ describe "when the feed has a local URL" do
+ let(:target_feed) { "someone@example.com" }
+ let(:service) { FeedService.new(target_feed, "http://example.com/") }
+ let(:user) { mock }
+ let(:author) { mock }
+ let(:feed) { mock }
+
+ describe "when the feed exists" do
+ before do
+ User.stubs(:find_by_case_insensitive_username).returns(user)
+ user.stubs(:author).returns(author)
+ author.stubs(:feed).returns(feed)
+ end
+
+ it "returns the feed" do
+ service.find_or_create!.must_equal feed
+ end
+ end
+
+ describe "when the feed does not exist" do
+ before do
+ User.stubs(:find_by_case_insensitive_username).returns(nil)
+ service.stubs(:find_feed_by_remote_url).returns(existing_feed)
+ end
+
+ it "moves on to trying to find by remote url" do
+ service.find_or_create!.must_equal existing_feed
+ end
+ end
+ end
+
+ describe "when the feed can by found by remote URL" do
+ # the remote URL of user 'gavinlaking@rstat.us' (follow me!)
+ let(:target_feed) { "https://rstat.us/feeds/505cc1beb4f2cd000200022c.atom" }
+
+ before do
+ service.stubs(:find_feed_by_id).returns nil
+ service.stubs(:find_feed_by_username).returns nil
+ service.stubs(:find_feed_by_remote_url).returns(existing_feed)
+ end
+
+ it "returns the feed" do
+ service.find_or_create!.must_equal existing_feed
+ end
+ end
+
+ describe "when the feed doesn't exist" do
+ let(:target_feed) { "gavinlaking@rstat.us" } # (follow me!)
+
+ before do
+ service.stubs(:find_feed_by_id).returns nil
+ service.stubs(:find_feed_by_username).returns nil
+ service.stubs(:find_feed_by_remote_url).returns nil
+ service.stubs(:create_feed_from_feed_data).returns(new_feed)
+ end
+
+ it "creates the feed" do
+ service.find_or_create!.must_equal new_feed
+ end
+ end
+ end
+end
View
76 test/services/finger_service_test.rb
@@ -0,0 +1,76 @@
+require 'minitest/autorun'
+require 'mocha'
+
+require_relative '../../app/services/finger_service'
+require_relative '../../app/models/feed_data'
+require_relative '../../app/models/finger_data'
+require 'redfinger'
+
+module RstatUs
+ class InvalidSubscribeTo < StandardError; end
+end
+
+describe FingerService do
+
+ describe "finger!" do
+ let(:target) { "gavinlaking@rstat.us" }
+ let(:feed_data) { mock }
+ let(:xrd) { mock }
+ let(:finger_data) { mock }
+ let(:finger_data_url) { mock }
+
+ before do
+ FeedData.stubs(:new).returns(feed_data)
+ Redfinger.stubs(:finger).with(target).returns(xrd)
+ FingerData.stubs(:new).with(xrd).returns(finger_data)
+
+ finger_data.stubs(:url).returns(finger_data_url)
+ feed_data.stubs(:url=)
+ feed_data.stubs(:finger_data=)
+ end
+
+ subject { FingerService.new(target).finger! }
+
+ it "returns a FeedData object" do
+ subject.must_equal feed_data
+ end
+
+ it "gets the url from FingerData" do
+ finger_data.expects(:url).returns(finger_data_url)
+ subject
+ end
+
+ it "sets the FeedData url" do
+ feed_data.expects(:url=).with(finger_data_url)
+ subject
+ end
+
+ it "sets the FeedData finger data" do
+ feed_data.expects(:finger_data=).with(finger_data)
+ subject
+ end
+
+ describe "with a local rstat.us email address that doesn't have a corresponding user" do
+ let(:target) { "nobody@rstat.us" }
+ before do
+ Redfinger.stubs(:finger).with(target).raises(RestClient::ResourceNotFound)
+ end
+
+ it "raises a RestClient::ResourceNotFound exception" do
+ lambda { subject }.must_raise RstatUs::InvalidSubscribeTo
+ end
+ end
+
+ describe "with an invalid ostatus email address" do
+ let(:target) { "ladygaga@twitter" }
+
+ before do
+ Redfinger.stubs(:finger).with(target).raises(SocketError)
+ end
+
+ it "raises a SocketError exception" do
+ lambda { subject }.must_raise RstatUs::InvalidSubscribeTo
+ end
+ end
+ end
+end
View
79 test/services/subscriber_to_feed_data_converter_test.rb
@@ -0,0 +1,79 @@
+require 'minitest/autorun'
+require 'mocha'
+require 'socket'
+require_relative '../../app/services/subscriber_to_feed_data_converter'
+
+FakeFingerData = Struct.new(:url)
+
+module RstatUs
+ class InvalidSubscribeTo < StandardError; end
+end
+
+describe "converting a subscriber to feed data" do
+ describe "when a Safari 'feed://' scheme is provided" do
+ it "should replace feed:// with http://" do
+ feed_data = SubscriberToFeedDataConverter.new("feed://stuff").get_feed_data!
+
+ assert_equal "http://stuff", feed_data.url
+ end
+ end
+
+ describe "when an email address is provided" do
+ it "should finger the user" do
+ email = "somebody@somewhere.com"
+
+ mock_finger_service = mock
+ FingerService.expects(:new).with(email).returns(mock_finger_service)
+
+ finger_data = FakeFingerData.new("url")
+ mock_finger_service.expects(:finger!).returns(finger_data)
+
+ new_feed_data = SubscriberToFeedDataConverter.new(email).get_feed_data!
+
+ assert_equal "url", new_feed_data.url
+ assert_equal finger_data, new_feed_data
+ end
+ end
+
+ describe "when an http:// URL is provided" do
+ it "should use the subscriber URL as the feed URL" do
+ feed_url = "http://feed.me"
+ feed_data = SubscriberToFeedDataConverter.new(feed_url).get_feed_data!
+
+ assert_equal feed_url, feed_data.url
+ end
+ end
+
+ describe "when an https:// URL is provided" do
+ it "should use the subscriber URL as the feed URL" do
+ feed_url = "https://feed.me"
+ feed_data = SubscriberToFeedDataConverter.new(feed_url).get_feed_data!
+
+ assert_equal feed_url, feed_data.url
+ end
+ end
+
+ describe "when we cannot currently understand the subscriber URL" do
+ it "should raise an exception so that we dont try and look it up as a file" do
+ feed_url = "Gemfile.lock"
+
+ lambda {
+ SubscriberToFeedDataConverter.new(feed_url).get_feed_data!
+ }.must_raise(RstatUs::InvalidSubscribeTo)
+ end
+ end
+
+ describe "when a network error occurs retrieving the subscriber info" do
+ it "consumes the SocketError and re-raises at an RstatUs exception" do
+ email = "ladygaga@twitter"
+
+ mock_finger_service = mock
+ FingerService.expects(:new).with(email).returns(mock_finger_service)
+ mock_finger_service.expects(:finger!).throws(SocketError)
+
+ lambda {
+ SubscriberToFeedDataConverter.new(email).get_feed_data!
+ }.must_raise(RstatUs::InvalidSubscribeTo)
+ end
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.