Merge branch 'repost'

This commit is contained in:
Raphael Sofaer 2011-07-22 18:00:02 -07:00
commit dfa6539ef2
57 changed files with 1052 additions and 151 deletions

View file

@ -44,7 +44,7 @@ class AspectsController < ApplicationController
@aspect_ids = @aspects.map { |a| a.id } @aspect_ids = @aspects.map { |a| a.id }
posts = current_user.visible_posts(:by_members_of => @aspect_ids, posts = current_user.visible_posts(:by_members_of => @aspect_ids,
:type => ['StatusMessage','ActivityStreams::Photo'], :type => ['StatusMessage','Reshare', 'ActivityStreams::Photo'],
:order => session[:sort_order] + ' DESC', :order => session[:sort_order] + ' DESC',
:max_time => params[:max_time].to_i :max_time => params[:max_time].to_i
).includes(:mentions => {:person => :profile}) ).includes(:mentions => {:person => :profile})

View file

@ -96,12 +96,11 @@ class PeopleController < ApplicationController
else else
@commenting_disabled = false @commenting_disabled = false
end end
@posts = current_user.posts_from(@person).where(:type => ["StatusMessage", "ActivityStreams::Photo"]).includes(:comments).limit(15).where(StatusMessage.arel_table[:created_at].lt(max_time)) @posts = current_user.posts_from(@person).where(:type => ["StatusMessage", "Reshare", "ActivityStreams::Photo"]).includes(:comments).limit(15).where(StatusMessage.arel_table[:created_at].lt(max_time))
else else
@commenting_disabled = true @commenting_disabled = true
@posts = @person.posts.where(:type => ["StatusMessage", "ActivityStreams::Photo"], :public => true).includes(:comments).limit(15).where(StatusMessage.arel_table[:created_at].lt(max_time)).order('posts.created_at DESC') @posts = @person.posts.where(:type => ["StatusMessage", "Reshare", "ActivityStreams::Photo"], :public => true).includes(:comments).limit(15).where(StatusMessage.arel_table[:created_at].lt(max_time)).order('posts.created_at DESC')
end end
@posts = PostsFake.new(@posts) @posts = PostsFake.new(@posts)
end end

View file

@ -12,6 +12,9 @@ class PublicsController < ApplicationController
skip_before_filter :set_grammatical_gender skip_before_filter :set_grammatical_gender
before_filter :allow_cross_origin, :only => [:hcard, :host_meta, :webfinger] before_filter :allow_cross_origin, :only => [:hcard, :host_meta, :webfinger]
respond_to :html
respond_to :xml, :only => :post
def allow_cross_origin def allow_cross_origin
headers["Access-Control-Allow-Origin"] = "*" headers["Access-Control-Allow-Origin"] = "*"
end end
@ -66,23 +69,31 @@ class PublicsController < ApplicationController
end end
def post def post
@post = Post.where(:id => params[:id], :public => true).includes(:author, :comments => :author).first
if params[:guid].to_s.length <= 8
@post = Post.where(:id => params[:guid], :public => true).includes(:author, :comments => :author).first
else
@post = Post.where(:guid => params[:guid], :public => true).includes(:author, :comments => :author).first
end
#hax to upgrade logged in users who can comment #hax to upgrade logged in users who can comment
if @post if @post
if user_signed_in? && current_user.find_visible_post_by_id(@post.id) if user_signed_in? && current_user.find_visible_post_by_id(@post.id)
redirect_to post_path(@post) redirect_to post_path(@post)
return
end
@landing_page = true
@person = @post.author
if @person.owner_id
I18n.locale = @person.owner.language
render "#{@post.class.to_s.underscore}", :layout => 'application'
else else
flash[:error] = I18n.t('posts.show.not_found') @landing_page = true
redirect_to root_url @person = @post.author
if @person.owner_id
I18n.locale = @person.owner.language
respond_to do |format|
format.all{ render "#{@post.class.to_s.underscore}", :layout => 'application'}
format.xml{ render :xml => @post.to_diaspora_xml }
end
else
flash[:error] = I18n.t('posts.show.not_found')
redirect_to root_url
end
end end
else else
flash[:error] = I18n.t('posts.show.not_found') flash[:error] = I18n.t('posts.show.not_found')

View file

@ -0,0 +1,14 @@
class ResharesController < ApplicationController
before_filter :authenticate_user!
respond_to :js
def create
@reshare = current_user.build_post(:reshare, :root_guid => params[:root_guid])
if @reshare.save
current_user.add_to_streams(@reshare, current_user.aspects)
current_user.dispatch_post(@reshare, :url => post_url(@reshare), :additional_subscribers => @reshare.root.author)
end
respond_with @reshare
end
end

View file

@ -0,0 +1,13 @@
module ResharesHelper
def reshare_error_message(reshare)
if @reshare.errors[:root_guid].present?
escape_javascript(@reshare.errors[:root_guid].first)
else
escape_javascript(t('reshares.create.failure'))
end
end
def reshare_link post
link_to t("reshares.reshare.reshare", :count => post.reshares.size), reshares_path(:root_guid => post.guid), :method => :post, :remote => true, :confirm => t('reshares.reshare.reshare_confirmation', :author => post.author.name, :text => post.text)
end
end

View file

@ -28,6 +28,7 @@ module SocketsHelper
post_hash = {:post => object, post_hash = {:post => object,
:author => object.author, :author => object.author,
:photos => object.photos, :photos => object.photos,
:reshare => nil,
:comments => object.comments.map{|c| :comments => object.comments.map{|c|
{:comment => c, {:comment => c,
:author => c.author :author => c.author

View file

@ -28,4 +28,8 @@ module StreamHelper
def comments_expanded def comments_expanded
false false
end end
def reshare?(post)
(defined?(post.model) && post.model.is_a?(Reshare)) || post.instance_of?(Reshare)
end
end end

View file

@ -6,7 +6,7 @@ require 'uri'
class AppConfig < Settingslogic class AppConfig < Settingslogic
def self.source_file_name def self.source_file_name
if Rails.env == 'test' || ENV["CI"] if Rails.env == 'test' || ENV["CI"] || Rails.env.include?("integration")
File.join(Rails.root, "config", "application.yml.example") File.join(Rails.root, "config", "application.yml.example")
else else
File.join(Rails.root, "config", "application.yml") File.join(Rails.root, "config", "application.yml")
@ -125,7 +125,7 @@ HELP
def self.pod_uri def self.pod_uri
if @@pod_uri.nil? if @@pod_uri.nil?
begin begin
@@pod_uri = URI.parse(self.pod_url) @@pod_uri = Addressable::URI.parse(self.pod_url)
rescue rescue
puts "WARNING: pod url " + self.pod_url + " is not a legal URI" puts "WARNING: pod url " + self.pod_url + " is not a legal URI"
end end

View file

@ -235,7 +235,6 @@ class Person < ActiveRecord::Base
protected protected
def clean_url def clean_url
self.url ||= "http://localhost:3000/" if self.class == User
if self.url if self.url
self.url = 'http://' + self.url unless self.url.match(/https?:\/\//) self.url = 'http://' + self.url unless self.url.match(/https?:\/\//)
self.url = self.url + '/' if self.url[-1, 1] != '/' self.url = self.url + '/' if self.url[-1, 1] != '/'

View file

@ -24,6 +24,9 @@ class Post < ActiveRecord::Base
has_many :contacts, :through => :post_visibilities has_many :contacts, :through => :post_visibilities
has_many :mentions, :dependent => :destroy has_many :mentions, :dependent => :destroy
has_many :reshares, :class_name => "Reshare", :foreign_key => :root_guid, :primary_key => :guid
has_many :resharers, :class_name => 'Person', :through => :reshares, :source => :author
belongs_to :author, :class_name => 'Person' belongs_to :author, :class_name => 'Person'
def diaspora_handle def diaspora_handle

View file

@ -2,81 +2,28 @@
# licensed under the Affero General Public License version 3 or later. See # licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file. # the COPYRIGHT file.
class RelayableRetraction class RelayableRetraction < SignedRetraction
include ROXML
include Diaspora::Webhooks
include Diaspora::Encryptable
xml_name :relayable_retraction xml_name :relayable_retraction
xml_attr :target_guid
xml_attr :target_type
xml_attr :sender_handle
xml_attr :parent_author_signature xml_attr :parent_author_signature
xml_attr :target_author_signature
attr_accessor :target_guid, attr_accessor :parent_author_signature
:target_type,
:parent_author_signature,
:target_author_signature,
:sender
def signable_accessors def signable_accessors
accessors = self.class.roxml_attrs.collect do |definition| super - ['parent_author_signature']
definition.accessor
end
['target_author_signature', 'parent_author_signature'].each do |acc|
accessors.delete acc
end
accessors
end
def sender_handle= new_sender_handle
@sender = Person.where(:diaspora_handle => new_sender_handle).first
end
def sender_handle
@sender.diaspora_handle
end
def diaspora_handle
self.sender_handle
end
def subscribers(user)
self.target.subscribers(user)
end end
def self.build(sender, target) def self.build(sender, target)
retraction = self.new retraction = super
retraction.sender = sender retraction.parent_author_signature = retraction.sign_with_key(sender.encryption_key) if defined?(target.parent) && sender.person == target.parent.author
retraction.target = target
retraction.target_author_signature = retraction.sign_with_key(sender.encryption_key) if sender.person == target.author
retraction.parent_author_signature = retraction.sign_with_key(sender.encryption_key) if sender.person == target.parent.author
retraction retraction
end end
def target
@target ||= self.target_type.constantize.where(:guid => target_guid).first
end
def guid
target_guid
end
def target= new_target
@target = new_target
@target_type = new_target.class.to_s
@target_guid = new_target.guid
end
def parent def parent
self.target.parent self.target.parent
end end
def perform receiving_user def diaspora_handle
Rails.logger.debug "Performing retraction for #{target_guid}" self.sender_handle
self.target.unsocket_from_user receiving_user if target.respond_to? :unsocket_from_user
self.target.destroy
Rails.logger.info(:event => :retraction, :status => :complete, :target_type => self.target_type, :guid => self.target_guid)
end end
def receive(recipient, sender) def receive(recipient, sender)
@ -101,8 +48,4 @@ class RelayableRetraction
def parent_author_signature_valid? def parent_author_signature_valid?
verify_signature(self.parent_author_signature, self.parent.author) verify_signature(self.parent_author_signature, self.parent.author)
end end
def target_author_signature_valid?
verify_signature(self.target_author_signature, self.target.author)
end
end end

67
app/models/reshare.rb Normal file
View file

@ -0,0 +1,67 @@
class Reshare < Post
belongs_to :root, :class_name => 'Post', :foreign_key => :root_guid, :primary_key => :guid
validate :root_must_be_public
attr_accessible :root_guid, :public
validates_presence_of :root, :on => :create
validates_uniqueness_of :root_guid, :scope => :author_id
xml_attr :root_diaspora_id
xml_attr :root_guid
before_validation do
self.public = true
end
def root_diaspora_id
self.root.author.diaspora_handle
end
def receive(recipient, sender)
local_reshare = Reshare.where(:guid => self.guid).first
if local_reshare && local_reshare.root.author_id == recipient.person.id
local_reshare.root.reshares << local_reshare
if recipient.contact_for(sender)
local_reshare.receive(recipient, sender)
end
else
super(recipient, sender)
end
end
private
def after_parse
root_author = Webfinger.new(@root_diaspora_id).fetch
root_author.save! unless root_author.persisted?
return if Post.exists?(:guid => self.root_guid)
fetched_post = self.class.fetch_post(root_author, self.root_guid)
#Why are we checking for this?
if root_author.diaspora_handle != fetched_post.diaspora_handle
raise "Diaspora ID (#{fetched_post.diaspora_handle}) in the root does not match the Diaspora ID (#{root_author.diaspora_handle}) specified in the reshare!"
end
fetched_post.save!
end
# Fetch a remote public post, used for receiving reshares of unknown posts
# @param [Person] author the remote post's author
# @param [String] guid the remote post's guid
# @return [Post] an unsaved remote post
def self.fetch_post author, guid
response = Faraday.get(author.url + "/p/#{guid}.xml")
Diaspora::Parser.from_xml(response.body)
end
def root_must_be_public
if self.root && !self.root.public
errors[:base] << "you must reshare public posts"
return false
end
end
end

View file

@ -15,6 +15,8 @@ class Retraction
def subscribers(user) def subscribers(user)
unless self.type == 'Person' unless self.type == 'Person'
@subscribers ||= self.object.subscribers(user) @subscribers ||= self.object.subscribers(user)
@subscribers -= self.object.resharers
@subscribers
else else
raise 'HAX: you must set the subscribers manaully before unfriending' if @subscribers.nil? raise 'HAX: you must set the subscribers manaully before unfriending' if @subscribers.nil?
@subscribers @subscribers

View file

@ -0,0 +1,97 @@
# Copyright (c) 2010, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
class SignedRetraction
include ROXML
include Diaspora::Webhooks
include Diaspora::Encryptable
xml_name :signed_retraction
xml_attr :target_guid
xml_attr :target_type
xml_attr :sender_handle
xml_attr :target_author_signature
attr_accessor :target_guid,
:target_type,
:target_author_signature,
:sender
def signable_accessors
accessors = self.class.roxml_attrs.collect do |definition|
definition.accessor
end
accessors - ['target_author_signature', 'sender_handle']
end
def sender_handle= new_sender_handle
@sender = Person.where(:diaspora_handle => new_sender_handle).first
end
def sender_handle
@sender.diaspora_handle
end
def diaspora_handle
self.sender_handle
end
def subscribers(user)
self.target.subscribers(user)
end
def self.build(sender, target)
retraction = self.new
retraction.sender = sender
retraction.target = target
retraction.target_author_signature = retraction.sign_with_key(sender.encryption_key) if sender.person == target.author
retraction
end
def target
@target ||= self.target_type.constantize.where(:guid => target_guid).first
end
def guid
target_guid
end
def target= new_target
@target = new_target
@target_type = new_target.class.to_s
@target_guid = new_target.guid
end
def perform receiving_user
Rails.logger.debug "Performing retraction for #{target_guid}"
if reshare = Reshare.where(:author_id => receiving_user.person.id, :root_guid => target_guid).first
onward_retraction = self.dup
onward_retraction.sender = receiving_user.person
Postzord::Dispatch.new(receiving_user, onward_retraction).post
end
if target
self.target.unsocket_from_user receiving_user if target.respond_to? :unsocket_from_user
self.target.destroy
end
Rails.logger.info(:event => :retraction, :status => :complete, :target_type => self.target_type, :guid => self.target_guid)
end
def receive(recipient, sender)
if self.target.nil?
Rails.logger.info("event=retraction status=abort reason='no post found' sender=#{sender.diaspora_handle} target_guid=#{target_guid}")
return
elsif self.target_author_signature_valid?
#this is a retraction from the upstream owner
self.perform(recipient)
else
Rails.logger.info("event=receive status=abort reason='object signature not valid' recipient=#{recipient.diaspora_handle} sender=#{self.sender_handle} payload_type=#{self.class}")
return
end
self
end
def target_author_signature_valid?
verify_signature(self.target_author_signature, self.target.author)
end
end

View file

@ -136,7 +136,8 @@ class User < ActiveRecord::Base
end end
def dispatch_post(post, opts = {}) def dispatch_post(post, opts = {})
mailman = Postzord::Dispatch.new(self, post) additional_people = opts.delete(:additional_subscribers)
mailman = Postzord::Dispatch.new(self, post, :additional_subscribers => additional_people)
mailman.post(opts) mailman.post(opts)
end end
@ -229,14 +230,20 @@ class User < ActiveRecord::Base
end end
######### Posts and Such ############### ######### Posts and Such ###############
def retract(target) def retract(target, opts={})
if target.respond_to?(:relayable?) && target.relayable? if target.respond_to?(:relayable?) && target.relayable?
retraction = RelayableRetraction.build(self, target) retraction = RelayableRetraction.build(self, target)
elsif target.is_a? Post
retraction = SignedRetraction.build(self, target)
else else
retraction = Retraction.for(target) retraction = Retraction.for(target)
end end
mailman = Postzord::Dispatch.new(self, retraction) if target.is_a?(Post)
opts[:additional_subscribers] = target.resharers
end
mailman = Postzord::Dispatch.new(self, retraction, opts)
mailman.post mailman.post
retraction.perform(self) retraction.perform(self)
@ -326,7 +333,7 @@ class User < ActiveRecord::Base
end end
self.person = Person.new(opts[:person]) self.person = Person.new(opts[:person])
self.person.diaspora_handle = "#{opts[:username]}@#{AppConfig[:pod_uri].host}" self.person.diaspora_handle = "#{opts[:username]}@#{AppConfig[:pod_uri].authority}"
self.person.url = AppConfig[:pod_url] self.person.url = AppConfig[:pod_url]

View file

@ -4,7 +4,7 @@
.span-20.append-2.prepend-2.last .span-20.append-2.prepend-2.last
#main_stream.stream.status_message_show #main_stream.stream.status_message_show
= render 'shared/stream_element', :post => @post, :all_aspects => @post.aspects = render 'shared/stream_element', :post => @post, :commenting_disabled => defined?(@commenting_disabled)
%br %br
%br %br
%br %br

View file

@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0' <XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'
xmlns:hm='http://host-meta.net/xrd/1.0'> xmlns:hm='http://host-meta.net/xrd/1.0'>
<hm:Host><%= AppConfig[:pod_uri].host %></hm:Host> <hm:Host><%= AppConfig[:pod_uri].authority %></hm:Host>
<Link rel='lrdd' <Link rel='lrdd'
template='<%= AppConfig[:pod_url].chomp("/") %>/webfinger?q={uri}'> template='<%= AppConfig[:pod_url].chomp("/") %>/webfinger?q={uri}'>
<Title>Resource Descriptor</Title> <Title>Resource Descriptor</Title>

View file

@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0' <XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'
xmlns:hm='http://host-meta.net/xrd/1.0'> xmlns:hm='http://host-meta.net/xrd/1.0'>
<hm:Host><%= AppConfig[:pod_uri].host %></hm:Host> <hm:Host><%= AppConfig[:pod_uri].authority %></hm:Host>
<Link rel='lrdd' <Link rel='lrdd'
template='<%= AppConfig[:pod_url] %>webfinger?q={uri}'> template='<%= AppConfig[:pod_url] %>webfinger?q={uri}'>
<Title>Resource Descriptor</Title> <Title>Resource Descriptor</Title>

View file

@ -0,0 +1,25 @@
-# Copyright (c) 2010, Diaspora Inc. This file is
-# licensed under the Affero General Public License version 3 or later. See
-# the COPYRIGHT file.
.reshare
- if post
= person_image_link(post.author, :size => :thumb_small)
.content
.right
= link_to t("show_original"), post_path(post)
%span.from
= person_link(post.author, :class => "hovercardable")
- if post.activity_streams?
= link_to image_tag(post.image_url, 'data-small-photo' => post.image_url, 'data-full-photo' => post.image_url, :class => 'stream-photo'), post.object_url, :class => "stream-photo-link"
- else
= render 'status_messages/status_message', :post => post, :photos => post.photos
- if defined?(current_user) && current_user && (post.author_id != current_user.person.id) && (post.public?) && !reshare?(post)
%span.reshare_action
= reshare_link(post)
- else
= t('.deleted')

View file

@ -0,0 +1,9 @@
<% if @reshare.persisted? %>
$('.stream_element#<%=params[:root_guid]%>').addClass('reshared');
ContentUpdater.addPostToStream(
"<%= escape_javascript(render(:partial => 'shared/stream_element', :locals => {:post => @reshare }))=%>"
);
<% else %>
Diaspora.widgets.flashes.render({success:false,
notice: "<%= reshare_error_message(@reshare) %>"});
<% end %>

View file

@ -6,9 +6,11 @@
- if current_user && post.author.owner_id == current_user.id - if current_user && post.author.owner_id == current_user.id
.right.controls .right.controls
= link_to image_tag('deletelabel.png'), post_path(post), :confirm => t('are_you_sure'), :method => :delete, :remote => true, :class => "delete stream_element_delete", :title => t('delete') = link_to image_tag('deletelabel.png'), post_path(post), :confirm => t('are_you_sure'), :method => :delete, :remote => true, :class => "delete stream_element_delete", :title => t('delete')
- else - else
.right.controls .right.controls
= link_to image_tag('deletelabel.png'), post_visibility_path(:id => "42", :post_id => post.id), :method => :put, :remote => true, :class => "delete stream_element_delete", :title => t('hide') = link_to image_tag('deletelabel.png'), post_visibility_path(:id => "42", :post_id => post.id), :method => :put, :remote => true, :class => "delete stream_element_delete", :title => t('hide')
.undo_text.hidden .undo_text.hidden
= t('post_visibilites.update.post_hidden', :name => post.author.name) = t('post_visibilites.update.post_hidden', :name => post.author.name)
= link_to t('undo'), post_visibility_path(:id => "42", :post_id => post.id), :method => :put, :remote => true, :class => "delete stream_element_delete" = link_to t('undo'), post_visibility_path(:id => "42", :post_id => post.id), :method => :put, :remote => true, :class => "delete stream_element_delete"
@ -20,6 +22,7 @@
%span.from %span.from
= person_link(post.author, :class => 'hovercardable') = person_link(post.author, :class => 'hovercardable')
%time.time.timeago{:datetime => post.created_at, :integer => time_for_sort(post).to_i} %time.time.timeago{:datetime => post.created_at, :integer => time_for_sort(post).to_i}
%span.details %span.details
%span.timeago %span.timeago
@ -27,6 +30,8 @@
- if post.activity_streams? - if post.activity_streams?
= link_to image_tag(post.image_url, 'data-small-photo' => post.image_url, 'data-full-photo' => post.image_url, :class => 'stream-photo'), post.object_url, :class => "stream-photo-link" = link_to image_tag(post.image_url, 'data-small-photo' => post.image_url, 'data-full-photo' => post.image_url, :class => 'stream-photo'), post.object_url, :class => "stream-photo-link"
- elsif reshare?(post)
= render 'reshares/reshare', :reshare => post, :post => post.root
- else - else
= render 'status_messages/status_message', :post => post, :photos => post.photos = render 'status_messages/status_message', :post => post, :photos => post.photos
@ -52,7 +57,13 @@
- unless (defined?(@commenting_disabled) && @commenting_disabled) - unless (defined?(@commenting_disabled) && @commenting_disabled)
%span.like_action %span.like_action
= like_action(post, current_user) = like_action(post, current_user)
- if (post.author_id != current_user.person.id) && (post.public?) && !reshare?(post)
·
%span.reshare_action
= reshare_link(post)
· ·
= link_to t('comments.new_comment.comment'), '#', :class => 'focus_comment_textarea' = link_to t('comments.new_comment.comment'), '#', :class => 'focus_comment_textarea'
.likes.on_post .likes.on_post

View file

@ -5,7 +5,8 @@
:author => @status_message.author, :author => @status_message.author,
:photos => @status_message.photos, :photos => @status_message.photos,
:comments => [], :comments => [],
:all_aspects => current_user.aspects :all_aspects => current_user.aspects,
:reshare => nil
} }
), ),
:post_id => @status_message.guid}.to_json.html_safe%> :post_id => @status_message.guid}.to_json.html_safe%>

View file

@ -154,3 +154,12 @@ test:
open_invitations: false open_invitations: false
integration_1:
<<: *defaults
pod_url: "http://localhost:45789"
enable_splunk_logging: false
integration_2:
<<: *defaults
pod_url: "http://localhost:34658"
enable_splunk_logging: false

View file

@ -15,13 +15,12 @@ Diaspora::Application.configure do
# Show full error reports and disable caching # Show full error reports and disable caching
config.consider_all_requests_local = true config.consider_all_requests_local = true
config.action_view.debug_rjs = true
config.action_controller.perform_caching = false config.action_controller.perform_caching = false
# Don't care if the mailer can't send # Don't care if the mailer can't send
config.action_mailer.raise_delivery_errors = false config.action_mailer.raise_delivery_errors = false
config.active_support.deprecation = :log config.active_support.deprecation = :log
#config.threadsafe! config.threadsafe!
# Monkeypatch around the nasty "2.5MB exception page" issue, caused by very large environment vars # Monkeypatch around the nasty "2.5MB exception page" issue, caused by very large environment vars
# This snippet via: http://stackoverflow.com/questions/3114993/exception-pages-in-development-mode-take-upwards-of-15-30-seconds-to-render-why # This snippet via: http://stackoverflow.com/questions/3114993/exception-pages-in-development-mode-take-upwards-of-15-30-seconds-to-render-why

View file

@ -3,7 +3,7 @@
# the COPYRIGHT file. # the COPYRIGHT file.
Diaspora::Application.configure do Diaspora::Application.configure do
config.action_mailer.default_url_options = {:host => AppConfig[:pod_uri].host} config.action_mailer.default_url_options = {:host => AppConfig[:pod_uri].authority }
unless Rails.env == 'test' || AppConfig[:mailer_on] != true unless Rails.env == 'test' || AppConfig[:mailer_on] != true
if AppConfig[:mailer_method] == "sendmail" if AppConfig[:mailer_method] == "sendmail"
config.action_mailer.delivery_method = :sendmail config.action_mailer.delivery_method = :sendmail

View file

@ -26,3 +26,4 @@
attributes: attributes:
from_id: from_id:
taken: "is a duplicate of a pre-existing request." taken: "is a duplicate of a pre-existing request."

View file

@ -65,8 +65,10 @@ en:
attributes: attributes:
from_id: from_id:
taken: "is a duplicate of a pre-existing request." taken: "is a duplicate of a pre-existing request."
reshare:
attributes:
root_guid:
taken: "You've already reshared that post!"
error_messages: error_messages:
helper: helper:
invalid_fields: "Invalid Fields" invalid_fields: "Invalid Fields"
@ -579,7 +581,19 @@ en:
few: "%{count} new requests!" few: "%{count} new requests!"
many: "%{count} new requests!" many: "%{count} new requests!"
other: "%{count} new requests!" other: "%{count} new requests!"
reshares:
reshare:
reshare:
zero: "Reshare"
one: "1 Reshare"
few: "%{count} Reshares"
many: "%{count} Reshares"
other: "%{count} Reshares"
show_original: "Show Original"
reshare_confirmation: "Reshare %{author} - %{text}?"
deleted: "Original post deleted by author."
create:
failure: "There was an error resharing this post."
services: services:
index: index:
logged_in_as: "logged in as" logged_in_as: "logged in as"

View file

@ -47,3 +47,5 @@ en:
comments: comments:
show: "show all comments" show: "show all comments"
hide: "hide comments" hide: "hide comments"
reshares:
duplicate:"You've already reshared that post!"

View file

@ -6,6 +6,8 @@ Diaspora::Application.routes.draw do
# Posting and Reading # Posting and Reading
resources :reshares
resources :aspects do resources :aspects do
put :toggle_contact_visibility put :toggle_contact_visibility
end end
@ -16,7 +18,7 @@ Diaspora::Application.routes.draw do
resources :likes, :only => [:create, :destroy, :index] resources :likes, :only => [:create, :destroy, :index]
resources :comments, :only => [:create, :destroy, :index] resources :comments, :only => [:create, :destroy, :index]
end end
get 'p/:id' => 'publics#post', :as => 'public_post' get 'p/:guid' => 'publics#post', :as => 'public_post'
# roll up likes into a nested resource above # roll up likes into a nested resource above
resources :comments, :only => [:create, :destroy] do resources :comments, :only => [:create, :destroy] do

View file

@ -0,0 +1,9 @@
class AddRootIdToPosts < ActiveRecord::Migration
def self.up
add_column :posts, :root_guid, :string, :limit => 30
end
def self.down
remove_column :posts, :root_guid
end
end

View file

@ -268,6 +268,7 @@ ActiveRecord::Schema.define(:version => 20110707234802) do
t.string "provider_display_name" t.string "provider_display_name"
t.string "actor_url" t.string "actor_url"
t.integer "objectId" t.integer "objectId"
t.string "root_guid", :limit => 30
t.string "status_message_guid" t.string "status_message_guid"
t.integer "likes_count", :default => 0 t.integer "likes_count", :default => 0
end end

View file

@ -1,6 +1,6 @@
@javascript @javascript
Feature: disconnecting users Feature: disconnecting users
In order to deal with life In order to help users feel secure on Diaspora
As a User As a User
I want to be able to disconnect from others I want to be able to disconnect from others

View file

@ -64,6 +64,7 @@ Feature: posting
And I am on "bob@bob.bob"'s page And I am on "bob@bob.bob"'s page
And I hover over the ".stream_element" And I hover over the ".stream_element"
And I preemptively confirm the alert
And I click to delete the first post And I click to delete the first post
And I wait for the ajax to finish And I wait for the ajax to finish
And I go to "bob@bob.bob"'s page And I go to "bob@bob.bob"'s page

98
features/repost.feature Normal file
View file

@ -0,0 +1,98 @@
@javascript
Feature: public repost
In order to make Diaspora more viral
As a User
I want to reshare my friends post
Background:
Given a user named "Bob Jones" with email "bob@bob.bob"
And a user named "Alice Smith" with email "alice@alice.alice"
And a user with email "bob@bob.bob" is connected with "alice@alice.alice"
Scenario: does not show the reshare button on my own posts
And "bob@bob.bob" has a non public post with text "reshare this!"
And I sign in as "bob@bob.bob"
Then I should not see "Reshare"
Scenario: does not show a reshare button on other private pots
And "bob@bob.bob" has a non public post with text "reshare this!"
And I sign in as "alice@alice.alice"
Then I should not see "Reshare"
Scenario: does shows the reshare button on my own posts
And "bob@bob.bob" has a public post with text "reshare this!"
And I sign in as "alice@alice.alice"
Then I should see "Reshare"
Scenario: shows up on the profile page
And "bob@bob.bob" has a public post with text "reshare this!"
And I sign in as "alice@alice.alice"
And I preemptively confirm the alert
And I follow "Reshare"
And I wait for the ajax to finish
And I wait for 2 seconds
And I am on "alice@alice.alice"'s page
Then I should see "reshare this!"
Then I should see a ".reshare"
And I should see "Bob"
Scenario: shows up on the aspects page
And "bob@bob.bob" has a public post with text "reshare this!"
And I sign in as "alice@alice.alice"
And I preemptively confirm the alert
And I follow "Reshare"
And I wait for the ajax to finish
And I go to the home page
Then I should see a ".reshare"
And I follow "Your Aspects"
Then I should see "reshare this!"
Then I should see a ".reshare"
And I should see "Bob"
Scenario: can be retracted
And "bob@bob.bob" has a public post with text "reshare this!"
And I sign in as "alice@alice.alice"
And I preemptively confirm the alert
And I follow "Reshare"
And I wait for the ajax to finish
And I go to the home page
Then I should see a ".reshare"
And I follow "Your Aspects"
Then I should see "reshare this!"
Then I should see a ".reshare"
And I should see "Bob"
And I go to the destroy user session page
And I sign in as "bob@bob.bob"
And The user deletes their first post
And I go to the destroy user session page
And I sign in as "alice@alice.alice"
And I go to the home page
Then I should see "Original post deleted by author"
Scenario: Keeps track of the number of reshares
And "bob@bob.bob" has a public post with text "reshare this!"
And I sign in as "alice@alice.alice"
And I preemptively confirm the alert
And I follow "Reshare"
And I wait for the ajax to finish
And I go to the home page
Then I should see a ".reshare"
And I follow "Your Aspects"
Then I should see "reshare this!"
Then I should see a ".reshare"
And I should see "Bob"
And I go to the home page
And I should see "1 Reshare"
Scenario: Can have text

View file

@ -44,6 +44,10 @@ When /^I click to delete the first post$/ do
page.execute_script('$(".stream_element").first().find(".stream_element_delete").first().click()') page.execute_script('$(".stream_element").first().find(".stream_element_delete").first().click()')
end end
When /^I click to delete the ([\d])(nd|rd|st|th) post$/ do |number, stuff|
page.execute_script('$(".stream_element:nth-child('+ number +'").first().find(".stream_element_delete").first().click()')
end
When /^I click to delete the first comment$/ do When /^I click to delete the first comment$/ do
page.execute_script('$(".comment.posted").first().find(".comment_delete").click()') page.execute_script('$(".comment.posted").first().find(".comment_delete").click()')
end end

View file

@ -59,7 +59,7 @@ class Chubbies
def self.run def self.run
@pid = fork do @pid = fork do
Process.exec "cd #{Rails.root}/spec/chubbies/ && bundle exec rackup -p #{PORT} 2> /dev/null 1> /dev/null" Process.exec "cd #{Rails.root}/spec/chubbies/ && bundle exec #{run_command} #{nullify}"
end end
at_exit do at_exit do
@ -71,6 +71,10 @@ class Chubbies
end end
end end
def self.nullify
"2> /dev/null > /dev/null"
end
def self.kill def self.kill
`kill -9 #{get_pid}` `kill -9 #{get_pid}`
end end
@ -94,9 +98,13 @@ class Chubbies
end end
end end
def self.run_command
"rackup -p #{PORT}"
end
def self.get_pid def self.get_pid
@pid ||= lambda { @pid ||= lambda {
processes = `ps ax -o pid,command | grep "rackup -p #{PORT}"`.split("\n") processes = `ps ax -o pid,command | grep "#{run_command}"`.split("\n")
processes = processes.select{|p| !p.include?("grep") } processes = processes.select{|p| !p.include?("grep") }
processes.first.split(" ").first processes.first.split(" ").first
}.call }.call

View file

@ -8,3 +8,17 @@ end
Then /^I should see an uploaded image within the photo drop zone$/ do Then /^I should see an uploaded image within the photo drop zone$/ do
find("#photodropzone img")["src"].should include("uploads/images") find("#photodropzone img")["src"].should include("uploads/images")
end end
Given /^"([^"]*)" has a public post with text "([^"]*)"$/ do |email, text|
user = User.find_by_email(email)
user.post(:status_message, :text => text, :public => true, :to => user.aspects)
end
Given /^"([^"]*)" has a non public post with text "([^"]*)"$/ do |email, text|
user = User.find_by_email(email)
user.post(:status_message, :text => text, :public => false, :to => user.aspects)
end
When /^The user deletes their first post$/ do
@me.posts.first.destroy
end

View file

@ -3,7 +3,9 @@
# the COPYRIGHT file. # the COPYRIGHT file.
class Postzord::Dispatch class Postzord::Dispatch
def initialize(user, object)
# @note Takes :additional_subscribers param to add to subscribers to dispatch to
def initialize(user, object, opts={})
unless object.respond_to? :to_diaspora_xml unless object.respond_to? :to_diaspora_xml
raise 'this object does not respond_to? to_diaspora xml. try including Diaspora::Webhooks into your object' raise 'this object does not respond_to? to_diaspora xml. try including Diaspora::Webhooks into your object'
end end
@ -12,6 +14,7 @@ class Postzord::Dispatch
@object = object @object = object
@xml = @object.to_diaspora_xml @xml = @object.to_diaspora_xml
@subscribers = @object.subscribers(@sender) @subscribers = @object.subscribers(@sender)
@subscribers = @subscribers | [*opts[:additional_subscribers]] if opts[:additional_subscribers]
end end
def salmon def salmon

View file

@ -4,7 +4,7 @@
namespace :db do namespace :db do
desc "rebuild and prepare test db" desc "rebuild and prepare test db"
task :rebuild => [:drop, :create, :migrate, :seed,'db:test:prepare'] task :rebuild => [:drop, :drop_integration, :create, :migrate, :seed, 'db:test:prepare']
namespace :integration do namespace :integration do
# desc 'Check for pending migrations and load the integration schema' # desc 'Check for pending migrations and load the integration schema'
@ -81,6 +81,14 @@ namespace :db do
end end
task :add_user => :environment task :add_user => :environment
task :drop_integration do
ActiveRecord::Base.configurations.keys.select{ |k|
k.include?("integration")
}.each{ |k|
drop_database ActiveRecord::Base.configurations[k] rescue Mysql2::Error
}
end
task :fix_diaspora_handle do task :fix_diaspora_handle do
puts "fixing the people in this seed" puts "fixing the people in this seed"
require File.join(File.dirname(__FILE__), '..', '..', 'config', 'environment') require File.join(File.dirname(__FILE__), '..', '..', 'config', 'environment')

View file

@ -17,11 +17,11 @@ var View = {
}); });
Diaspora.widgets.subscribe("stream/scrolled", function() { Diaspora.widgets.subscribe("stream/scrolled", function() {
$('#main_stream .comments label').inFieldLabels(); $('#main_stream label').inFieldLabels();
}); });
Diaspora.widgets.subscribe("stream/reloaded", function() { Diaspora.widgets.subscribe("stream/reloaded", function() {
$('#main_stream .comments label').inFieldLabels(); $('#main_stream label').inFieldLabels();
}); });

View file

@ -475,6 +475,7 @@ ul.as-selections
.from .from
a a
:color $blue :color $blue
.status_message_show .status_message_show
.stream_element .stream_element
.content .content
@ -554,7 +555,8 @@ ul.as-selections
ul.comments, ul.comments,
ul.show_comments, ul.show_comments,
.likes_container .likes_container,
.stream_element .reshare
.avatar .avatar
:width 30px :width 30px
@ -589,18 +591,18 @@ ul.show_comments,
:height 250px :height 250px
:width 400px :width 400px
.content .content
:margin :margin
:top 0px :top 0px
:bottom -2px :bottom -2px
:padding :padding
:left 36px :left 36px
:right 10px :right 10px
p p
:margin :margin
:bottom 0 :bottom 0
:top 0 :top 0
.right .right
:right 4px :right 4px
@ -609,6 +611,10 @@ ul.show_comments,
:position absolute :position absolute
:display inline :display inline
.stream_element .reshare
:padding 10px
:border 1px solid #eee
ul.show_comments ul.show_comments
:padding :padding
:bottom 6px :bottom 6px

View file

@ -179,6 +179,12 @@ describe AspectsController do
assigns(:posts).models.length.should == 2 assigns(:posts).models.length.should == 2
end end
it "posts include reshares" do
reshare = alice.post(:reshare, :public => true, :root_guid => Factory(:status_message, :public => true).guid, :to => alice.aspects)
get :index
assigns[:posts].post_fakes.map{|x| x.id}.should include(reshare.id)
end
it "can filter to a single aspect" do it "can filter to a single aspect" do
get :index, :a_ids => [@alices_aspect_2.id.to_s] get :index, :a_ids => [@alices_aspect_2.id.to_s]
assigns(:posts).models.length.should == 1 assigns(:posts).models.length.should == 1

