# 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