Posts API Endpoint feature complete with full unit tests

This commit is contained in:
Hank Grabowski 2018-10-18 22:41:31 -04:00 committed by Frank Rousseau
parent f64a8e04ed
commit bb2261b47d
8 changed files with 695 additions and 64 deletions

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -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

View file

@ -962,6 +962,8 @@ en:
no_like: "Like doesnt 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."

View file

@ -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

View 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