View file

@ -137,6 +137,7 @@ describe PeopleController do
response.body.match(profile.first_name).should be_false response.body.match(profile.first_name).should be_false
end end
context "when the person is the current user" do context "when the person is the current user" do
it "succeeds" do it "succeeds" do
get :show, :id => @user.person.to_param get :show, :id => @user.person.to_param
@ -204,6 +205,12 @@ describe PeopleController do
@public_posts.first.save @public_posts.first.save
end end
it "posts include reshares" do
reshare = @user.post(:reshare, :public => true, :root_guid => Factory(:status_message, :public => true).guid, :to => alice.aspects)
get :show, :id => @user.person.id
assigns[:posts].post_fakes.map{|x| x.id}.should include(reshare.id)
end
it "assigns only public posts" do it "assigns only public posts" do
get :show, :id => @person.id get :show, :id => @person.id
assigns[:posts].models.should =~ @public_posts assigns[:posts].models.should =~ @public_posts
@ -251,6 +258,12 @@ describe PeopleController do
assigns(:posts).models.should =~ posts_user_can_see assigns(:posts).models.should =~ posts_user_can_see
end end
it "posts include reshares" do
reshare = @user.post(:reshare, :public => true, :root_guid => Factory(:status_message, :public => true).guid, :to => alice.aspects)
get :show, :id => @user.person.id
assigns[:posts].post_fakes.map{|x| x.id}.should include(reshare.id)
end
it 'sets @commenting_disabled to true' do it 'sets @commenting_disabled to true' do
get :show, :id => @person.id get :show, :id => @person.id
assigns(:commenting_disabled).should == false assigns(:commenting_disabled).should == false
@ -283,6 +296,12 @@ describe PeopleController do
assigns[:posts].models.should =~ [public_post] assigns[:posts].models.should =~ [public_post]
end end
it "posts include reshares" do
reshare = @user.post(:reshare, :public => true, :root_guid => Factory(:status_message, :public => true).guid, :to => alice.aspects)
get :show, :id => @user.person.id
assigns[:posts].post_fakes.map{|x| x.id}.should include(reshare.id)
end
it 'sets @commenting_disabled to true' do it 'sets @commenting_disabled to true' do
get :show, :id => @person.id get :show, :id => @person.id
assigns(:commenting_disabled).should == true assigns(:commenting_disabled).should == true

View file

@ -65,22 +65,49 @@ describe PublicsController do
it 'shows a public post' do it 'shows a public post' do
status = alice.post(:status_message, :text => "hello", :public => true, :to => 'all') status = alice.post(:status_message, :text => "hello", :public => true, :to => 'all')
get :post, :id => status.id get :post, :guid => status.id
response.status= 200 response.status= 200
end end
it 'does not show a private post' do it 'does not show a private post' do
status = alice.post(:status_message, :text => "hello", :public => false, :to => 'all') status = alice.post(:status_message, :text => "hello", :public => false, :to => 'all')
get :post, :id => status.id get :post, :guid => status.id
response.status = 302 response.status = 302
end end
it 'redirects to the proper show page if the user has visibility of the post' do it 'redirects to the proper show page if the user has visibility of the post' do
status = alice.post(:status_message, :text => "hello", :public => true, :to => 'all') status = alice.post(:status_message, :text => "hello", :public => true, :to => 'all')
sign_in bob sign_in bob
get :post, :id => status.id get :post, :guid => status.id
response.should be_redirect response.should be_redirect
end end
it 'responds with diaspora xml if format is xml' do
status = alice.post(:status_message, :text => "hello", :public => true, :to => 'all')
get :post, :guid => status.guid, :format => :xml
response.body.should == status.to_diaspora_xml
end
# We want to be using guids from now on for this post route, but do not want to break
# preexisiting permalinks. We can assume a guid is 8 characters long as we have
# guids set to hex(8) since we started using them.
context 'id/guid switch' do
before do
@status = alice.post(:status_message, :text => "hello", :public => true, :to => 'all')
end
it 'assumes guids less than 8 chars are ids and not guids' do
Post.should_receive(:where).with(hash_including(:id => @status.id)).and_return(Post)
get :post, :guid => @status.id
response.status= 200
end
it 'assumes guids more than (or equal to) 8 chars are actually guids' do
Post.should_receive(:where).with(hash_including(:guid => @status.guid)).and_return(Post)
get :post, :guid => @status.guid
response.status= 200
end
end
end end
describe '#hcard' do describe '#hcard' do

