diaspora/spec/integration/api/posts_controller_spec.rb

695 lines
23 KiB
Ruby

# frozen_string_literal: true
require "spec_helper"
describe Api::V1::PostsController do
let!(:auth_with_read) { FactoryGirl.create(:auth_with_read) }
let!(:access_token_with_read) { auth_with_read.create_access_token.to_s }
let(:auth_with_read_and_write) {
FactoryGirl.create(:auth_with_read_and_write)
}
let!(:access_token_with_read_and_write) {
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
@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
# 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}",
public: true,
to: "all"
)
get(
api_v1_post_path(@status.id),
params: {access_token: access_token_with_read}
)
expect(response.status).to eq(200)
post = response_body(response)
expect(post["post_type"]).to eq("StatusMessage")
expect(post["public"]).to eq(true)
expect(post["author"]["id"]).to eq(auth_with_read.user.person.id)
expect(post["interactions"]["comments_count"]).to eq(0)
mention_ids = Mention.where(
mentions_container_id: @status.id,
mentions_container_type: "Post",
person_id: bob.person.id
).ids
Notification.where(
recipient_id: bob.person.id,
target_type: "Mention",
target_id: mention_ids,
unread: true
)
# expect(notifications.length).to eq(0)
end
end
context "when mark notifications is false" 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}",
public: true,
to: "all"
)
get(
api_v1_post_path(@status.id),
params: {
access_token: access_token_with_read,
mark_notifications: "false"
}
)
expect(response.status).to eq(200)
post = response_body(response)
expect(post["post_type"]).to eq("StatusMessage")
expect(post["public"]).to eq(true)
expect(post["author"]["id"]).to eq(auth_with_read.user.person.id)
expect(post["interactions"]["comments_count"]).to eq(0)
mention_ids = Mention.where(
mentions_container_id: @status.id,
mentions_container_type: "Post",
person_id: bob.person.id
).ids
Notification.where(
recipient_id: bob.person.id,
target_type: "Mention",
target_id: mention_ids,
unread: true
)
# 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,
body: "Hello this is a public post!",
public: 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,
body: "Hello this is a private post!",
public: false,
aspects: [aspect[:id]]
}
)
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
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!"},
public: true
}
)
expect(response.status).to eq(403)
end
end
end
describe "#destroy" do
context "when given read-write access token" do
it "attempts to destroy the post" do
@status = auth_with_read_and_write.user.post(
:status_message,
text: "hello",
public: true,
to: "all"
)
delete(
api_v1_post_path(@status.id),
params: {access_token: access_token_with_read_and_write}
)
expect(response.status).to eq(204)
end
end
context "when given read only access token" do
it "doesn't delete the post" do
@status = auth_with_read.user.post(
:status_message,
text: "hello",
public: true
)
delete(
api_v1_post_path(@status.id),
params: {access_token: access_token_with_read}
)
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