Merge pull request #6744 from SuperTux88/refactor-services

Refactor services
This commit is contained in:
Dennis Schubert 2016-03-08 23:35:43 +01:00
commit df1c1ab24e
23 changed files with 661 additions and 444 deletions

View file

@ -12,17 +12,16 @@ class CommentsController < ApplicationController
end
def create
@comment = CommentService.new(post_id: params[:post_id], text: params[:text], user: current_user).create_comment
if @comment
respond_create_success
comment = comment_service.create(params[:post_id], params[:text])
if comment
respond_create_success(comment)
else
render nothing: true, status: 404
end
end
def destroy
service = CommentService.new(comment_id: params[:id], user: current_user)
if service.destroy_comment
if comment_service.destroy(params[:id])
respond_destroy_success
else
respond_destroy_error
@ -36,22 +35,24 @@ class CommentsController < ApplicationController
end
def index
service = CommentService.new(post_id: params[:post_id], user: current_user)
@post = service.post
@comments = service.comments
comments = comment_service.find_for_post(params[:post_id])
respond_with do |format|
format.json { render json: CommentPresenter.as_collection(@comments), status: 200 }
format.mobile { render layout: false }
format.json { render json: CommentPresenter.as_collection(comments), status: 200 }
format.mobile { render layout: false, locals: {comments: comments} }
end
end
private
def respond_create_success
def comment_service
@comment_service ||= CommentService.new(current_user)
end
def respond_create_success(comment)
respond_to do |format|
format.json { render json: CommentPresenter.new(@comment), status: 201 }
format.json { render json: CommentPresenter.new(comment), status: 201 }
format.html { render nothing: true, status: 201 }
format.mobile { render partial: "comment", locals: {post: @comment.post, comment: @comment} }
format.mobile { render partial: "comment", locals: {comment: comment} }
end
end

View file

@ -3,22 +3,13 @@
# the COPYRIGHT file.
class PostsController < ApplicationController
include PostsHelper
before_action :authenticate_user!, only: :destroy
before_action :set_format_if_malformed_from_status_net, only: :show
respond_to :html, :mobile, :json, :xml
rescue_from Diaspora::NonPublic do
if user_signed_in?
@code = "not-public"
respond_to do |format|
format.all { render template: "errors/not_public", status: 404, layout: "error_page" }
end
else
authenticate_user!
end
authenticate_user!
end
rescue_from Diaspora::NotMine do
@ -26,38 +17,36 @@ class PostsController < ApplicationController
end
def show
post_service = PostService.new(id: params[:id], user: current_user)
post_service.mark_user_notifications
@post = post_service.post
post = post_service.find!(params[:id])
post_service.mark_user_notifications(post.id)
respond_to do |format|
format.html { gon.post = post_service.present_json }
format.xml { render xml: @post.to_diaspora_xml }
format.json { render json: post_service.present_json }
format.html {
gon.post = PostPresenter.new(post, current_user)
render locals: {post: post}
}
format.mobile { render locals: {post: post} }
format.xml { render xml: post.to_diaspora_xml }
format.json { render json: PostPresenter.new(post, current_user) }
end
end
def iframe
render text: post_iframe_url(params[:id]), layout: false
end
def oembed
post_id = OEmbedPresenter.id_from_url(params.delete(:url))
post_service = PostService.new(id: post_id, user: current_user,
oembed: params.slice(:format, :maxheight, :minheight))
render json: post_service.present_oembed
post = post_service.find!(post_id)
oembed = params.slice(:format, :maxheight, :minheight)
render json: OEmbedPresenter.new(post, oembed)
rescue
render nothing: true, status: 404
end
def interactions
post_service = PostService.new(id: params[:id], user: current_user)
respond_with post_service.present_interactions_json
post = post_service.find!(params[:id])
respond_with PostInteractionPresenter.new(post, current_user)
end
def destroy
post_service = PostService.new(id: params[:id], user: current_user)
post_service.retract_post
@post = post_service.post
post_service.destroy(params[:id])
respond_to do |format|
format.js { render "destroy", layout: false, format: :js }
format.json { render nothing: true, status: 204 }
format.any { redirect_to stream_path }
end
@ -65,6 +54,10 @@ class PostsController < ApplicationController
private
def post_service
@post_service ||= PostService.new(current_user)
end
def set_format_if_malformed_from_status_net
request.format = :html if request.format == "application/html+xml"
end

View file

@ -47,12 +47,17 @@ class StatusMessagesController < ApplicationController
end
def create
@status_message = StatusMessageCreationService.new(params, current_user).status_message
handle_mention_feedback
normalized_params = params.merge(
services: normalize_services,
aspect_ids: normalize_aspect_ids,
public: normalize_public_flag
)
status_message = StatusMessageCreationService.new(current_user).create(normalized_params)
handle_mention_feedback(status_message)
respond_to do |format|
format.html { redirect_to :back }
format.mobile { redirect_to stream_path }
format.json { render json: PostPresenter.new(@status_message, current_user), status: 201 }
format.json { render json: PostPresenter.new(status_message, current_user), status: 201 }
end
rescue StandardError => error
handle_create_error(error)
@ -73,9 +78,9 @@ class StatusMessagesController < ApplicationController
end
end
def handle_mention_feedback
def handle_mention_feedback(status_message)
return unless comes_from_others_profile_page?
flash[:notice] = successful_mention_message
flash[:notice] = t("status_messages.create.success", names: status_message.mentioned_people_names)
end
def comes_from_others_profile_page?
@ -87,11 +92,24 @@ class StatusMessagesController < ApplicationController
end
def own_profile_page?
request.env["HTTP_REFERER"].include?("/people/" + params[:status_message][:author][:guid].to_s)
request.env["HTTP_REFERER"].include?("/people/" + current_user.guid)
end
def successful_mention_message
t("status_messages.create.success", names: @status_message.mentioned_people_names)
def normalize_services
[*params[:services]].compact
end
def normalize_aspect_ids
aspect_ids = [*params[:aspect_ids]]
if aspect_ids.first == "all_aspects"
current_user.aspect_ids
else
aspect_ids
end
end
def normalize_public_flag
[*params[:aspect_ids]].first == "public"
end
def remove_getting_started

View file

@ -133,12 +133,10 @@ class Post < ActiveRecord::Base
#############
def self.diaspora_initialize(params)
new_post = self.new params.to_hash.stringify_keys.slice(*self.column_names)
new_post.author = params[:author]
new_post.public = params[:public] if params[:public]
new_post.pending = params[:pending] if params[:pending]
new_post.diaspora_handle = new_post.author.diaspora_handle
new_post
new(params.to_hash.stringify_keys.slice(*column_names)).tap do |new_post|
new_post.author = params[:author]
new_post.diaspora_handle = new_post.author.diaspora_handle
end
end
# @return Returns true if this Post will accept updates (i.e. updates to the caption of a photo).
@ -157,21 +155,4 @@ class Post < ActiveRecord::Base
def nsfw
self.author.profile.nsfw?
end
def self.find_public(id)
where(post_key(id) => id).includes(:author, comments: :author).first.tap do |post|
raise ActiveRecord::RecordNotFound.new("could not find a post with id #{id}") unless post
raise Diaspora::NonPublic unless post.public?
end
end
def self.find_non_public_by_guid_or_id_with_user(id, user)
user.find_visible_shareable_by_id(Post, id, key: post_key(id)).tap do |post|
raise ActiveRecord::RecordNotFound.new("could not find a post with id #{id}") unless post
end
end
def self.post_key(id)
id.to_s.length <= 8 ? :id : :guid
end
end

View file

@ -1,43 +1,32 @@
class CommentService
attr_reader :post, :comments
def initialize(params)
@user = params[:user]
@post_id = params[:post_id]
@comment_id = params[:comment_id]
@text = params[:text]
@post = find_post! if @post_id
@comments = @post.comments.for_a_stream if @post
def initialize(user=nil)
@user = user
end
def create_comment
@user.comment!(post, @text) if @post
def create(post_id, text)
post = post_service.find!(post_id)
user.comment!(post, text)
end
def destroy_comment
@comment = Comment.find(@comment_id)
if @user.owns?(@comment) || @user.owns?(@comment.parent)
@user.retract(@comment)
def destroy(comment_id)
comment = Comment.find(comment_id)
if user.owns?(comment) || user.owns?(comment.parent)
user.retract(comment)
true
else
false
end
end
def find_for_post(post_id)
post_service.find!(post_id).comments.for_a_stream
end
private
def find_post!
find_post.tap do |post|
raise(ActiveRecord::RecordNotFound) unless post
end
end
attr_reader :user
def find_post
if @user
@user.find_visible_shareable_by_id(Post, @post_id)
else
Post.find_by_id_and_public(@post_id, true)
end
def post_service
@post_service ||= PostService.new(user)
end
end

View file

@ -1,65 +1,66 @@
class PostService
attr_reader :post
def initialize(params)
@id = params[:id]
@user = params[:user]
@oembed = params[:oembed] || {}
assign_post
def initialize(user=nil)
@user = user
end
def assign_post
def find(id)
if user
@post = Post.find_non_public_by_guid_or_id_with_user(id, user)
user.find_visible_shareable_by_id(Post, id)
else
@post = Post.find_public(id)
Post.find_by_id_and_public(id, true)
end
end
def present_json
PostPresenter.new(post, user)
def find!(id_or_guid)
if user
find_non_public_by_guid_or_id_with_user!(id_or_guid)
else
find_public!(id_or_guid)
end
end
def present_interactions_json
PostInteractionPresenter.new(post, user)
def mark_user_notifications(post_id)
return unless user
mark_comment_reshare_like_notifications_read(post_id)
mark_mention_notifications_read(post_id)
end
def present_oembed
OEmbedPresenter.new(post, oembed)
end
def mark_user_notifications
mark_corresponding_notifications_read if user
end
def retract_post
raise Diaspora::NotMine unless user_owns_post?
user.retract(@post)
def destroy(post_id)
post = find!(post_id)
raise Diaspora::NotMine unless post.author == user.person
user.retract(post)
end
private
attr_reader :user, :id, :oembed
attr_reader :user
def user_owns_post?
post.author == user.person
end
def mark_corresponding_notifications_read
mark_comment_reshare_like_notifications_read
mark_mention_notifications_read
end
def mark_comment_reshare_like_notifications_read
notification = Notification.where(recipient_id: user.id, target_type: "Post", target_id: post.id, unread: true)
notification.each do |notification|
notification.set_read_state(true)
def find_public!(id_or_guid)
Post.where(post_key(id_or_guid) => id_or_guid).first.tap do |post|
raise ActiveRecord::RecordNotFound, "could not find a post with id #{id_or_guid}" unless post
raise Diaspora::NonPublic unless post.public?
end
end
def mark_mention_notifications_read
mention = post.mentions.where(person_id: user.person_id).first
Notification.where(recipient_id: user.id, target_type: "Mention", target_id: mention.id, unread: true)
.first.try(:set_read_state, true) if mention
def find_non_public_by_guid_or_id_with_user!(id_or_guid)
user.find_visible_shareable_by_id(Post, id_or_guid, key: post_key(id_or_guid)).tap do |post|
raise ActiveRecord::RecordNotFound, "could not find a post with id #{id_or_guid} for user #{user.id}" unless post
end
end
# We can assume a guid is at least 16 characters long as we have guids set to hex(8) since we started using them.
def post_key(id_or_guid)
id_or_guid.to_s.length < 16 ? :id : :guid
end
def mark_comment_reshare_like_notifications_read(post_id)
Notification.where(recipient_id: user.id, target_type: "Post", target_id: post_id, unread: true)
.update_all(unread: false)
end
def mark_mention_notifications_read(post_id)
mention_id = Mention.where(post_id: post_id, person_id: user.person_id).pluck(:id)
Notification.where(recipient_id: user.id, target_type: "Mention", target_id: mention_id, unread: true)
.update_all(unread: false) if mention_id
end
end

View file

@ -1,89 +1,66 @@
class StatusMessageCreationService
include Rails.application.routes.url_helpers
attr_reader :status_message
def initialize(user)
@user = user
end
def initialize(params, user)
normalize_params(params, user)
status_message_initial = user.build_post(:status_message, params[:status_message])
@status_message = add_attachments(params, status_message_initial)
@status_message.save
process_status_message(user)
def create(params)
build_status_message(params).tap do |status_message|
add_attachments(status_message, params)
status_message.save
process(status_message, params[:aspect_ids], params[:services])
end
end
private
attr_reader :services, :destination_aspect_ids
attr_reader :user
def normalize_params(params, user)
normalize_aspect_ids(params)
normalize_public_flag!(params)
@services = [*params[:services]].compact
@destination_aspect_ids = destination_aspect_ids(params, user)
def build_status_message(params)
public = params[:public] || false
user.build_post(:status_message, params[:status_message].merge(public: public))
end
def normalize_aspect_ids(params)
params[:status_message][:aspect_ids] = [*params[:aspect_ids]]
def add_attachments(status_message, params)
add_location(status_message, params[:location_address], params[:location_coords])
add_poll(status_message, params)
add_photos(status_message, params[:photos])
end
def normalize_public_flag!(params)
sm = params[:status_message]
public_flag_string = (sm[:aspect_ids] && sm[:aspect_ids].first == "public") || sm[:public]
public_flag = public_flag_string.to_s.match(/(true)|(on)/) ? true : false
params[:status_message][:public] = public_flag
end
def destination_aspect_ids(params, user)
if params[:status_message][:aspect_ids].first == "all_aspects"
user.aspect_ids
elsif !params[:status_message][:public]
params[:aspect_ids]
end
end
def add_attachments(params, status_message_initial)
status_message_with_location = add_location(params, status_message_initial)
status_message_with_poll = add_poll(params, status_message_with_location)
add_photos(params, status_message_with_poll)
end
def add_location(params, status_message)
address = params[:location_address]
coordinates = params[:location_coords]
def add_location(status_message, address, coordinates)
status_message.build_location(address: address, coordinates: coordinates) if address.present?
status_message
end
def add_poll(params, status_message)
def add_poll(status_message, params)
if params[:poll_question].present?
status_message.build_poll(question: params[:poll_question])
[*params[:poll_answers]].each do |poll_answer|
status_message.poll.poll_answers.build(answer: poll_answer)
end
end
status_message
end
def add_photos(params, status_message)
status_message.attach_photos_by_ids(params[:photos])
status_message
def add_photos(status_message, photos)
status_message.attach_photos_by_ids(photos)
status_message.photos.each {|photo| photo.public = status_message.public }
end
def process_status_message(user)
add_status_message_to_streams(user)
dispatch_status_message(user)
user.participate!(@status_message)
def process(status_message, aspect_ids, services)
add_to_streams(status_message, aspect_ids) unless status_message.public
dispatch(status_message, services)
user.participate!(status_message)
end
def add_status_message_to_streams(user)
aspects = user.aspects_from_ids(@destination_aspect_ids)
user.add_to_streams(@status_message, aspects)
def add_to_streams(status_message, aspect_ids)
aspects = user.aspects_from_ids(aspect_ids)
user.add_to_streams(status_message, aspects)
end
def dispatch_status_message(user)
receiving_services = Service.titles(@services)
user.dispatch_post(@status_message,
url: short_post_url(@status_message.guid, host: AppConfig.environment.url),
def dispatch(status_message, services)
receiving_services = services ? Service.titles(services) : []
user.dispatch_post(status_message,
url: short_post_url(status_message.guid, host: AppConfig.environment.url),
service_types: receiving_services)
end
end

View file

@ -1,3 +1,3 @@
.comment-container
%ul.comments
= render partial: "comments/comment", collection: @post.comments.for_a_stream, locals: {post: @post}
= render partial: "comments/comment", collection: comments

View file

@ -1,10 +0,0 @@
-# Copyright (c) 2010-2012, Diaspora Inc. This file is
-# licensed under the Affero General Public License version 3 or later. See
-# the COPYRIGHT file.
.transparent.big-number
404
%h3
= t("error_messages.post_not_public_or_not_exist")
%p
= t("error_messages.login_try_again", login_link: new_user_session_path).html_safe

View file

@ -1,2 +0,0 @@
var target = $("#<%= @post.guid %>")
target.hide('blind', { direction: 'vertical' }, 300, function(){ target.remove() });

View file

@ -3,7 +3,7 @@
-# the COPYRIGHT file.
- content_for :page_title do
= post_page_title @post
= post_page_title post
- content_for :content do
#container.container-fluid

View file

@ -3,6 +3,6 @@
-# the COPYRIGHT file.
.stream
= render :partial => 'shared/stream_element',
:locals => {:post => @post, :commenting_disabled => commenting_disabled?(@post), :expanded_info => true}
= render partial: "shared/stream_element",
locals: {post: post, commenting_disabled: commenting_disabled?(post), expanded_info: true}

View file

@ -26,7 +26,7 @@
!= reactions_link(post, "active")
.comment-container
%ul.comments
= render partial: "comments/comment", collection: @post.comments.for_a_stream, locals: {post: @post}
= render partial: "comments/comment", collection: post.comments.for_a_stream, locals: {post: post}
- else
!= reactions_link(post)

View file

@ -91,9 +91,6 @@ en:
helper:
invalid_fields: "Invalid fields"
correct_the_following_errors_and_try_again: "Correct the following errors and try again."
post_not_public: "The post you are trying to view is not public!"
post_not_public_or_not_exist: "The post you are trying to view is not public, or does not exist!"
login_try_again: "Please <a href='%{login_link}'>login</a> and try again."
admins:
admin_bar:

View file

@ -40,10 +40,7 @@ Diaspora::Application.routes.draw do
resources :comments, only: %i(new create destroy index)
end
get 'p/:id' => 'posts#show', :as => 'short_post'
get 'posts/:id/iframe' => 'posts#iframe', :as => 'iframe'
# roll up likes into a nested resource above
resources :comments, :only => [:create, :destroy] do

View file

@ -5,8 +5,6 @@ Then /^I should see the "(.*)" message$/ do |message|
I18n.translate('invitation_codes.excited', :name => @alice.name)
when "welcome to diaspora"
I18n.translate('users.getting_started.well_hello_there')
when 'post not public'
I18n.translate('error_messages.post_not_public_or_not_exist')
else
raise "muriel, you don't have that message key, add one here"
end

View file

@ -140,7 +140,7 @@ describe CommentsController, :type => :controller do
comments = [alice, bob, eve].map{ |u| u.comment!(@message, "hey") }
get :index, :post_id => @message.id, :format => :json
expect(assigns[:comments].map(&:id)).to match_array(comments.map(&:id))
expect(JSON.parse(response.body).map {|comment| comment["id"] }).to match_array(comments.map(&:id))
end
it 'returns a 404 on a nonexistent post' do

View file

@ -5,104 +5,86 @@
require "spec_helper"
describe PostsController, type: :controller do
let!(:post_service_double) { double("post_service") }
before do
aspect = alice.aspects.first
@message = alice.build_post :status_message, text: "ohai", to: aspect.id
@message.save!
alice.add_to_streams(@message, [aspect])
alice.dispatch_post @message, to: aspect.id
allow(PostService).to receive(:new).and_return(post_service_double)
end
let(:post) { alice.post(:status_message, text: "ohai", to: alice.aspects.first) }
describe "#show" do
before do
expect(post_service_double).to receive(:mark_user_notifications)
allow(post_service_double).to receive(:present_json)
end
context "user signed in" do
context "given a post that the user is allowed to see" do
before do
sign_in :user, alice
expect(post_service_double).to receive(:post).and_return(@message)
end
it "succeeds" do
get :show, id: @message.id
expect_any_instance_of(PostService).to receive(:mark_user_notifications).with(post.id)
get :show, id: post.id
expect(response).to be_success
end
it 'succeeds after removing a mention when closing the mentioned user\'s account' do
it "succeeds after removing a mention when closing the mentioned user's account" do
user = FactoryGirl.create(:user, username: "user")
alice.share_with(user.person, alice.aspects.first)
msg = alice.build_post :status_message,
text: "Mention @{User ; #{user.diaspora_handle}}", public: true, to: "all"
msg.save!
msg = alice.post(:status_message, text: "Mention @{User ; #{user.diaspora_handle}}", public: true)
expect(msg.mentioned_people.count).to eq(1)
user.destroy
get :show, id: msg.id
expect(response).to be_success
end
it "renders the application layout on mobile" do
get :show, id: @message.id, format: :mobile
get :show, id: post.id, format: :mobile
expect(response).to render_template("layouts/application")
end
it "succeeds on mobile with a reshare" do
get :show, id: FactoryGirl.create(:reshare, author: alice.person).id, format: :mobile
reshare_id = FactoryGirl.create(:reshare, author: alice.person).id
expect_any_instance_of(PostService).to receive(:mark_user_notifications).with(reshare_id)
get :show, id: reshare_id, format: :mobile
expect(response).to be_success
end
end
context "given a post that the user is not allowed to see" do
before do
sign_in :user, alice
expect(post_service_double).to receive(:post).and_raise(Diaspora::NonPublic)
sign_in :user, eve
end
it "returns a 404" do
get :show, id: @message.id
expect(response.code).to eq("404")
expect {
get :show, id: post.id
}.to raise_error ActiveRecord::RecordNotFound
end
end
end
context "user not signed in" do
context "given a public post" do
before :each do
@status = alice.post(:status_message, text: "hello", public: true, to: "all")
expect(post_service_double).to receive(:post).and_return(@status)
end
let(:public) { alice.post(:status_message, text: "hello", public: true) }
it "shows a public post" do
get :show, id: @status.id
get :show, id: public.id
expect(response.body).to match "hello"
end
it "succeeds for statusnet" do
@request.env["HTTP_ACCEPT"] = "application/html+xml,text/html"
get :show, id: @status.id
get :show, id: public.id
expect(response.body).to match "hello"
end
it "responds with diaspora xml if format is xml" do
get :show, id: @status.guid, format: :xml
expect(response.body).to eq(@status.to_diaspora_xml)
get :show, id: public.guid, format: :xml
expect(response.body).to eq(public.to_diaspora_xml)
end
end
context "given a limited post" do
before do
expect(post_service_double).to receive(:post).and_raise(Diaspora::NonPublic)
end
it "forces the user to sign" do
get :show, id: @message.id
get :show, id: post.id
expect(response).to be_redirect
expect(response).to redirect_to new_user_session_path
end
@ -110,40 +92,55 @@ describe PostsController, type: :controller do
end
end
describe "iframe" do
it "contains an iframe" do
get :iframe, id: @message.id
describe "oembed" do
it "works when you can see it" do
sign_in alice
get :oembed, url: "/posts/#{post.id}"
expect(response.body).to match /iframe/
end
end
describe "oembed" do
it "receives a present oembed" do
expect(post_service_double).to receive(:present_oembed)
get :oembed, url: "/posts/#{@message.id}"
it "returns a 404 response when the post is not found" do
get :oembed, url: "/posts/#{post.id}"
expect(response.status).to eq(404)
end
end
describe "#destroy" do
before do
sign_in alice
context "own post" do
before do
sign_in alice
end
it "works when it is your post" do
expect_any_instance_of(PostService).to receive(:destroy).with(post.id.to_s)
delete :destroy, format: :json, id: post.id
expect(response.status).to eq(204)
end
it "redirects to stream on mobile" do
delete :destroy, format: :mobile, id: post.id
expect(response).to be_redirect
expect(response).to redirect_to stream_path
end
end
it "will receive a retract post" do
expect(post_service_double).to receive(:retract_post)
expect(post_service_double).to receive(:post).and_return(@message)
message = alice.post(:status_message, text: "hey", to: alice.aspects.first.id)
delete :destroy, format: :js, id: message.id
end
context "when Diaspora::NotMine is raised by retract post" do
context "post of another user" do
it "will respond with a 403" do
expect(post_service_double).to receive(:retract_post).and_raise(Diaspora::NotMine)
message = alice.post(:status_message, text: "hey", to: alice.aspects.first.id)
delete :destroy, format: :js, id: message.id
sign_in :user, bob
delete :destroy, format: :json, id: post.id
expect(response.body).to eq("You are not allowed to do that")
expect(response.status).to eq(403)
end
it "will respond with a 404 if the post is not visible" do
sign_in :user, eve
expect {
delete :destroy, format: :json, id: post.id
}.to raise_error ActiveRecord::RecordNotFound
end
end
end
end

View file

@ -49,12 +49,12 @@ describe StatusMessagesController, :type => :controller do
end
describe '#create' do
let(:text) { "facebook, is that you?" }
let(:status_message_hash) {
{ :status_message => {
:public => "true",
:text => "facebook, is that you?",
},
:aspect_ids => [@aspect1.id.to_s] }
{
status_message: {text: text},
aspect_ids: [@aspect1.id.to_s]
}
}
it 'creates with valid html' do
@ -96,14 +96,52 @@ describe StatusMessagesController, :type => :controller do
post :create, status_message_hash
end
it 'takes public in aspect ids' do
post :create, status_message_hash.merge(:aspect_ids => ['public'])
expect(response.status).to eq(302)
end
context "with aspect_ids" do
before do
@aspect2 = alice.aspects.create(name: "another aspect")
end
it 'takes all_aspects in aspect ids' do
post :create, status_message_hash.merge(:aspect_ids => ['all_aspects'])
expect(response.status).to eq(302)
it "takes one aspect as array in aspect_ids" do
post :create, status_message_hash
expect(response.status).to eq(302)
status_message = StatusMessage.find_by_text(text)
expect(status_message.aspect_visibilities.map(&:aspect)).to eq([@aspect1])
end
it "takes one aspect as string in aspect_ids" do
post :create, status_message_hash.merge(aspect_ids: @aspect1.id.to_s)
expect(response.status).to eq(302)
status_message = StatusMessage.find_by_text(text)
expect(status_message.aspect_visibilities.map(&:aspect)).to eq([@aspect1])
end
it "takes public as array in aspect_ids" do
post :create, status_message_hash.merge(aspect_ids: ["public"])
expect(response.status).to eq(302)
status_message = StatusMessage.find_by_text(text)
expect(status_message.public).to be_truthy
end
it "takes public as string in aspect_ids" do
post :create, status_message_hash.merge(aspect_ids: "public")
expect(response.status).to eq(302)
status_message = StatusMessage.find_by_text(text)
expect(status_message.public).to be_truthy
end
it "takes all_aspects as array in aspect_ids" do
post :create, status_message_hash.merge(aspect_ids: ["all_aspects"])
expect(response.status).to eq(302)
status_message = StatusMessage.find_by_text(text)
expect(status_message.aspect_visibilities.map(&:aspect)).to match_array([@aspect1, @aspect2])
end
it "takes all_aspects as string in aspect_ids" do
post :create, status_message_hash.merge(aspect_ids: "all_aspects")
expect(response.status).to eq(302)
status_message = StatusMessage.find_by_text(text)
expect(status_message.aspect_visibilities.map(&:aspect)).to match_array([@aspect1, @aspect2])
end
end
it "dispatches the post to the specified services" do
@ -127,7 +165,7 @@ describe StatusMessagesController, :type => :controller do
it "doesn't overwrite author_id" do
status_message_hash[:status_message][:author_id] = bob.person.id
post :create, status_message_hash
new_message = StatusMessage.find_by_text(status_message_hash[:status_message][:text])
new_message = StatusMessage.find_by_text(text)
expect(new_message.author_id).to eq(alice.person.id)
end
@ -152,15 +190,9 @@ describe StatusMessagesController, :type => :controller do
expect(StatusMessage.first.provider_display_name).to eq('mobile')
end
# disabled to fix federation
# it 'sends the errors in the body on js' do
# post :create, status_message_hash.merge!(:format => 'js', :status_message => {:text => ''})
# response.body.should include('Status message requires a message or at least one photo')
# end
it "has one participation" do
post :create, status_message_hash
new_message = StatusMessage.find_by_text(status_message_hash[:status_message][:text])
new_message = StatusMessage.find_by_text(text)
expect(new_message.participations.count).to eq(1)
expect(new_message.participations.first.count).to eq(1)
end
@ -185,7 +217,8 @@ describe StatusMessagesController, :type => :controller do
it "attaches all referenced photos" do
post :create, @hash
expect(assigns[:status_message].photos.map(&:id)).to match_array([@photo1, @photo2].map(&:id))
status_message = StatusMessage.find_by_text(text)
expect(status_message.photos.map(&:id)).to match_array([@photo1, @photo2].map(&:id))
end
it "sets the pending bit of referenced photos" do

View file

@ -415,56 +415,4 @@ describe Post, :type => :model do
expect(post.interacted_at).not_to be_blank
end
end
describe "#find_public" do
it "succeeds with an id" do
post = FactoryGirl.create :status_message, public: true
expect(Post.find_public post.id).to eq(post)
end
it "succeeds with an guid" do
post = FactoryGirl.create :status_message, public: true
expect(Post.find_public post.guid).to eq(post)
end
it "raises ActiveRecord::RecordNotFound for a non-existing id without a user" do
allow(Post).to receive_messages where: double(includes: double(first: nil))
expect {
Post.find_public 123
}.to raise_error ActiveRecord::RecordNotFound
end
it "raises Diaspora::NonPublic for a private post without a user" do
post = FactoryGirl.create :status_message
expect {
Post.find_public post.id
}.to raise_error Diaspora::NonPublic
end
end
describe "#find_non_public_by_guid_or_id_with_user" do
it "succeeds with an id" do
post = FactoryGirl.create :status_message_in_aspect
expect(Post.find_non_public_by_guid_or_id_with_user(post.id, post.author.owner)).to eq(post)
end
it "succeeds with an guid" do
post = FactoryGirl.create :status_message_in_aspect
expect(Post.find_non_public_by_guid_or_id_with_user(post.guid, post.author.owner)).to eq(post)
end
it "looks up on the passed user object if it's non-nil" do
post = FactoryGirl.create :status_message
user = double
expect(user).to receive(:find_visible_shareable_by_id).with(Post, post.id, key: :id).and_return(post)
Post.find_non_public_by_guid_or_id_with_user(post.id, user)
end
it "raises ActiveRecord::RecordNotFound with a non-existing id and a user" do
user = double(find_visible_shareable_by_id: nil)
expect {
Post.find_non_public_by_guid_or_id_with_user(123, user)
}.to raise_error ActiveRecord::RecordNotFound
end
end
end

View file

@ -0,0 +1,102 @@
require "spec_helper"
describe CommentService do
let(:post) { alice.post(:status_message, text: "hello", to: alice.aspects.first) }
describe "#create" do
it "creates a comment on my own post" do
comment = CommentService.new(alice).create(post.id, "hi")
expect(comment.text).to eq("hi")
end
it "creates a comment on post of a contact" do
comment = CommentService.new(bob).create(post.id, "hi")
expect(comment.text).to eq("hi")
end
it "attaches the comment to the post" do
comment = CommentService.new(alice).create(post.id, "hi")
expect(post.comments.first.text).to eq("hi")
expect(post.comments.first.id).to eq(comment.id)
end
it "fail if the post does not exist" do
expect {
CommentService.new(alice).create("unknown id", "hi")
}.to raise_error ActiveRecord::RecordNotFound
end
it "fail if the user can not see the post" do
expect {
CommentService.new(eve).create("unknown id", "hi")
}.to raise_error ActiveRecord::RecordNotFound
end
end
describe "#destroy" do
let(:comment) { CommentService.new(bob).create(post.id, "hi") }
it "lets the user destroy his own comment" do
result = CommentService.new(bob).destroy(comment.id)
expect(result).to be_truthy
end
it "lets the parent author destroy others comment" do
result = CommentService.new(alice).destroy(comment.id)
expect(result).to be_truthy
end
it "does not let someone destroy others comment" do
result = CommentService.new(eve).destroy(comment.id)
expect(result).to be_falsey
end
it "fails if the comment does not exist" do
expect {
CommentService.new(bob).destroy("unknown id")
}.to raise_error ActiveRecord::RecordNotFound
end
end
describe "#find_for_post" do
context "with user" do
it "returns comments for a public post" do
post = alice.post(:status_message, text: "hello", public: true)
comment = CommentService.new(alice).create(post.id, "hi")
expect(CommentService.new(eve).find_for_post(post.id)).to include(comment)
end
it "returns comments for a visible private post" do
comment = CommentService.new(alice).create(post.id, "hi")
expect(CommentService.new(bob).find_for_post(post.id)).to include(comment)
end
it "does not return comments for private post the user can not see" do
expect {
CommentService.new(eve).find_for_post(post.id)
}.to raise_error ActiveRecord::RecordNotFound
end
end
context "without user" do
it "returns comments for a public post" do
post = alice.post(:status_message, text: "hello", public: true)
comment = CommentService.new(alice).create(post.id, "hi")
expect(CommentService.new.find_for_post(post.id)).to include(comment)
end
it "does not return comments for private post" do
expect {
CommentService.new.find_for_post(post.id)
}.to raise_error Diaspora::NonPublic
end
end
it "returns all comments of a post" do
post = alice.post(:status_message, text: "hello", public: true)
comments = [alice, bob, eve].map {|user| CommentService.new(user).create(post.id, "hi") }
expect(CommentService.new.find_for_post(post.id)).to eq(comments)
end
end
end

View file

@ -1,127 +1,188 @@
require "spec_helper"
describe PostService do
before do
aspect = alice.aspects.first
@message = alice.build_post :status_message, text: "ohai", to: aspect.id
@message.save!
let(:post) { alice.post(:status_message, text: "ohai", to: alice.aspects.first) }
let(:public) { alice.post(:status_message, text: "hey", public: true) }
alice.add_to_streams(@message, [aspect])
alice.dispatch_post @message, to: aspect.id
describe "#find" do
context "with user" do
it "returns the post, if it is the users post" do
expect(PostService.new(alice).find(post.id)).to eq(post)
end
it "returns the post, if the user can see the it" do
expect(PostService.new(bob).find(post.id)).to eq(post)
end
it "returns the post, if it is public" do
expect(PostService.new(eve).find(public.id)).to eq(public)
end
it "does not return the post, if the post cannot be found" do
expect(PostService.new(alice).find("unknown")).to be_nil
end
it "does not return the post, if user cannot see the post" do
expect(PostService.new(eve).find(post.id)).to be_nil
end
end
context "without user" do
it "returns the post, if it is public" do
expect(PostService.new.find(public.id)).to eq(public)
end
it "does not return the post, if the post is private" do
expect(PostService.new.find(post.id)).to be_nil
end
it "does not return the post, if the post cannot be found" do
expect(PostService.new.find("unknown")).to be_nil
end
end
end
describe "#assign_post" do
context "when the post is private" do
describe "#find!" do
context "with user" do
it "returns the post, if it is the users post" do
expect(PostService.new(alice).find!(post.id)).to eq(post)
end
it "works with guid" do
expect(PostService.new(alice).find!(post.guid)).to eq(post)
end
it "returns the post, if the user can see the it" do
expect(PostService.new(bob).find!(post.id)).to eq(post)
end
it "returns the post, if it is public" do
expect(PostService.new(eve).find!(public.id)).to eq(public)
end
it "RecordNotFound if the post cannot be found" do
expect { PostService.new(id: 1_234_567, user: alice) }.to raise_error(ActiveRecord::RecordNotFound)
expect {
PostService.new(alice).find!("unknown")
}.to raise_error ActiveRecord::RecordNotFound, "could not find a post with id unknown for user #{alice.id}"
end
it "NonPublic if there is no user" do
expect { PostService.new(id: @message.id) }.to raise_error(Diaspora::NonPublic)
end
it "RecordNotFound if user cannot see post" do
expect { PostService.new(id: @message.id, user: eve) }.to raise_error(ActiveRecord::RecordNotFound)
it "RecordNotFound if user cannot see the post" do
expect {
PostService.new(eve).find!(post.id)
}.to raise_error ActiveRecord::RecordNotFound, "could not find a post with id #{post.id} for user #{eve.id}"
end
end
context "when the post is public" do
context "without user" do
it "returns the post, if it is public" do
expect(PostService.new.find!(public.id)).to eq(public)
end
it "works with guid" do
expect(PostService.new.find!(public.guid)).to eq(public)
end
it "NonPublic if the post is private" do
expect {
PostService.new.find!(post.id)
}.to raise_error Diaspora::NonPublic
end
it "RecordNotFound if the post cannot be found" do
expect { PostService.new(id: 1_234_567) }.to raise_error(ActiveRecord::RecordNotFound)
expect {
PostService.new.find!("unknown")
}.to raise_error ActiveRecord::RecordNotFound, "could not find a post with id unknown"
end
end
# We want to be using guids from now on for this post route, but do not want to break
# pre-exisiting 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")
let(:public) { alice.post(:status_message, text: "ohai", public: true) }
it "assumes ids less than 16 chars are ids and not guids" do
post = Post.where(id: public.id)
expect(Post).to receive(:where).with(hash_including(id: "123456789012345")).and_return(post).at_least(:once)
PostService.new(alice).find!("123456789012345")
end
it "assumes guids less than 8 chars are ids and not guids" do
post = Post.where(id: @status.id.to_s)
expect(Post).to receive(:where).with(hash_including(id: @status.id)).and_return(post).at_least(:once)
PostService.new(id: @status.id, user: alice)
end
it "assumes guids more than (or equal to) 8 chars are actually guids" do
post = Post.where(guid: @status.guid)
expect(Post).to receive(:where).with(hash_including(guid: @status.guid)).and_return(post).at_least(:once)
PostService.new(id: @status.guid, user: alice)
it "assumes ids more than (or equal to) 16 chars are actually guids" do
post = Post.where(guid: public.guid)
expect(Post).to receive(:where).with(hash_including(guid: "1234567890123456")).and_return(post).at_least(:once)
PostService.new(alice).find!("1234567890123456")
end
end
end
describe "#mark_user_notifications" do
it "marks a corresponding notifications as read" do
FactoryGirl.create(:notification, recipient: alice, target: @message, unread: true)
FactoryGirl.create(:notification, recipient: alice, target: @message, unread: true)
post_service = PostService.new(id: @message.id, user: alice)
expect { post_service.mark_user_notifications }.to change(Notification.where(unread: true), :count).by(-2)
FactoryGirl.create(:notification, recipient: alice, target: post, unread: true)
FactoryGirl.create(:notification, recipient: alice, target: post, unread: true)
expect {
PostService.new(alice).mark_user_notifications(post.id)
}.to change(Notification.where(unread: true), :count).by(-2)
end
it "marks a corresponding mention notification as read" do
status_text = "this is a text mentioning @{Mention User ; #{alice.diaspora_handle}} ... have fun testing!"
status_msg =
bob.post(:status_message, text: status_text, public: true, to: "all")
mention = status_msg.mentions.where(person_id: alice.person.id).first
FactoryGirl.create(:notification, recipient: alice, target_type: "Mention", target_id: mention.id, unread: true)
post_service = PostService.new(id: status_msg.id, user: alice)
expect { post_service.mark_user_notifications }.to change(Notification.where(unread: true), :count).by(-1)
mention_post = bob.post(:status_message, text: status_text, public: true)
expect {
PostService.new(alice).mark_user_notifications(mention_post.id)
}.to change(Notification.where(unread: true), :count).by(-1)
end
it "does not change the update_at date/time for post notifications" do
notification = Timecop.travel(1.minute.ago) do
FactoryGirl.create(:notification, recipient: alice, target: post, unread: true)
end
expect {
PostService.new(alice).mark_user_notifications(post.id)
}.not_to change { Notification.where(id: notification.id).pluck(:updated_at) }
end
it "does not change the update_at date/time for mention notifications" do
status_text = "this is a text mentioning @{Mention User ; #{alice.diaspora_handle}} ... have fun testing!"
mention_post = Timecop.travel(1.minute.ago) do
bob.post(:status_message, text: status_text, public: true)
end
mention = mention_post.mentions.where(person_id: alice.person.id).first
expect {
PostService.new(alice).mark_user_notifications(post.id)
}.not_to change { Notification.where(target_type: "Mention", target_id: mention.id).pluck(:updated_at) }
end
it "does nothing without a user" do
expect_any_instance_of(PostService).not_to receive(:mark_comment_reshare_like_notifications_read).with(post.id)
expect_any_instance_of(PostService).not_to receive(:mark_mention_notifications_read).with(post.id)
PostService.new.mark_user_notifications(post.id)
end
end
describe "#present_json" do
it "works for a private post" do
post_service = PostService.new(id: @message.id, user: alice)
expect(post_service.present_json.to_json).to match(/\"text\"\:\"ohai\"/)
end
it "works for a public post " do
status = alice.post(:status_message, text: "hello", public: true, to: "all")
post_service = PostService.new(id: status.id)
expect(post_service.present_json.to_json).to match(/\"text\"\:\"hello\"/)
end
end
describe "#present_oembed" do
it "works for a private post" do
post_service = PostService.new(id: @message.id, user: alice)
expect(post_service.present_oembed.to_json).to match(/iframe/)
end
it "works for a public post" do
status = alice.post(:status_message, text: "hello", public: true, to: "all")
post_service = PostService.new(id: status.id)
expect(post_service.present_oembed.to_json).to match(/iframe/)
end
end
describe "#retract_post" do
describe "#destroy" do
it "let a user delete his message" do
message = alice.post(:status_message, text: "hey", to: alice.aspects.first.id)
post_service = PostService.new(id: message.id, user: alice)
post_service.retract_post
expect(StatusMessage.find_by_id(message.id)).to be_nil
PostService.new(alice).destroy(post.id)
expect(StatusMessage.find_by_id(post.id)).to be_nil
end
it "sends a retraction on delete" do
message = alice.post(:status_message, text: "hey", to: alice.aspects.first.id)
post_service = PostService.new(id: message.id, user: alice)
expect(alice).to receive(:retract).with(message)
post_service.retract_post
expect(alice).to receive(:retract).with(post)
PostService.new(alice).destroy(post.id)
end
it "will not let you destroy posts visible to you but that you do not own" do
message = bob.post(:status_message, text: "hey", to: bob.aspects.first.id)
post_service = PostService.new(id: message.id, user: alice)
expect { post_service.retract_post }.to raise_error(Diaspora::NotMine)
expect(StatusMessage.exists?(message.id)).to be true
expect {
PostService.new(bob).destroy(post.id)
}.to raise_error Diaspora::NotMine
expect(StatusMessage.find_by_id(post.id)).not_to be_nil
end
it "will not let you destroy posts that are not visible to you" do
message = eve.post(:status_message, text: "hey", to: eve.aspects.first.id)
expect { PostService.new(id: message.id, user: alice) }.to raise_error(ActiveRecord::RecordNotFound)
expect(StatusMessage.exists?(message.id)).to be true
expect {
PostService.new(eve).destroy(post.id)
}.to raise_error(ActiveRecord::RecordNotFound)
expect(StatusMessage.find_by_id(post.id)).not_to be_nil
end
end
end

View file

@ -0,0 +1,136 @@
require "spec_helper"
describe StatusMessageCreationService do
describe "#create" do
let(:aspect) { alice.aspects.first }
let(:text) { "I'm writing tests" }
let(:params) {
{
status_message: {text: text},
aspect_ids: [aspect.id.to_s]
}
}
it "returns the created StatusMessage" do
status_message = StatusMessageCreationService.new(alice).create(params)
expect(status_message).to_not be_nil
expect(status_message.text).to eq(text)
end
context "with aspect_ids" do
it "creates aspect_visibilities for the StatusMessages" do
alice.aspects.create(name: "another aspect")
status_message = StatusMessageCreationService.new(alice).create(params)
expect(status_message.aspect_visibilities.map(&:aspect)).to eq([aspect])
end
it "does not create aspect_visibilities if the post is public" do
status_message = StatusMessageCreationService.new(alice).create(params.merge(public: true))
expect(status_message.aspect_visibilities).to be_empty
end
end
context "with public" do
it "it creates a private StatusMessage by default" do
status_message = StatusMessageCreationService.new(alice).create(params)
expect(status_message.public).to be_falsey
end
it "it creates a private StatusMessage" do
status_message = StatusMessageCreationService.new(alice).create(params.merge(public: false))
expect(status_message.public).to be_falsey
end
it "it creates a public StatusMessage" do
status_message = StatusMessageCreationService.new(alice).create(params.merge(public: true))
expect(status_message.public).to be_truthy
end
end
context "with location" do
it "it creates a location" do
location_params = {location_address: "somewhere", location_coords: "1,2"}
status_message = StatusMessageCreationService.new(alice).create(params.merge(location_params))
location = status_message.location
expect(location.address).to eq("somewhere")
expect(location.lat).to eq("1")
expect(location.lng).to eq("2")
end
it "does not add a location without location params" do
status_message = StatusMessageCreationService.new(alice).create(params)
expect(status_message.location).to be_nil
end
end
context "with poll" do
it "it creates a poll" do
poll_params = {poll_question: "something?", poll_answers: %w(yes no maybe)}
status_message = StatusMessageCreationService.new(alice).create(params.merge(poll_params))
poll = status_message.poll
expect(poll.question).to eq("something?")
expect(poll.poll_answers.size).to eq(3)
poll_answers = poll.poll_answers.map(&:answer)
expect(poll_answers).to include("yes")
expect(poll_answers).to include("no")
expect(poll_answers).to include("maybe")
end
it "does not add a poll without poll params" do
status_message = StatusMessageCreationService.new(alice).create(params)
expect(status_message.poll).to be_nil
end
end
context "with photos" do
let(:photo1) {
alice.build_post(:photo, pending: true, user_file: File.open(photo_fixture_name), to: aspect.id).tap(&:save!)
}
let(:photo2) {
alice.build_post(:photo, pending: true, user_file: File.open(photo_fixture_name), to: aspect.id).tap(&:save!)
}
let(:photo_ids) { [photo1.id.to_s, photo2.id.to_s] }
it "it attaches all photos" do
status_message = StatusMessageCreationService.new(alice).create(params.merge(photos: photo_ids))
photos = status_message.photos
expect(photos.size).to eq(2)
expect(photos.map(&:id).map(&:to_s)).to eq(photo_ids)
end
it "it marks the photos as non-public if the post is non-public" do
status_message = StatusMessageCreationService.new(alice).create(params.merge(photos: photo_ids, public: false))
status_message.photos.each do |photo|
expect(photo.public).to be_falsey
end
end
it "it marks the photos as public if the post is public" do
status_message = StatusMessageCreationService.new(alice).create(params.merge(photos: photo_ids, public: true))
status_message.photos.each do |photo|
expect(photo.public).to be_truthy
end
end
it "does not attach photos without photos param" do
status_message = StatusMessageCreationService.new(alice).create(params)
expect(status_message.photos).to be_empty
end
end
context "dispatch" do
it "dispatches the StatusMessage" do
expect(alice).to receive(:dispatch_post).with(instance_of(StatusMessage), hash_including(service_types: []))
StatusMessageCreationService.new(alice).create(params)
end
it "dispatches the StatusMessage to services" do
expect(alice).to receive(:dispatch_post)
.with(instance_of(StatusMessage),
hash_including(service_types: array_including(%w(Services::Facebook Services::Twitter))))
StatusMessageCreationService.new(alice).create(params.merge(services: %w(twitter facebook)))
end
end
end
end