View file

@ -0,0 +1,39 @@
require 'spec_helper'
describe ResharesController do
describe '#create' do
it 'requires authentication' do
post :create, :format => :js
response.should_not be_success
end
context 'with an authenticated user' do
before do
sign_in :user, bob
@post_guid = Factory(:status_message, :public => true).guid
@controller.stub(:current_user).and_return(bob)
end
it 'succeeds' do
post :create, :format => :js, :root_guid => @post_guid
response.should be_success
end
it 'creates a reshare' do
expect{
post :create, :format => :js, :root_guid => @post_guid
}.should change(Reshare, :count).by(1)
end
it 'after save, calls add to streams' do
bob.should_receive(:add_to_streams)
post :create, :format => :js, :root_guid => @post_guid
end
it 'calls dispatch' do
bob.should_receive(:dispatch_post).with(anything, hash_including(:additional_subscribers))
post :create, :format => :js, :root_guid => @post_guid
end
end
end
end

View file

@ -19,7 +19,7 @@ end
Factory.define :person do |p| Factory.define :person do |p|
p.sequence(:diaspora_handle) { |n| "bob-person-#{n}#{r_str}@aol.com" } p.sequence(:diaspora_handle) { |n| "bob-person-#{n}#{r_str}@aol.com" }
p.sequence(:url) { |n| "http://google-#{n}#{r_str}.com/" } p.sequence(:url) { |n| AppConfig[:pod_url] }
p.serialized_public_key OpenSSL::PKey::RSA.generate(1024).public_key.export p.serialized_public_key OpenSSL::PKey::RSA.generate(1024).public_key.export
p.after_build do |person| p.after_build do |person|
person.profile ||= Factory.build(:profile, :person => person) person.profile ||= Factory.build(:profile, :person => person)
@ -41,6 +41,7 @@ Factory.define :like do |x|
end end
Factory.define :user do |u| Factory.define :user do |u|
u.getting_started false
u.sequence(:username) { |n| "bob#{n}#{r_str}" } u.sequence(:username) { |n| "bob#{n}#{r_str}" }
u.sequence(:email) { |n| "bob#{n}#{r_str}@pivotallabs.com" } u.sequence(:email) { |n| "bob#{n}#{r_str}@pivotallabs.com" }
u.password "bluepin7" u.password "bluepin7"
@ -91,6 +92,11 @@ Factory.define(:photo) do |p|
end end
end end
Factory.define :reshare do |r|
r.association(:root, :public => true, :factory => :status_message)
r.association(:author, :factory => :person)
end
Factory.define :service do |service| Factory.define :service do |service|
service.nickname "sirrobertking" service.nickname "sirrobertking"
service.type "Services::Twitter" service.type "Services::Twitter"

View file

@ -0,0 +1,15 @@
require 'spec_helper'
# Specs in this file have access to a helper object that includes
# the ResharesHelper. For example:
#
# describe ResharesHelper do
# describe "string concat" do
# it "concats two strings with spaces" do
# helper.concat_strings("this","that").should == "this that"
# end
# end
# end
describe ResharesHelper do
pending "add some examples to (or delete) #{__FILE__}"
end

View file

@ -23,10 +23,20 @@ describe Postzord::Dispatch do
zord.instance_variable_get(:@object).should == @sm zord.instance_variable_get(:@object).should == @sm
end end
it 'sets @subscribers from object' do context 'setting @subscribers' do
@sm.should_receive(:subscribers).and_return(@subscribers) it 'sets @subscribers from object' do
zord = Postzord::Dispatch.new(alice, @sm) @sm.should_receive(:subscribers).and_return(@subscribers)
zord.instance_variable_get(:@subscribers).should == @subscribers zord = Postzord::Dispatch.new(alice, @sm)
zord.instance_variable_get(:@subscribers).should == @subscribers
end
it 'accepts additional subscribers from opts' do
new_person = Factory(:person)
@sm.should_receive(:subscribers).and_return(@subscribers)
zord = Postzord::Dispatch.new(alice, @sm, :additional_subscribers => new_person)
zord.instance_variable_get(:@subscribers).should == @subscribers | [new_person]
end
end end
it 'sets the @sender_person object' do it 'sets the @sender_person object' do

View file

@ -41,7 +41,7 @@ describe Person do
context 'local people' do context 'local people' do
it 'uses the pod config url to set the diaspora_handle' do it 'uses the pod config url to set the diaspora_handle' do
new_person = User.build(:username => "foo123", :email => "foo123@example.com", :password => "password", :password_confirmation => "password").person new_person = User.build(:username => "foo123", :email => "foo123@example.com", :password => "password", :password_confirmation => "password").person
new_person.diaspora_handle.should == "foo123@#{AppConfig[:pod_uri].host}" new_person.diaspora_handle.should == "foo123@#{AppConfig[:pod_uri].authority}"
end end
end end

151
spec/models/reshare_spec.rb Normal file
View file

@ -0,0 +1,151 @@
require 'spec_helper'
describe Reshare do
include ActionView::Helpers::UrlHelper
include Rails.application.routes.url_helpers
def controller
mock()
end
it 'has a valid Factory' do
Factory(:reshare).should be_valid
end
it 'requires root' do
reshare = Factory.build(:reshare, :root => nil)
reshare.should_not be_valid
end
it 'require public root' do
Factory.build(:reshare, :root => Factory.build(:status_message, :public => false)).should_not be_valid
end
it 'forces public' do
Factory(:reshare, :public => false).public.should be_true
end
describe "#receive" do
before do
@reshare = Factory.create(:reshare, :root => Factory(:status_message, :author => bob.person, :public => true))
@root = @reshare.root
@reshare.receive(@root.author.owner, @reshare.author)
end
it 'increments the reshare count' do
@root.resharers.count.should == 1
end
it 'adds the resharer to the re-sharers of the post' do
@root.resharers.should include(@reshare.author)
end
end
describe "XML" do
before do
@reshare = Factory(:reshare)
@xml = @reshare.to_xml.to_s
end
context 'serialization' do
it 'serializes root_diaspora_id' do
@xml.should include("root_diaspora_id")
@xml.should include(@reshare.author.diaspora_handle)
end
it 'serializes root_guid' do
@xml.should include("root_guid")
@xml.should include(@reshare.root.guid)
end
end
context 'marshalling' do
context 'local' do
before do
@original_author = @reshare.root.author
@root_object = @reshare.root
end
it 'marshals the guid' do
Reshare.from_xml(@xml).root_guid.should == @root_object.guid
end
it 'fetches the root post from root_guid' do
Reshare.from_xml(@xml).root.should == @root_object
end
it 'fetches the root author from root_diaspora_id' do
Reshare.from_xml(@xml).root.author.should == @original_author
end
end
context 'remote' do
before do
@root_object = @reshare.root
@root_object.delete
end
it 'fetches the root author from root_diaspora_id' do
@original_profile = @reshare.root.author.profile.dup
@reshare.root.author.profile.delete
@original_author = @reshare.root.author.dup
@reshare.root.author.delete
@original_author.profile = @original_profile
wf_prof_mock = mock
wf_prof_mock.should_receive(:fetch).and_return(@original_author)
Webfinger.should_receive(:new).and_return(wf_prof_mock)
response = mock
response.stub(:body).and_return(@root_object.to_diaspora_xml)
Faraday.default_connection.should_receive(:get).with(@original_author.url + public_post_path(:guid => @root_object.guid, :format => "xml")).and_return(response)
Reshare.from_xml(@xml)
end
context 'saving the post' do
before do
response = mock
response.stub(:body).and_return(@root_object.to_diaspora_xml)
Faraday.default_connection.stub(:get).with(@reshare.root.author.url + public_post_path(:guid => @root_object.guid, :format => "xml")).and_return(response)
end
it 'fetches the root post from root_guid' do
root = Reshare.from_xml(@xml).root
[:text, :guid, :diaspora_handle, :type, :public].each do |attr|
root.send(attr).should == @reshare.root.send(attr)
end
end
it 'correctly saves the type' do
Reshare.from_xml(@xml).root.reload.type.should == "StatusMessage"
end
it 'correctly sets the author' do
@original_author = @reshare.root.author
Reshare.from_xml(@xml).root.reload.author.reload.should == @original_author
end
it 'verifies that the author of the post received is the same as the author in the reshare xml' do
@original_author = @reshare.root.author.dup
@xml = @reshare.to_xml.to_s
different_person = Factory.create(:person)
wf_prof_mock = mock
wf_prof_mock.should_receive(:fetch).and_return(different_person)
Webfinger.should_receive(:new).and_return(wf_prof_mock)
different_person.stub(:url).and_return(@original_author.url)
lambda{
Reshare.from_xml(@xml)
}.should raise_error /^Diaspora ID \(.+\) in the root does not match the Diaspora ID \(.+\) specified in the reshare!$/
end
end
end
end
end
end

View file

@ -8,7 +8,7 @@ describe Retraction do
before do before do
@aspect = alice.aspects.first @aspect = alice.aspects.first
alice.contacts.create(:person => eve.person, :aspects => [@aspect]) alice.contacts.create(:person => eve.person, :aspects => [@aspect])
@post = alice.post :status_message, :text => "Destroy!", :to => @aspect.id @post = alice.post(:status_message, :public => true, :text => "Destroy!", :to => @aspect.id)
end end
describe 'serialization' do describe 'serialization' do
@ -20,12 +20,24 @@ describe Retraction do
end end
describe '#subscribers' do describe '#subscribers' do
it 'returns the subscribers to the post for all objects other than person' do context 'posts' do
retraction = Retraction.for(@post) before do
obj = retraction.instance_variable_get(:@object) @retraction = Retraction.for(@post)
wanted_subscribers = obj.subscribers(alice) @obj = @retraction.instance_variable_get(:@object)
obj.should_receive(:subscribers).with(alice).and_return(wanted_subscribers) @wanted_subscribers = @obj.subscribers(alice)
retraction.subscribers(alice).map{|s| s.id}.should =~ wanted_subscribers.map{|s| s.id} end
it 'returns the subscribers to the post for all objects other than person' do
@retraction.subscribers(alice).map(&:id).should =~ @wanted_subscribers.map(&:id)
end
it 'does not return the authors of reshares' do
@post.reshares << Factory.create(:reshare, :root => @post, :author => bob.person)
@post.save!
@wanted_subscribers -= [bob.person]
@retraction.subscribers(alice).map(&:id).should =~ @wanted_subscribers.map(&:id)
end
end end
context 'setting subscribers' do context 'setting subscribers' do

View file

@ -0,0 +1,47 @@
require 'spec_helper'
describe SignedRetraction do
before do
@post = Factory(:status_message, :author => bob.person, :public => true)
@resharer = Factory(:user)
@post.reshares << Factory.create(:reshare, :root => @post, :author => @resharer.person)
@post.save!
end
describe '#perform' do
it "dispatches the retraction onward to recipients of the recipient's reshare" do
retraction = SignedRetraction.build(bob, @post)
onward_retraction = retraction.dup
retraction.should_receive(:dup).and_return(onward_retraction)
dis = mock
Postzord::Dispatch.should_receive(:new).with(@resharer, onward_retraction).and_return(dis)
dis.should_receive(:post)
retraction.perform(@resharer)
end
it 'relays the retraction onward even if the post does not exist' do
remote_post = Factory(:status_message, :public => true)
bob.post(:reshare, :root_guid => remote_post.guid)
alice.post(:reshare, :root_guid => remote_post.guid)
remote_retraction = SignedRetraction.new.tap{|r|
r.target_type = remote_post.type
r.target_guid = remote_post.guid
r.sender = remote_post.author
r.stub!(:target_author_signature_valid?).and_return(true)
}
remote_retraction.dup.perform(bob)
Post.exists?(:id => remote_post.id).should be_false
dis = mock
Postzord::Dispatch.should_receive(:new){ |sender, retraction|
sender.should == alice
retraction.sender.should == alice.person
dis
}
dis.should_receive(:post)
remote_retraction.perform(alice)
end
end
end

View file

@ -114,13 +114,13 @@ describe User do
alice.email = eve.email alice.email = eve.email
alice.should_not be_valid alice.should_not be_valid
end end
it "requires a vaild email address" do it "requires a vaild email address" do
alice.email = "somebody@anywhere" alice.email = "somebody@anywhere"
alice.should_not be_valid alice.should_not be_valid
end end
end end
describe "of unconfirmed_email" do describe "of unconfirmed_email" do
it "unconfirmed_email address can be nil/blank" do it "unconfirmed_email address can be nil/blank" do
alice.unconfirmed_email = nil alice.unconfirmed_email = nil
@ -134,7 +134,7 @@ describe User do
alice.unconfirmed_email = "new@email.com" alice.unconfirmed_email = "new@email.com"
alice.should be_valid alice.should be_valid
end end
it "requires a vaild unconfirmed_email address" do it "requires a vaild unconfirmed_email address" do
alice.unconfirmed_email = "somebody@anywhere" alice.unconfirmed_email = "somebody@anywhere"
alice.should_not be_valid alice.should_not be_valid
@ -679,7 +679,7 @@ describe User do
user.confirm_email_token.size.should eql(30) user.confirm_email_token.size.should eql(30)
end end
end end
describe '#mail_confirm_email' do describe '#mail_confirm_email' do
it 'enqueues a mail job on user with unconfirmed email' do it 'enqueues a mail job on user with unconfirmed email' do
user.update_attribute(:unconfirmed_email, "alice@newmail.com") user.update_attribute(:unconfirmed_email, "alice@newmail.com")
@ -712,14 +712,14 @@ describe User do
user.unconfirmed_email.should_not eql(nil) user.unconfirmed_email.should_not eql(nil)
user.confirm_email_token.should_not eql(nil) user.confirm_email_token.should_not eql(nil)
end end
it 'returns false and does not change anything on blank token' do it 'returns false and does not change anything on blank token' do
user.confirm_email("").should eql(false) user.confirm_email("").should eql(false)
user.email.should_not eql("alice@newmail.com") user.email.should_not eql("alice@newmail.com")
user.unconfirmed_email.should_not eql(nil) user.unconfirmed_email.should_not eql(nil)
user.confirm_email_token.should_not eql(nil) user.confirm_email_token.should_not eql(nil)
end end
it 'returns false and does not change anything on blank token' do it 'returns false and does not change anything on blank token' do
user.confirm_email(nil).should eql(false) user.confirm_email(nil).should eql(false)
user.email.should_not eql("alice@newmail.com") user.email.should_not eql("alice@newmail.com")
@ -752,4 +752,43 @@ describe User do
end end
end end
end end
describe '#retract' do
before do
@retraction = mock
@post = Factory(:status_message, :author => bob.person, :public => true)
end
context "posts" do
before do
SignedRetraction.stub(:build).and_return(@retraction)
@retraction.stub(:perform)
end
it 'sends a retraction' do
dispatcher = mock
Postzord::Dispatch.should_receive(:new).with(bob, @retraction, anything()).and_return(dispatcher)
dispatcher.should_receive(:post)
bob.retract(@post)
end
it 'adds resharers of target post as additional subsctibers' do
person = Factory(:person)
reshare = Factory(:reshare, :root => @post, :author => person)
@post.reshares << reshare
dispatcher = mock
Postzord::Dispatch.should_receive(:new).with(bob, @retraction, {:additional_subscribers => [person]}).and_return(dispatcher)
dispatcher.should_receive(:post)
bob.retract(@post)
end
it 'performs the retraction' do
pending
end
end
end
end end

