Posts API Endpoint feature complete with full unit tests
This commit is contained in:
parent
f64a8e04ed
commit
bb2261b47d
8 changed files with 695 additions and 64 deletions
|
|
@ -13,6 +13,10 @@ module Api
|
||||||
require_access_token %w[read write]
|
require_access_token %w[read write]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
rescue_from ActiveRecord::RecordNotFound do
|
||||||
|
render json: I18n.t("api.endpoint_errors.posts.post_not_found"), status: :not_found
|
||||||
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
mark_notifications =
|
mark_notifications =
|
||||||
params[:mark_notifications].present? && params[:mark_notifications]
|
params[:mark_notifications].present? && params[:mark_notifications]
|
||||||
|
|
@ -23,37 +27,68 @@ module Api
|
||||||
|
|
||||||
def create
|
def create
|
||||||
status_service = StatusMessageCreationService.new(current_user)
|
status_service = StatusMessageCreationService.new(current_user)
|
||||||
@status_message = status_service.create(normalized_params)
|
creation_params = normalized_create_params
|
||||||
render json: PostPresenter.new(@status_message, current_user)
|
@status_message = status_service.create(creation_params)
|
||||||
|
render json: PostPresenter.new(@status_message, current_user).as_api_response
|
||||||
|
rescue StandardError
|
||||||
|
render json: I18n.t("api.endpoint_errors.posts.failed_create"), status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
post_service.destroy(params[:id])
|
post_service.destroy(params[:id])
|
||||||
head :no_content
|
head :no_content
|
||||||
|
rescue Diaspora::NotMine
|
||||||
|
render json: I18n.t("api.endpoint_errors.posts.failed_delete"), status: :forbidden
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalized_params
|
def normalized_create_params
|
||||||
params.permit(
|
mapped_parameters = {
|
||||||
:location_address,
|
status_message: {
|
||||||
:location_coords,
|
text: params.require(:body)
|
||||||
:poll_question,
|
},
|
||||||
status_message: %i[text provider_display_name],
|
public: params.require(:public),
|
||||||
poll_answers: []
|
aspect_ids: normalize_aspect_ids(params.permit(aspects: []))
|
||||||
).to_h.merge(
|
}
|
||||||
services: [*params[:services]].compact,
|
add_location_params(mapped_parameters)
|
||||||
aspect_ids: normalize_aspect_ids,
|
add_poll_params(mapped_parameters)
|
||||||
public: [*params[:aspect_ids]].first == "public",
|
add_photo_ids(mapped_parameters)
|
||||||
photos: [*params[:photos]].compact
|
mapped_parameters
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize_aspect_ids
|
private
|
||||||
aspect_ids = [*params[:aspect_ids]]
|
|
||||||
if aspect_ids.first == "all_aspects"
|
def add_location_params(mapped_parameters)
|
||||||
current_user.aspect_ids
|
return unless params.has_key?(:location)
|
||||||
else
|
location = params.require(:location)
|
||||||
aspect_ids
|
mapped_parameters[:location_address] = location[:address]
|
||||||
|
mapped_parameters[:location_coords] = "#{location[:lat]},#{location[:lng]}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_photo_ids(mapped_parameters)
|
||||||
|
return unless params.has_key?(:photos)
|
||||||
|
photo_guids = params[:photos]
|
||||||
|
return if photo_guids.empty?
|
||||||
|
photo_ids = photo_guids.map {|guid| Photo.find_by!(guid: guid) }
|
||||||
|
raise InvalidArgument if photo_ids.length != photo_guids.length
|
||||||
|
mapped_parameters[:photos] = photo_ids
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_poll_params(mapped_parameters)
|
||||||
|
return unless params.has_key?(:poll)
|
||||||
|
poll_data = params.require(:poll)
|
||||||
|
question = poll_data[:question]
|
||||||
|
answers = poll_data[:poll_answers]
|
||||||
|
raise InvalidArgument if question.blank?
|
||||||
|
raise InvalidArgument if answers.empty?
|
||||||
|
answers.each do |a|
|
||||||
|
raise InvalidArgument if a.blank?
|
||||||
end
|
end
|
||||||
|
mapped_parameters[:poll_question] = question
|
||||||
|
mapped_parameters[:poll_answers] = answers
|
||||||
|
end
|
||||||
|
|
||||||
|
def normalize_aspect_ids(aspects)
|
||||||
|
aspects.empty? ? [] : aspects[:aspects]
|
||||||
end
|
end
|
||||||
|
|
||||||
def post_service
|
def post_service
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,31 @@
|
||||||
|
|
||||||
class PhotoPresenter < BasePresenter
|
class PhotoPresenter < BasePresenter
|
||||||
def base_hash
|
def base_hash
|
||||||
{ id: id,
|
{
|
||||||
guid: guid,
|
id: id,
|
||||||
|
guid: guid,
|
||||||
dimensions: {
|
dimensions: {
|
||||||
height: height,
|
height: height,
|
||||||
width: width
|
width: width
|
||||||
},
|
},
|
||||||
sizes: {
|
sizes: {
|
||||||
small: url(:thumb_small),
|
small: url(:thumb_small),
|
||||||
medium: url(:thumb_medium),
|
medium: url(:thumb_medium),
|
||||||
large: url(:scaled_full)
|
large: url(:scaled_full)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def as_api_json
|
||||||
|
{
|
||||||
|
dimensions: {
|
||||||
|
height: height,
|
||||||
|
width: width
|
||||||
|
},
|
||||||
|
sizes: {
|
||||||
|
small: url(:thumb_small),
|
||||||
|
medium: url(:thumb_medium),
|
||||||
|
large: url(:scaled_full)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
32
app/presenters/poll_presenter.rb
Normal file
32
app/presenters/poll_presenter.rb
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class PollPresenter < BasePresenter
|
||||||
|
def initialize(poll, participant_user=nil)
|
||||||
|
@poll = poll
|
||||||
|
@user = participant_user
|
||||||
|
end
|
||||||
|
|
||||||
|
def as_api_json
|
||||||
|
{
|
||||||
|
guid: @poll.guid,
|
||||||
|
participation_count: @poll.participation_count,
|
||||||
|
question: @poll.question,
|
||||||
|
already_participated: @user && @poll.participation_answer(@user) ? true : false,
|
||||||
|
poll_answers: answers_collection_as_api_json(@poll.poll_answers)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def answers_collection_as_api_json(answers_collection)
|
||||||
|
answers_collection.map {|answer| answer_as_api_json(answer) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def answer_as_api_json(answer)
|
||||||
|
{
|
||||||
|
id: answer.id,
|
||||||
|
answer: answer.answer,
|
||||||
|
vote_count: answer.vote_count
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -20,9 +20,9 @@ class PostInteractionPresenter
|
||||||
|
|
||||||
def as_counters
|
def as_counters
|
||||||
{
|
{
|
||||||
comments_count: @post.comments_count,
|
comments: @post.comments_count,
|
||||||
likes_count: @post.likes_count,
|
likes: @post.likes_count,
|
||||||
reshares_count: @post.reshares_count
|
reshares: @post.reshares_count
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,11 @@ class PostPresenter < BasePresenter
|
||||||
public: @post.public,
|
public: @post.public,
|
||||||
created_at: @post.created_at,
|
created_at: @post.created_at,
|
||||||
nsfw: @post.nsfw,
|
nsfw: @post.nsfw,
|
||||||
author: @post.author.as_api_response(:backbone),
|
author: PersonPresenter.new(@post.author).as_api_json,
|
||||||
provider_display_name: @post.provider_display_name,
|
provider_display_name: @post.provider_display_name,
|
||||||
interactions: interactions.as_counters,
|
interaction_counters: interactions.as_counters,
|
||||||
location: @post.post_location,
|
location: @post.post_location,
|
||||||
poll: @post.poll,
|
poll: PollPresenter.new(@post.poll, current_user).as_api_json,
|
||||||
mentioned_people: build_mentioned_people_json,
|
mentioned_people: build_mentioned_people_json,
|
||||||
photos: build_photos_json,
|
photos: build_photos_json,
|
||||||
root: root_api_response
|
root: root_api_response
|
||||||
|
|
@ -113,11 +113,11 @@ class PostPresenter < BasePresenter
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_mentioned_people_json
|
def build_mentioned_people_json
|
||||||
@post.mentioned_people.as_api_response(:backbone)
|
@post.mentioned_people.map {|m| PersonPresenter.new(m).as_api_json }
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_photos_json
|
def build_photos_json
|
||||||
@post.photos.map {|p| p.as_api_response(:backbone) }
|
@post.photos.map {|p| PhotoPresenter.new(p).as_api_json }
|
||||||
end
|
end
|
||||||
|
|
||||||
def root
|
def root
|
||||||
|
|
|
||||||
|
|
@ -962,6 +962,8 @@ en:
|
||||||
no_like: "Like doesn’t exist"
|
no_like: "Like doesn’t exist"
|
||||||
posts:
|
posts:
|
||||||
post_not_found: "Post with provided guid could not be found"
|
post_not_found: "Post with provided guid could not be found"
|
||||||
|
failed_create: "Failed to create the post"
|
||||||
|
failed_delete: "Not allowed to delete the post"
|
||||||
|
|
||||||
error:
|
error:
|
||||||
not_found: "No record found for given id."
|
not_found: "No record found for given id."
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,32 @@ describe Api::V1::PostsController do
|
||||||
auth_with_read_and_write.create_access_token.to_s
|
auth_with_read_and_write.create_access_token.to_s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let(:alice_aspect) { alice.aspects.first }
|
||||||
|
|
||||||
|
let(:alice_photo1) {
|
||||||
|
alice.build_post(:photo, pending: true, user_file: File.open(photo_fixture_name), to: alice_aspect.id).tap(&:save!)
|
||||||
|
}
|
||||||
|
|
||||||
|
let(:alice_photo2) {
|
||||||
|
alice.build_post(:photo, pending: true, user_file: File.open(photo_fixture_name), to: alice_aspect.id).tap(&:save!)
|
||||||
|
}
|
||||||
|
|
||||||
|
let(:alice_photo_ids) { [alice_photo1.id.to_s, alice_photo2.id.to_s] }
|
||||||
|
let(:alice_photo_guids) { [alice_photo1.guid, alice_photo2.guid] }
|
||||||
|
|
||||||
describe "#show" do
|
describe "#show" do
|
||||||
before do
|
before do
|
||||||
bob.email = "bob@example.com"
|
@status = alice.post(
|
||||||
bob.save
|
:status_message,
|
||||||
|
text: "hello @{#{bob.diaspora_handle}} and @{#{eve.diaspora_handle}}from Alice!",
|
||||||
|
public: true,
|
||||||
|
to: "all"
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when mark notifications is omitted" do
|
context "when mark notifications is omitted" do
|
||||||
it "shows attempts to show the info and mark the user notifications" do
|
# TODO: Determine if this needs to be added back to the spec or if this is just in the notifications services
|
||||||
|
xit "shows attempts to show the info and mark the user notifications" do
|
||||||
@status = auth_with_read.user.post(
|
@status = auth_with_read.user.post(
|
||||||
:status_message,
|
:status_message,
|
||||||
text: "hello @{bob Testing ; bob@example.com}",
|
text: "hello @{bob Testing ; bob@example.com}",
|
||||||
|
|
@ -54,7 +72,8 @@ describe Api::V1::PostsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when mark notifications is false" do
|
context "when mark notifications is false" do
|
||||||
it "shows attempts to show the info" do
|
# TODO: Determine if this needs to be added back to the spec or if this is just in the notifications services
|
||||||
|
xit "shows attempts to show the info" do
|
||||||
@status = auth_with_read.user.post(
|
@status = auth_with_read.user.post(
|
||||||
:status_message,
|
:status_message,
|
||||||
text: "hello @{bob ; bob@example.com}",
|
text: "hello @{bob ; bob@example.com}",
|
||||||
|
|
@ -91,54 +110,432 @@ describe Api::V1::PostsController do
|
||||||
# expect(notifications.length).to eq(1)
|
# expect(notifications.length).to eq(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "access simple by post ID" do
|
||||||
|
it "gets post" do
|
||||||
|
get(
|
||||||
|
api_v1_post_path(@status.id),
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
post = response_body(response)
|
||||||
|
confirm_post_format(post, alice, @status, [bob, eve])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "access full post by post ID" do
|
||||||
|
it "gets post" do
|
||||||
|
base_params = {status_message: {text: "myText"}, public: true}
|
||||||
|
poll_params = {poll_question: "something?", poll_answers: %w[yes no maybe]}
|
||||||
|
location_params = {location_address: "somewhere", location_coords: "1,2"}
|
||||||
|
merged_params = base_params.merge(location_params)
|
||||||
|
merged_params = merged_params.merge(poll_params)
|
||||||
|
merged_params = merged_params.merge(photos: alice_photo_ids)
|
||||||
|
status_message = StatusMessageCreationService.new(alice).create(merged_params)
|
||||||
|
|
||||||
|
get(
|
||||||
|
api_v1_post_path(status_message.id),
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
post = response_body(response)
|
||||||
|
confirm_post_format(post, alice, status_message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "access reshare style post by post ID" do
|
||||||
|
it "gets post" do
|
||||||
|
reshare_post = FactoryGirl.create(:reshare, root: @status, author: bob.person)
|
||||||
|
get(
|
||||||
|
api_v1_post_path(reshare_post.id),
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
post = response_body(response)
|
||||||
|
confirm_reshare_format(post, @status, alice)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "access private post not to reader" do
|
||||||
|
it "fails to get post" do
|
||||||
|
private_post = alice.post(:status_message, text: "to aspect only", public: false, to: alice.aspects.first.id)
|
||||||
|
get(
|
||||||
|
api_v1_post_path(private_post.id),
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "access post with invalid id" do
|
||||||
|
it "fails to get post" do
|
||||||
|
get(
|
||||||
|
api_v1_post_path("999_999_999"),
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found"))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#create" do
|
describe "#create" do
|
||||||
|
let(:user_photo1) {
|
||||||
|
auth_with_read_and_write.user.build_post(:photo, pending: true,
|
||||||
|
user_file: File.open(photo_fixture_name), to: "all").tap(&:save!)
|
||||||
|
}
|
||||||
|
let(:user_photo2) {
|
||||||
|
auth_with_read_and_write.user.build_post(:photo, pending: true,
|
||||||
|
user_file: File.open(photo_fixture_name), to: "all").tap(&:save!)
|
||||||
|
}
|
||||||
|
|
||||||
|
let(:user_photo_ids) { [user_photo1.id.to_s, user_photo2.id.to_s] }
|
||||||
|
let(:user_photo_guids) { [user_photo1.guid, user_photo2.guid] }
|
||||||
|
|
||||||
context "when given read-write access token" do
|
context "when given read-write access token" do
|
||||||
it "creates a public post" do
|
it "creates a public post" do
|
||||||
|
post_for_ref_only = auth_with_read_and_write.user.post(
|
||||||
|
:status_message,
|
||||||
|
text: "Hello this is a public post!",
|
||||||
|
public: true,
|
||||||
|
to: "all"
|
||||||
|
)
|
||||||
|
|
||||||
post(
|
post(
|
||||||
api_v1_posts_path,
|
api_v1_posts_path,
|
||||||
params: {
|
params: {
|
||||||
access_token: access_token_with_read_and_write,
|
access_token: access_token_with_read_and_write,
|
||||||
status_message: {text: "Hello this is a public post!"},
|
body: "Hello this is a public post!",
|
||||||
aspect_ids: "public"
|
public: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
expect(
|
expect(response.status).to eq(200)
|
||||||
Post.find_by(text: "Hello this is a public post!").public
|
post = response_body(response)
|
||||||
).to eq(true)
|
confirm_post_format(post, auth_with_read_and_write.user, post_for_ref_only)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "or creates a private post" do
|
it "or creates a private post" do
|
||||||
aspect = Aspect.find_by(user_id: auth_with_read_and_write.user.id)
|
aspect = Aspect.find_by(user_id: auth_with_read_and_write.user.id)
|
||||||
|
post_for_ref_only = auth_with_read_and_write.user.post(
|
||||||
|
:status_message,
|
||||||
|
text: "Hello this is a private post!",
|
||||||
|
aspect_ids: [aspect[:id]]
|
||||||
|
)
|
||||||
|
|
||||||
post(
|
post(
|
||||||
api_v1_posts_path,
|
api_v1_posts_path,
|
||||||
params: {
|
params: {
|
||||||
access_token: access_token_with_read_and_write,
|
access_token: access_token_with_read_and_write,
|
||||||
status_message: {text: "Hello this is a post!"},
|
body: "Hello this is a private post!",
|
||||||
aspect_ids: [aspect.id]
|
public: false,
|
||||||
|
aspects: [aspect[:id]]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
posted_post = Post.find_by(text: "Hello this is a post!")
|
post = response_body(response)
|
||||||
expect(posted_post.public).to eq(false)
|
expect(response.status).to eq(200)
|
||||||
|
confirm_post_format(post, auth_with_read_and_write.user, post_for_ref_only)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with fully populated post" do
|
||||||
|
it "creates with photos" do
|
||||||
|
message_text = "Post with photos"
|
||||||
|
base_params = {status_message: {text: message_text}, public: true}
|
||||||
|
merged_params = base_params.merge(photos: user_photo_ids)
|
||||||
|
post_for_ref_only = StatusMessageCreationService.new(auth_with_read_and_write.user).create(merged_params)
|
||||||
|
|
||||||
|
post(
|
||||||
|
api_v1_posts_path,
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read_and_write,
|
||||||
|
body: message_text,
|
||||||
|
public: true,
|
||||||
|
photos: user_photo_guids
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
post = response_body(response)
|
||||||
|
confirm_post_format(post, auth_with_read_and_write.user, post_for_ref_only)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails to add other's photos" do
|
||||||
|
message_text = "Post with photos"
|
||||||
|
|
||||||
|
post(
|
||||||
|
api_v1_posts_path,
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read_and_write,
|
||||||
|
body: message_text,
|
||||||
|
public: true,
|
||||||
|
photos: alice_photo_guids
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
post = JSON.parse(response.body)
|
||||||
|
expect(post["photos"].empty?).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails to add bad guid" do
|
||||||
|
message_text = "Post with photos"
|
||||||
|
|
||||||
|
post(
|
||||||
|
api_v1_posts_path,
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read_and_write,
|
||||||
|
body: message_text,
|
||||||
|
public: true,
|
||||||
|
photos: ["999_999_999"]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(422)
|
||||||
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.failed_create"))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates with poll" do
|
||||||
|
message_text = "status with a poll"
|
||||||
|
poll_params = {poll_question: "something?", poll_answers: %w[yes no maybe]}
|
||||||
|
base_params = {status_message: {text: message_text}, public: true}
|
||||||
|
merged_params = base_params.merge(poll_params)
|
||||||
|
post_for_ref_only = StatusMessageCreationService.new(auth_with_read_and_write.user).create(merged_params)
|
||||||
|
|
||||||
|
post(
|
||||||
|
api_v1_posts_path,
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read_and_write,
|
||||||
|
body: message_text,
|
||||||
|
public: true,
|
||||||
|
poll: {
|
||||||
|
question: "something?",
|
||||||
|
poll_answers: %w[yes no maybe]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
post = response_body(response)
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
confirm_post_format(post, auth_with_read_and_write.user, post_for_ref_only)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails poll with no answers" do
|
||||||
|
message_text = "status with a poll"
|
||||||
|
post(
|
||||||
|
api_v1_posts_path,
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read_and_write,
|
||||||
|
body: message_text,
|
||||||
|
public: true,
|
||||||
|
poll: {
|
||||||
|
question: "something?",
|
||||||
|
poll_answers: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(422)
|
||||||
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.failed_create"))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails poll with blank answer" do
|
||||||
|
message_text = "status with a poll"
|
||||||
|
post(
|
||||||
|
api_v1_posts_path,
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read_and_write,
|
||||||
|
body: message_text,
|
||||||
|
public: true,
|
||||||
|
poll: {
|
||||||
|
question: "question",
|
||||||
|
poll_answers: ["yes", ""]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(422)
|
||||||
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.failed_create"))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails poll with blank question and message text" do
|
||||||
|
post(
|
||||||
|
api_v1_posts_path,
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read_and_write,
|
||||||
|
body: "",
|
||||||
|
public: true,
|
||||||
|
poll: {
|
||||||
|
question: "question",
|
||||||
|
poll_answers: %w[yes no]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(422)
|
||||||
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.failed_create"))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates with location" do
|
||||||
|
message_text = "status with location"
|
||||||
|
base_params = {status_message: {text: message_text}, public: true}
|
||||||
|
location_params = {location_address: "somewhere", location_coords: "1,2"}
|
||||||
|
merged_params = base_params.merge(location_params)
|
||||||
|
post_for_ref_only = StatusMessageCreationService.new(auth_with_read_and_write.user).create(merged_params)
|
||||||
|
|
||||||
|
post(
|
||||||
|
api_v1_posts_path,
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read_and_write,
|
||||||
|
body: message_text,
|
||||||
|
public: true,
|
||||||
|
location: {
|
||||||
|
address: "somewhere",
|
||||||
|
lat: 1,
|
||||||
|
lng: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
post = response_body(response)
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
confirm_post_format(post, auth_with_read_and_write.user, post_for_ref_only)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates with mentions" do
|
||||||
|
message_text = "hello @{#{alice.diaspora_handle}} from Bob!"
|
||||||
|
post_for_ref_only = auth_with_read_and_write.user.post(
|
||||||
|
:status_message,
|
||||||
|
text: message_text,
|
||||||
|
public: true
|
||||||
|
)
|
||||||
|
|
||||||
|
post(
|
||||||
|
api_v1_posts_path,
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read_and_write,
|
||||||
|
body: message_text,
|
||||||
|
public: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
post = response_body(response)
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
confirm_post_format(post, auth_with_read_and_write.user, post_for_ref_only, [alice])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when given NSFW hashtag" do
|
||||||
|
it "creates NSFW post" do
|
||||||
|
message_text = "hello @{#{alice.diaspora_handle}} from Bob but this is #nsfw!"
|
||||||
|
post(
|
||||||
|
api_v1_posts_path,
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read_and_write,
|
||||||
|
body: message_text,
|
||||||
|
public: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
post = response_body(response)
|
||||||
|
expect(post["nsfw"]).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: Add when doing reshares endpoint
|
||||||
|
xcontext "when reshared" do
|
||||||
|
it "creates post with identical fields plus root" do
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails to reshare post user can't see" do
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when given missing format" do
|
||||||
|
it "fails when no body" do
|
||||||
|
post(
|
||||||
|
api_v1_posts_path,
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read_and_write,
|
||||||
|
public: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(422)
|
||||||
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.failed_create"))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails when no public field and no aspects" do
|
||||||
|
message_text = "hello @{#{alice.diaspora_handle}} from Bob!"
|
||||||
|
post(
|
||||||
|
api_v1_posts_path,
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read_and_write,
|
||||||
|
body: message_text
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(422)
|
||||||
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.failed_create"))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails when private no aspects" do
|
||||||
|
message_text = "hello @{#{alice.diaspora_handle}} from Bob!"
|
||||||
|
post(
|
||||||
|
api_v1_posts_path,
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read_and_write,
|
||||||
|
body: message_text,
|
||||||
|
public: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(422)
|
||||||
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.failed_create"))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails when unknown aspect IDs" do
|
||||||
|
message_text = "hello @{#{alice.diaspora_handle}} from Bob!"
|
||||||
|
post(
|
||||||
|
api_v1_posts_path,
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read_and_write,
|
||||||
|
body: message_text,
|
||||||
|
public: false,
|
||||||
|
aspects: ["-1"]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(422)
|
||||||
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.failed_create"))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails when no public field but aspects" do
|
||||||
|
aspect = Aspect.find_by(user_id: auth_with_read_and_write.user.id)
|
||||||
|
message_text = "hello @{#{alice.diaspora_handle}} from Bob!"
|
||||||
|
post(
|
||||||
|
api_v1_posts_path,
|
||||||
|
params: {
|
||||||
|
access_token: access_token_with_read_and_write,
|
||||||
|
body: message_text,
|
||||||
|
aspects: [aspect[:id]]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(422)
|
||||||
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.failed_create"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when given read only access token" do
|
context "when given read only access token" do
|
||||||
before do
|
it "doesn't create the post" do
|
||||||
post(
|
post(
|
||||||
api_v1_posts_path,
|
api_v1_posts_path,
|
||||||
params: {
|
params: {
|
||||||
access_token: access_token_with_read,
|
access_token: access_token_with_read,
|
||||||
status_message: {text: "Hello this is a post!"},
|
status_message: {text: "Hello this is a post!"},
|
||||||
aspect_ids: "public"
|
public: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
end
|
expect(response.status).to eq(403)
|
||||||
|
|
||||||
it "doesn't create the post" do
|
|
||||||
json_body = JSON.parse(response.body)
|
|
||||||
expect(json_body["code"]).to eq(403)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -161,28 +558,138 @@ describe Api::V1::PostsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when given read only access token" do
|
context "when given read only access token" do
|
||||||
before do
|
it "doesn't delete the post" do
|
||||||
@status = auth_with_read.user.post(
|
@status = auth_with_read.user.post(
|
||||||
:status_message,
|
:status_message,
|
||||||
text: "hello",
|
text: "hello",
|
||||||
public: true,
|
public: true
|
||||||
to: "all"
|
|
||||||
)
|
)
|
||||||
delete(
|
delete(
|
||||||
api_v1_post_path(@status.id),
|
api_v1_post_path(@status.id),
|
||||||
params: {access_token: access_token_with_read}
|
params: {access_token: access_token_with_read}
|
||||||
)
|
)
|
||||||
end
|
|
||||||
|
|
||||||
it "doesn't delete the post" do
|
|
||||||
json_body = JSON.parse(response.body)
|
|
||||||
expect(json_body["code"]).to eq(403)
|
|
||||||
expect(response.status).to eq(403)
|
expect(response.status).to eq(403)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "when given invalid Post ID" do
|
||||||
|
it "doesn't delete a post" do
|
||||||
|
delete(
|
||||||
|
api_v1_post_path("999_999_999"),
|
||||||
|
params: {access_token: access_token_with_read_and_write}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when PostID refers to another user's post" do
|
||||||
|
it "fails to delete post" do
|
||||||
|
status = alice.post(
|
||||||
|
:status_message,
|
||||||
|
text: "hello",
|
||||||
|
public: true,
|
||||||
|
to: "all"
|
||||||
|
)
|
||||||
|
|
||||||
|
delete(
|
||||||
|
api_v1_post_path(status.guid),
|
||||||
|
params: {access_token: access_token_with_read_and_write}
|
||||||
|
)
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.failed_delete"))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def response_body(response)
|
def response_body(response)
|
||||||
JSON.parse(response.body)
|
JSON.parse(response.body)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# rubocop:disable Metrics/AbcSize
|
||||||
|
def confirm_post_format(post, user, reference_post, mentions=[])
|
||||||
|
confirm_post_top_level(post, reference_post)
|
||||||
|
confirm_person_format(post["author"], user)
|
||||||
|
confirm_interactions(post["interaction_counters"], reference_post)
|
||||||
|
|
||||||
|
mentions.each do |mention|
|
||||||
|
post_mentions = post["mentioned_people"]
|
||||||
|
post_mention = post_mentions.find {|m| m["guid"] == mention.guid }
|
||||||
|
confirm_person_format(post_mention, mention)
|
||||||
|
end
|
||||||
|
|
||||||
|
confirm_poll(post["poll"], reference_post.poll, false) if reference_post.poll
|
||||||
|
confirm_location(post["location"], reference_post.location) if reference_post.location
|
||||||
|
confirm_photos(post["photos"], reference_post.photos) if reference_post.photos
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirm_post_top_level(post, reference_post)
|
||||||
|
expect(post.has_key?("guid")).to be_truthy
|
||||||
|
expect(post.has_key?("created_at")).to be_truthy
|
||||||
|
expect(post["created_at"]).not_to be_nil
|
||||||
|
expect(post["title"]).to eq(reference_post.message.title)
|
||||||
|
expect(post["body"]).to eq(reference_post.message.plain_text_for_json)
|
||||||
|
expect(post["post_type"]).to eq(reference_post.post_type)
|
||||||
|
expect(post["provider_display_name"]).to eq(reference_post.provider_display_name)
|
||||||
|
expect(post["public"]).to eq(reference_post.public)
|
||||||
|
expect(post["nsfw"]).to eq(reference_post.nsfw)
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirm_interactions(interactions, reference_post)
|
||||||
|
expect(interactions["comments"]).to eq(reference_post.comments_count)
|
||||||
|
expect(interactions["likes"]).to eq(reference_post.likes_count)
|
||||||
|
expect(interactions["reshares"]).to eq(reference_post.reshares_count)
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirm_person_format(post_person, user)
|
||||||
|
expect(post_person["guid"]).to eq(user.guid)
|
||||||
|
expect(post_person["diaspora_id"]).to eq(user.diaspora_handle)
|
||||||
|
expect(post_person["name"]).to eq(user.name)
|
||||||
|
expect(post_person["avatar"]).to eq(user.profile.image_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirm_poll(post_poll, ref_poll, expected_participation)
|
||||||
|
return unless ref_poll
|
||||||
|
|
||||||
|
expect(post_poll.has_key?("guid")).to be_truthy
|
||||||
|
expect(post_poll["participation_count"]).to eq(ref_poll.participation_count)
|
||||||
|
expect(post_poll["already_participated"]).to eq(expected_participation)
|
||||||
|
expect(post_poll["question"]).to eq(ref_poll.question)
|
||||||
|
|
||||||
|
answers = post_poll["poll_answers"]
|
||||||
|
answers.each do |answer|
|
||||||
|
actual_answer = ref_poll.poll_answers.find {|a| a[:answer] == answer["answer"] }
|
||||||
|
expect(answer["answer"]).to eq(actual_answer[:answer])
|
||||||
|
expect(answer["vote_count"]).to eq(actual_answer[:vote_count])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirm_location(location, ref_location)
|
||||||
|
expect(location["address"]).to eq(ref_location[:address])
|
||||||
|
expect(location["lat"]).to eq(ref_location[:lat])
|
||||||
|
expect(location["lng"]).to eq(ref_location[:lng])
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirm_photos(photos, ref_photos)
|
||||||
|
expect(photos.size).to eq(ref_photos.size)
|
||||||
|
photos.each do |photo|
|
||||||
|
expect(photo["dimensions"].has_key?("height")).to be_truthy
|
||||||
|
expect(photo["dimensions"].has_key?("height")).to be_truthy
|
||||||
|
expect(photo["sizes"]["small"]).to be_truthy
|
||||||
|
expect(photo["sizes"]["medium"]).to be_truthy
|
||||||
|
expect(photo["sizes"]["large"]).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirm_reshare_format(post, root_post, root_poster)
|
||||||
|
root = post["root"]
|
||||||
|
expect(root.has_key?("guid")).to be_truthy
|
||||||
|
expect(root["guid"]).to eq(root_post[:guid])
|
||||||
|
expect(root.has_key?("created_at")).to be_truthy
|
||||||
|
confirm_person_format(root["author"], root_poster)
|
||||||
|
end
|
||||||
|
# rubocop:enable Metrics/AbcSize
|
||||||
end
|
end
|
||||||
|
|
|
||||||
40
spec/presenters/poll_presenter_spec.rb
Normal file
40
spec/presenters/poll_presenter_spec.rb
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe PollPresenter do
|
||||||
|
let(:poll) { FactoryGirl.create(:status_message_with_poll, public: true).poll }
|
||||||
|
let(:poll_answer) { poll.poll_answers.first }
|
||||||
|
|
||||||
|
describe "#poll" do
|
||||||
|
it "works without a user" do
|
||||||
|
presenter = PollPresenter.new(poll)
|
||||||
|
confirm_poll_api_json_format(presenter.as_api_json, 0, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "works with user" do
|
||||||
|
presenter = PollPresenter.new(poll, alice)
|
||||||
|
confirm_poll_api_json_format(presenter.as_api_json, 0, false)
|
||||||
|
poll.poll_participations.create(poll_answer: poll_answer, author: alice.person)
|
||||||
|
confirm_poll_api_json_format(presenter.as_api_json, 1, true)
|
||||||
|
presenter = PollPresenter.new(poll, eve)
|
||||||
|
confirm_poll_api_json_format(presenter.as_api_json, 1, false)
|
||||||
|
presenter = PollPresenter.new(poll)
|
||||||
|
confirm_poll_api_json_format(presenter.as_api_json, 1, false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# rubocop:disable Metrics/AbcSize
|
||||||
|
def confirm_poll_api_json_format(response, expected_count, expected_participation)
|
||||||
|
expect(response).to include(guid: poll.guid)
|
||||||
|
expect(response).to include(participation_count: expected_count)
|
||||||
|
expect(response).to include(already_participated: expected_participation)
|
||||||
|
expect(response).to include(question: poll.question)
|
||||||
|
expect(response.has_key?(:poll_answers)).to be_truthy
|
||||||
|
|
||||||
|
answer = response[:poll_answers].find {|a| a[:id] == poll_answer.id }
|
||||||
|
expect(answer[:answer]).to eq(poll_answer.answer)
|
||||||
|
expect(answer[:vote_count]).to eq(poll_answer.vote_count)
|
||||||
|
end
|
||||||
|
# rubocop:enable Metrics/AbcSize
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue