From bb2261b47dabb3d03f489d53a04235e72710fbc0 Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Thu, 18 Oct 2018 22:41:31 -0400 Subject: [PATCH] Posts API Endpoint feature complete with full unit tests --- app/controllers/api/v1/posts_controller.rb | 77 ++- app/presenters/photo_presenter.rb | 27 +- app/presenters/poll_presenter.rb | 32 + app/presenters/post_interaction_presenter.rb | 6 +- app/presenters/post_presenter.rb | 10 +- config/locales/diaspora/en.yml | 2 + spec/integration/api/posts_controller_spec.rb | 565 +++++++++++++++++- spec/presenters/poll_presenter_spec.rb | 40 ++ 8 files changed, 695 insertions(+), 64 deletions(-) create mode 100644 app/presenters/poll_presenter.rb create mode 100644 spec/presenters/poll_presenter_spec.rb diff --git a/app/controllers/api/v1/posts_controller.rb b/app/controllers/api/v1/posts_controller.rb index 9ca4f6616..673d78b96 100644 --- a/app/controllers/api/v1/posts_controller.rb +++ b/app/controllers/api/v1/posts_controller.rb @@ -13,6 +13,10 @@ module Api require_access_token %w[read write] end + rescue_from ActiveRecord::RecordNotFound do + render json: I18n.t("api.endpoint_errors.posts.post_not_found"), status: :not_found + end + def show mark_notifications = params[:mark_notifications].present? && params[:mark_notifications] @@ -23,37 +27,68 @@ module Api def create status_service = StatusMessageCreationService.new(current_user) - @status_message = status_service.create(normalized_params) - render json: PostPresenter.new(@status_message, current_user) + creation_params = normalized_create_params + @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 def destroy post_service.destroy(params[:id]) head :no_content + rescue Diaspora::NotMine + render json: I18n.t("api.endpoint_errors.posts.failed_delete"), status: :forbidden end - def normalized_params - params.permit( - :location_address, - :location_coords, - :poll_question, - status_message: %i[text provider_display_name], - poll_answers: [] - ).to_h.merge( - services: [*params[:services]].compact, - aspect_ids: normalize_aspect_ids, - public: [*params[:aspect_ids]].first == "public", - photos: [*params[:photos]].compact - ) + def normalized_create_params + mapped_parameters = { + status_message: { + text: params.require(:body) + }, + public: params.require(:public), + aspect_ids: normalize_aspect_ids(params.permit(aspects: [])) + } + add_location_params(mapped_parameters) + add_poll_params(mapped_parameters) + add_photo_ids(mapped_parameters) + mapped_parameters end - def normalize_aspect_ids - aspect_ids = [*params[:aspect_ids]] - if aspect_ids.first == "all_aspects" - current_user.aspect_ids - else - aspect_ids + private + + def add_location_params(mapped_parameters) + return unless params.has_key?(:location) + location = params.require(:location) + 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 + mapped_parameters[:poll_question] = question + mapped_parameters[:poll_answers] = answers + end + + def normalize_aspect_ids(aspects) + aspects.empty? ? [] : aspects[:aspects] end def post_service diff --git a/app/presenters/photo_presenter.rb b/app/presenters/photo_presenter.rb index b67168ace..8eb6070d5 100644 --- a/app/presenters/photo_presenter.rb +++ b/app/presenters/photo_presenter.rb @@ -2,16 +2,31 @@ class PhotoPresenter < BasePresenter def base_hash - { id: id, - guid: guid, + { + id: id, + guid: guid, dimensions: { height: height, - width: width + width: width }, - sizes: { - small: url(:thumb_small), + sizes: { + small: url(:thumb_small), 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 diff --git a/app/presenters/poll_presenter.rb b/app/presenters/poll_presenter.rb new file mode 100644 index 000000000..84836238a --- /dev/null +++ b/app/presenters/poll_presenter.rb @@ -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 diff --git a/app/presenters/post_interaction_presenter.rb b/app/presenters/post_interaction_presenter.rb index d783cdaed..e1a80afed 100644 --- a/app/presenters/post_interaction_presenter.rb +++ b/app/presenters/post_interaction_presenter.rb @@ -20,9 +20,9 @@ class PostInteractionPresenter def as_counters { - comments_count: @post.comments_count, - likes_count: @post.likes_count, - reshares_count: @post.reshares_count + comments: @post.comments_count, + likes: @post.likes_count, + reshares: @post.reshares_count } end diff --git a/app/presenters/post_presenter.rb b/app/presenters/post_presenter.rb index a84eae14e..2afd5ef1b 100644 --- a/app/presenters/post_presenter.rb +++ b/app/presenters/post_presenter.rb @@ -26,11 +26,11 @@ class PostPresenter < BasePresenter public: @post.public, created_at: @post.created_at, 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, - interactions: interactions.as_counters, + interaction_counters: interactions.as_counters, location: @post.post_location, - poll: @post.poll, + poll: PollPresenter.new(@post.poll, current_user).as_api_json, mentioned_people: build_mentioned_people_json, photos: build_photos_json, root: root_api_response @@ -113,11 +113,11 @@ class PostPresenter < BasePresenter end def build_mentioned_people_json - @post.mentioned_people.as_api_response(:backbone) + @post.mentioned_people.map {|m| PersonPresenter.new(m).as_api_json } end def build_photos_json - @post.photos.map {|p| p.as_api_response(:backbone) } + @post.photos.map {|p| PhotoPresenter.new(p).as_api_json } end def root diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 3eee898c4..b411d2670 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -962,6 +962,8 @@ en: no_like: "Like doesn’t exist" posts: 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: not_found: "No record found for given id." diff --git a/spec/integration/api/posts_controller_spec.rb b/spec/integration/api/posts_controller_spec.rb index 8bead8c4f..19562d42b 100644 --- a/spec/integration/api/posts_controller_spec.rb +++ b/spec/integration/api/posts_controller_spec.rb @@ -13,14 +13,32 @@ describe Api::V1::PostsController do 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 before do - bob.email = "bob@example.com" - bob.save + @status = alice.post( + :status_message, + text: "hello @{#{bob.diaspora_handle}} and @{#{eve.diaspora_handle}}from Alice!", + public: true, + to: "all" + ) end 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_message, text: "hello @{bob Testing ; bob@example.com}", @@ -54,7 +72,8 @@ describe Api::V1::PostsController do end 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_message, text: "hello @{bob ; bob@example.com}", @@ -91,54 +110,432 @@ describe Api::V1::PostsController do # expect(notifications.length).to eq(1) 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 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 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( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, - status_message: {text: "Hello this is a public post!"}, - aspect_ids: "public" + access_token: access_token_with_read_and_write, + body: "Hello this is a public post!", + public: true } ) - expect( - Post.find_by(text: "Hello this is a public post!").public - ).to eq(true) + 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 "or creates a private post" do 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( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, - status_message: {text: "Hello this is a post!"}, - aspect_ids: [aspect.id] + access_token: access_token_with_read_and_write, + body: "Hello this is a private post!", + public: false, + aspects: [aspect[:id]] } ) - posted_post = Post.find_by(text: "Hello this is a post!") - expect(posted_post.public).to eq(false) + 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 + 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 context "when given read only access token" do - before do + it "doesn't create the post" do post( api_v1_posts_path, params: { access_token: access_token_with_read, status_message: {text: "Hello this is a post!"}, - aspect_ids: "public" + public: true } ) - end - - it "doesn't create the post" do - json_body = JSON.parse(response.body) - expect(json_body["code"]).to eq(403) + expect(response.status).to eq(403) end end end @@ -161,28 +558,138 @@ describe Api::V1::PostsController do end context "when given read only access token" do - before do + it "doesn't delete the post" do @status = auth_with_read.user.post( :status_message, text: "hello", - public: true, - to: "all" + public: true ) delete( api_v1_post_path(@status.id), 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) 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 def response_body(response) JSON.parse(response.body) 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 diff --git a/spec/presenters/poll_presenter_spec.rb b/spec/presenters/poll_presenter_spec.rb new file mode 100644 index 000000000..fee0ba082 --- /dev/null +++ b/spec/presenters/poll_presenter_spec.rb @@ -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