View file

@ -0,0 +1,76 @@
require 'spec_helper'
unless Server.all.empty?
describe "reposting" do
before(:all) do
WebMock::Config.instance.allow_localhost = true
enable_typhoeus
#Server.all.each{|s| s.kill if s.running?}
#Server.all.each{|s| s.run}
end
after(:all) do
disable_typhoeus
#Server.all.each{|s| s.kill if s.running?}
#sleep(1)
#Server.all.each{|s| puts "Server at port #{s.port} still running." if s.running?}
WebMock::Config.instance.allow_localhost = false
end
before do
Server.all.each{|s| s.truncate_database; }
@original_post = nil
Server[0].in_scope do
original_poster = Factory.create(:user_with_aspect, :username => "original_poster")
resharer = Factory.create(:user_with_aspect, :username => "resharer")
connect_users_with_aspects(original_poster, resharer)
@original_post = original_poster.post(:status_message,
:public => true,
:text => "Awesome Sauce!",
:to => 'all')
end
Server[1].in_scope do
recipient = Factory.create(:user_with_aspect, :username => "recipient")
end
Server[0].in_scope do
r = User.find_by_username("resharer")
person = Webfinger.new("recipient@localhost:#{Server[1].port}").fetch
r.share_with(person, r.aspects.first)
end
Server[1].in_scope do
r = User.find_by_username("recipient")
person = Webfinger.new("resharer@localhost:#{Server[0].port}").fetch
r.share_with(person, r.aspects.first)
end
Server[0].in_scope do
r = User.find_by_username("resharer")
r.post(:reshare, :root_guid => @original_post.guid, :to => 'all')
end
end
it 'fetches the original post from the root server' do
Server[1].in_scope do
Post.exists?(:guid => @original_post.guid).should be_true
end
end
it 'relays the retraction for the root post to recipients of the reshare' do
Server[0].in_scope do
poster = User.find_by_username 'original_poster'
fantasy_resque do
poster.retract @original_post
end
end
Server[1].in_scope do
Post.exists?(:guid => @original_post.guid).should be_false
end
end
end
end

View file

@ -1,14 +1,18 @@
#This is a spec for the class that runs the servers used in the other multi-server specs #This is a spec for the class that runs the servers used in the other multi-server specs
require 'spec_helper' require 'spec_helper'
WebMock::Config.instance.allow_localhost = true unless Server.all.empty?
if Server.all && Server.all.first && Server.all.first.running?
describe Server do describe Server do
before(:all) do before(:all) do
WebMock::Config.instance.allow_localhost = true WebMock::Config.instance.allow_localhost = true
#Server.all.each{|s| s.kill if s.running?}
#Server.all.each{|s| s.run}
end end
after(:all) do after(:all) do
#Server.all.each{|s| s.kill if s.running?}
#sleep(1)
#Server.all.each{|s| puts "Server at port #{s.port} still running." if s.running?}
WebMock::Config.instance.allow_localhost = false WebMock::Config.instance.allow_localhost = false
end end
@ -25,12 +29,12 @@ if Server.all && Server.all.first && Server.all.first.running?
it 'takes an environment' do it 'takes an environment' do
server = Server.new("integration_1") server = Server.new("integration_1")
server.env.should == "integration_1" server.env.should == "integration_1"
server.port.should == ActiveRecord::Base.configurations["integration_1"]["app_server_port"]
end end
end end
describe "#running?" do describe "#running?" do
it "verifies that the server is running" do it "verifies that the server is running" do
Server.new("integration_1").running?.should be_true server = Server.new("integration_1")
server.running?.should be_true
end end
end end
describe '#db' do describe '#db' do
@ -52,4 +56,3 @@ if Server.all && Server.all.first && Server.all.first.running?
end end
end end
end end
WebMock::Config.instance.allow_localhost = false

View file

@ -1,5 +1,10 @@
#This class is for running servers in the background during integration testing. This will not run on Windows. #This class is for running servers in the background during integration testing. This will not run on Windows.
class Server class Server
def self.[] index
self.all[index]
end
def self.all def self.all
@servers ||= ActiveRecord::Base.configurations.keys.select{ @servers ||= ActiveRecord::Base.configurations.keys.select{
|k| k.include?("integration") |k| k.include?("integration")
@ -8,9 +13,46 @@ class Server
attr_reader :port, :env attr_reader :port, :env
def initialize(env) def initialize(env)
@config = ActiveRecord::Base.configurations[env] @db_config = ActiveRecord::Base.configurations[env]
@port = @config["app_server_port"] @app_config = (YAML.load_file AppConfig.source)[env]
@port = URI::parse(@app_config["pod_url"]).port
@env = env @env = env
ensure_database_setup
end
def ensure_database_setup
`cd #{Rails.root} && RAILS_ENV=#{@env} bundle exec rake db:create`
tables_exist = self.db do
ActiveRecord::Base.connection.tables.include?("users")
end
if tables_exist
truncate_database
else
`cd #{Rails.root} && RAILS_ENV=#{@env} bundle exec rake db:schema:load`
end
end
def run
@pid = fork do
Process.exec "cd #{Rails.root} && RAILS_ENV=#{@env} bundle exec #{run_command}"# 2> /dev/null"
end
end
def kill
puts "I am trying to kill the server"
`kill -9 #{get_pid}`
end
def run_command
"rails s -p #{@port}"
end
def get_pid
@pid = lambda {
processes = `ps ax -o pid,command | grep "#{run_command}"`.split("\n")
processes = processes.select{|p| !p.include?("grep") }
processes.first.split(" ").first
}.call
end end
def running? def running?
@ -25,8 +67,12 @@ class Server
def db def db
former_env = Rails.env former_env = Rails.env
ActiveRecord::Base.establish_connection(env) ActiveRecord::Base.establish_connection(env)
yield begin
ActiveRecord::Base.establish_connection(former_env) result = yield
ensure
ActiveRecord::Base.establish_connection(former_env)
end
result
end end
def truncate_database def truncate_database
@ -34,4 +80,18 @@ class Server
DatabaseCleaner.clean_with(:truncation) DatabaseCleaner.clean_with(:truncation)
end end
end end
def in_scope
pod_url = "http://localhost:#{@port}/"
old_pod_url = AppConfig[:pod_url]
AppConfig[:pod_url] = pod_url
begin
result = db do
yield
end
ensure
AppConfig[:pod_url] = old_pod_url
end
result
end
end end

View file

@ -1,4 +1,8 @@
class User class User
include Rails.application.routes.url_helpers
def default_url_options
{:host => AppConfig[:pod_url]}
end
alias_method :share_with_original, :share_with alias_method :share_with_original, :share_with
@ -16,7 +20,9 @@ class User
aspects = self.aspects_from_ids(opts[:to]) aspects = self.aspects_from_ids(opts[:to])
add_to_streams(p, aspects) add_to_streams(p, aspects)
dispatch_post(p, :to => opts[:to]) dispatch_opts = {:url => post_url(p), :to => opts[:to]}
dispatch_opts.merge!(:additional_subscribers => p.root.author) if class_name == :reshare
dispatch_post(p, dispatch_opts)
end end
unless opts[:created_at] unless opts[:created_at]
p.created_at = Time.now - 1 p.created_at = Time.now - 1