# frozen_string_literal: true require 'rails_helper' shared_examples 'finding and showing post' do let!(:post) { post_by_user } it "ensures the user can't see the post" do topic = post.topic topic.convert_to_private_message(Discourse.system_user) topic.remove_allowed_user(Discourse.system_user, user.username) get url expect(response).to be_forbidden end it 'succeeds' do get url expect(response.status).to eq(200) end context "deleted post" do before do post.trash!(user) end it "can't find deleted posts as an anonymous user" do get url expect(response.status).to eq(404) end it "can't find deleted posts as a regular user" do sign_in(user) get url expect(response.status).to eq(404) end it "can find posts as a moderator" do sign_in(moderator) get url expect(response.status).to eq(200) end it "can find posts as a admin" do sign_in(admin) get url expect(response.status).to eq(200) end end end shared_examples 'action requires login' do |method, url, params = {}| it 'raises an exception when not logged in' do self.public_send(method, url, params) expect(response.status).to eq(403) end end describe PostsController do fab!(:admin) { Fabricate(:admin) } fab!(:moderator) { Fabricate(:moderator) } fab!(:user) { Fabricate(:user) } fab!(:category) { Fabricate(:category) } fab!(:topic) { Fabricate(:topic) } fab!(:post_by_user) { Fabricate(:post, user: user) } let(:public_post) { Fabricate(:post, user: user, topic: topic) } let(:topicless_post) { Fabricate(:post, user: user, raw: '
Car 54, where are you?
') } let(:private_topic) do Fabricate(:topic, archetype: Archetype.private_message, category_id: nil) end let(:private_post) { Fabricate(:post, user: user, topic: private_topic) } describe '#show' do include_examples 'finding and showing post' do let(:url) { "/posts/#{post.id}.json" } end it 'gets all the expected fields' do # non fabricated test new_post = create_post get "/posts/#{new_post.id}.json" parsed = JSON.parse(response.body) expect(parsed["topic_slug"]).to eq(new_post.topic.slug) expect(parsed["moderator"]).to eq(false) expect(parsed["username"]).to eq(new_post.user.username) expect(parsed["cooked"]).to eq(new_post.cooked) end end describe '#by_number' do include_examples 'finding and showing post' do let(:url) { "/posts/by_number/#{post.topic_id}/#{post.post_number}.json" } end end describe '#by_date' do include_examples 'finding and showing post' do let(:url) { "/posts/by-date/#{post.topic_id}/#{post.created_at.strftime("%Y-%m-%d")}.json" } end it 'returns the expected post' do first_post = Fabricate(:post, created_at: 10.days.ago) second_post = Fabricate(:post, topic: first_post.topic, created_at: 4.days.ago) _third_post = Fabricate(:post, topic: first_post.topic, created_at: 3.days.ago) get "/posts/by-date/#{second_post.topic_id}/#{(second_post.created_at - 2.days).strftime("%Y-%m-%d")}.json" json = JSON.parse(response.body) expect(response.status).to eq(200) expect(json["id"]).to eq(second_post.id) end it 'returns no post if date is > at last created post' do get "/posts/by-date/#{post.topic_id}/2245-11-11.json" _json = JSON.parse(response.body) expect(response.status).to eq(404) end end describe '#reply_history' do include_examples 'finding and showing post' do let(:url) { "/posts/#{post.id}/reply-history.json" } end it "returns the replies with whitelisted user custom fields" do parent = Fabricate(:post) child = Fabricate(:post, topic: parent.topic, reply_to_post_number: parent.post_number) parent.user.upsert_custom_fields(hello: 'world', hidden: 'dontshow') SiteSetting.public_user_custom_fields = 'hello' get "/posts/#{child.id}/reply-history.json" expect(response.status).to eq(200) json = JSON.parse(response.body) expect(json[0]['id']).to eq(parent.id) expect(json[0]['user_custom_fields']['hello']).to eq('world') expect(json[0]['user_custom_fields']['hidden']).to be_blank end end describe '#replies' do include_examples 'finding and showing post' do let(:url) { "/posts/#{post.id}/replies.json" } end it 'asks post for replies' do parent = Fabricate(:post) child = Fabricate(:post, topic: parent.topic, reply_to_post_number: parent.post_number) PostReply.create!(post: parent, reply: child) child.user.upsert_custom_fields(hello: 'world', hidden: 'dontshow') SiteSetting.public_user_custom_fields = 'hello' get "/posts/#{parent.id}/replies.json" expect(response.status).to eq(200) json = JSON.parse(response.body) expect(json[0]['id']).to eq(child.id) expect(json[0]['user_custom_fields']['hello']).to eq('world') expect(json[0]['user_custom_fields']['hidden']).to be_blank end end describe '#destroy' do include_examples 'action requires login', :delete, "/posts/123.json" describe 'when logged in' do let(:topic) { Fabricate(:topic) } it "raises an error when the user doesn't have permission to see the post" do pm = Fabricate(:private_message_topic) post = Fabricate(:post, topic: pm, post_number: 3) sign_in(user) delete "/posts/#{post.id}.json" expect(response).to be_forbidden end it "uses a PostDestroyer" do post = Fabricate(:post, topic_id: topic.id, post_number: 3) sign_in(moderator) destroyer = mock PostDestroyer.expects(:new).returns(destroyer) destroyer.expects(:destroy) delete "/posts/#{post.id}.json" end end end describe '#destroy_many' do include_examples 'action requires login', :delete, "/posts/destroy_many.json", params: { post_ids: [123, 345] } describe 'when logged in' do fab!(:poster) { Fabricate(:moderator) } fab!(:post1) { Fabricate(:post, user: poster, post_number: 2) } fab!(:post2) { Fabricate(:post, topic: post1.topic, user: poster, post_number: 3, reply_to_post_number: post1.post_number) } it "raises invalid parameters no post_ids" do sign_in(poster) delete "/posts/destroy_many.json" expect(response.status).to eq(400) expect(response.message.downcase).to eq("bad request") end it "raises invalid parameters with missing ids" do sign_in(poster) delete "/posts/destroy_many.json", params: { post_ids: [12345] } expect(response.status).to eq(400) end it "raises an error when the user doesn't have permission to delete the posts" do sign_in(user) delete "/posts/destroy_many.json", params: { post_ids: [post1.id, post2.id] } expect(response).to be_forbidden end it "deletes the post" do sign_in(poster) PostDestroyer.any_instance.expects(:destroy).twice delete "/posts/destroy_many.json", params: { post_ids: [post1.id, post2.id] } expect(response.status).to eq(200) end it "updates the highest read data for the forum" do sign_in(poster) Topic.expects(:reset_highest).twice delete "/posts/destroy_many.json", params: { post_ids: [post1.id, post2.id] } end describe "can delete replies" do before do PostReply.create(post_id: post1.id, reply_post_id: post2.id) end it "deletes the post and the reply to it" do sign_in(poster) PostDestroyer.any_instance.expects(:destroy).twice delete "/posts/destroy_many.json", params: { post_ids: [post1.id], reply_post_ids: [post1.id] } end end context "deleting flagged posts" do before do sign_in(moderator) PostActionCreator.off_topic(moderator, post1) PostActionCreator.off_topic(moderator, post2) Jobs::SendSystemMessage.clear end it "defers the child posts by default" do expect(ReviewableFlaggedPost.pending.count).to eq(2) delete "/posts/destroy_many.json", params: { post_ids: [post1.id, post2.id] } expect(Jobs::SendSystemMessage.jobs.size).to eq(1) expect(ReviewableFlaggedPost.pending.count).to eq(0) end it "can defer all posts based on `agree_with_first_reply_flag` param" do expect(ReviewableFlaggedPost.pending.count).to eq(2) delete "/posts/destroy_many.json", params: { post_ids: [post1.id, post2.id], agree_with_first_reply_flag: false } PostActionCreator.off_topic(moderator, post1) PostActionCreator.off_topic(moderator, post2) Jobs::SendSystemMessage.clear end end end end describe '#recover' do include_examples 'action requires login', :put, "/posts/123/recover.json" describe 'when logged in' do it "raises an error when the user doesn't have permission to see the post" do post = Fabricate(:post, topic: Fabricate(:private_message_topic), post_number: 3) sign_in(user) put "/posts/#{post.id}/recover.json" expect(response).to be_forbidden end it "recovers a post correctly" do topic_id = create_post.topic_id post = create_post(topic_id: topic_id) sign_in(user) PostDestroyer.new(user, post).destroy put "/posts/#{post.id}/recover.json" post.reload expect(post.trashed?).to be_falsey end end end describe '#update' do include_examples 'action requires login', :put, "/posts/2.json" let!(:post) { post_by_user } let(:update_params) do { post: { raw: 'edited body', edit_reason: 'typo' }, image_sizes: { 'http://image.com/image.jpg' => { 'width' => 123, 'height' => 456 } }, } end describe 'when logged in as a regular user' do before do sign_in(user) end it 'does not allow TL0 or TL1 to update when edit time limit expired' do SiteSetting.post_edit_time_limit = 5 SiteSetting.tl2_post_edit_time_limit = 30 post = Fabricate(:post, created_at: 10.minutes.ago, user: user) user.update_columns(trust_level: 1) put "/posts/#{post.id}.json", params: update_params expect(response.status).to eq(422) expect(JSON.parse(response.body)['errors']).to include(I18n.t('too_late_to_edit')) end it 'does not allow TL2 to update when edit time limit expired' do SiteSetting.post_edit_time_limit = 12 SiteSetting.tl2_post_edit_time_limit = 8 user.update_columns(trust_level: 2) post = Fabricate(:post, created_at: 10.minutes.ago, user: user) put "/posts/#{post.id}.json", params: update_params expect(response.status).to eq(422) expect(JSON.parse(response.body)['errors']).to include(I18n.t('too_late_to_edit')) end it 'passes the image sizes through' do Post.any_instance.expects(:image_sizes=) put "/posts/#{post.id}.json", params: update_params end it 'passes the edit reason through' do put "/posts/#{post.id}.json", params: update_params expect(response.status).to eq(200) post.reload expect(post.edit_reason).to eq("typo") expect(post.raw).to eq("edited body") end it 'checks for an edit conflict' do update_params[:post][:raw_old] = 'old body' put "/posts/#{post.id}.json", params: update_params expect(response.status).to eq(409) end it "raises an error when the post parameter is missing" do update_params.delete(:post) put "/posts/#{post.id}.json", params: update_params expect(response.status).to eq(400) expect(response.message.downcase).to eq("bad request") end it "raises an error when the user doesn't have permission to see the post" do post = Fabricate(:private_message_post, post_number: 3) put "/posts/#{post.id}.json", params: update_params expect(response).to be_forbidden end it "calls revise with valid parameters" do PostRevisor.any_instance.expects(:revise!).with(post.user, { raw: 'edited body' , edit_reason: 'typo' }, anything) put "/posts/#{post.id}.json", params: update_params end it "extracts links from the new body" do param = update_params param[:post][:raw] = 'I just visited this https://google.com so many cool links' put "/posts/#{post.id}.json", params: param expect(response.status).to eq(200) expect(TopicLink.count).to eq(1) end it "doesn't allow updating of deleted posts" do first_post = post.topic.ordered_posts.first PostDestroyer.new(moderator, first_post).destroy put "/posts/#{first_post.id}.json", params: update_params expect(response).not_to be_successful end end describe "when logged in as staff" do before do sign_in(moderator) end it "supports updating posts in deleted topics" do first_post = post.topic.ordered_posts.first PostDestroyer.new(moderator, first_post).destroy put "/posts/#{first_post.id}.json", params: update_params expect(response.status).to eq(200) post.reload expect(post.raw).to eq('edited body') end it "won't update bump date if post is a whisper" do post = Fabricate(:post, post_type: Post.types[:whisper], user: user) put "/posts/#{post.id}.json", params: update_params expect(response.status).to eq(200) expect(post.topic.reload.bumped_at).to be < post.created_at end end it 'can not change category to a disallowed category' do post = create_post sign_in(post.user) category = Fabricate(:category) category.set_permissions(staff: :full) category.save! put "/posts/#{post.id}.json", params: { post: { category_id: category.id, raw: "this is a test edit to post" } } expect(response.status).not_to eq(200) expect(post.topic.category_id).not_to eq(category.id) end it 'can not move to a category that requires topic approval' do post = create_post sign_in(post.user) category = Fabricate(:category) category.custom_fields[Category::REQUIRE_TOPIC_APPROVAL] = true category.save! put "/posts/#{post.id}.json", params: { post: { category_id: category.id, raw: "this is a test edit to post" } } expect(response.status).to eq(403) expect(post.topic.reload.category_id).not_to eq(category.id) end end describe '#bookmark' do include_examples 'action requires login', :put, "/posts/2/bookmark.json" let!(:post) { post_by_user } describe 'when logged in' do before do sign_in(user) end fab!(:private_message) { Fabricate(:private_message_post) } it "raises an error if the user doesn't have permission to see the post" do put "/posts/#{private_message.id}/bookmark.json", params: { bookmarked: "true" } expect(response).to be_forbidden end it 'creates a bookmark' do put "/posts/#{post.id}/bookmark.json", params: { bookmarked: "true" } expect(response.status).to eq(200) post_action = PostAction.find_by(user: user, post: post) expect(post_action.post_action_type_id).to eq(PostActionType.types[:bookmark]) end context "removing a bookmark" do let(:post_action) { PostActionCreator.create(user, post, :bookmark).post_action } it "returns the right response when post is not bookmarked" do put "/posts/#{post_by_user.id}/bookmark.json" expect(response.status).to eq(404) end it "should be able to remove a bookmark" do post_action put "/posts/#{post.id}/bookmark.json" expect(PostAction.find_by(id: post_action.id)).to eq(nil) end describe "when user doesn't have permission to see bookmarked post" do it "should still be able to remove a bookmark" do post_action post = post_action.post topic = post.topic topic.convert_to_private_message(admin) topic.remove_allowed_user(admin, user.username) expect(Guardian.new(user).can_see_post?(post.reload)).to eq(false) put "/posts/#{post.id}/bookmark.json" expect(PostAction.find_by(id: post_action.id)).to eq(nil) end end describe "when post has been deleted" do it "should still be able to remove a bookmark" do post = post_action.post post.trash! put "/posts/#{post.id}/bookmark.json" expect(PostAction.find_by(id: post_action.id)).to eq(nil) end end end end context "api" do let(:api_key) { Fabricate(:api_key, user: user) } let(:master_key) { Fabricate(:api_key, user: nil) } # choosing an arbitrarily easy to mock trusted activity it 'allows users with api key to bookmark posts' do put "/posts/#{post.id}/bookmark.json", params: { bookmarked: "true", api_key: api_key.key } expect(response.status).to eq(200) expect(PostAction.where( post: post, user: user, post_action_type_id: PostActionType.types[:bookmark] ).count).to eq(1) end it 'raises an error with a user key that does not match an optionally specified username' do put "/posts/#{post.id}/bookmark.json", params: { bookmarked: "true", api_key: api_key.key, api_username: 'made_up' } expect(response.status).to eq(403) end it 'allows users with a master api key to bookmark posts' do put "/posts/#{post.id}/bookmark.json", params: { bookmarked: "true", api_key: master_key.key, api_username: user.username } expect(response.status).to eq(200) expect(PostAction.where( post: post, user: user, post_action_type_id: PostActionType.types[:bookmark] ).count).to eq(1) end it 'disallows phonies to bookmark posts' do put "/posts/#{post.id}/bookmark.json", params: { bookmarked: "true", api_key: SecureRandom.hex(32), api_username: user.username } expect(response.status).to eq(403) end it 'disallows blank api' do put "/posts/#{post.id}/bookmark.json", params: { bookmarked: "true", api_key: "", api_username: user.username } expect(response.status).to eq(403) end end end describe '#wiki' do include_examples "action requires login", :put, "/posts/2/wiki.json" describe "when logged in" do before do sign_in(user) end let!(:post) { post_by_user } it "raises an error if the user doesn't have permission to wiki the post" do put "/posts/#{post.id}/wiki.json", params: { wiki: 'true' } expect(response).to be_forbidden end it "toggle wiki status should create a new version" do sign_in(admin) another_user = Fabricate(:user) another_post = Fabricate(:post, user: another_user) expect do put "/posts/#{another_post.id}/wiki.json", params: { wiki: 'true' } end.to change { another_post.reload.version }.by(1) expect do put "/posts/#{another_post.id}/wiki.json", params: { wiki: 'false' } end.to change { another_post.reload.version }.by(-1) sign_in(Fabricate(:admin)) expect do put "/posts/#{another_post.id}/wiki.json", params: { wiki: 'true' } end.to change { another_post.reload.version }.by(1) end it "can wiki a post" do sign_in(admin) put "/posts/#{post.id}/wiki.json", params: { wiki: 'true' } post.reload expect(post.wiki).to eq(true) end it "can unwiki a post" do wikied_post = Fabricate(:post, user: user, wiki: true) sign_in(admin) put "/posts/#{wikied_post.id}/wiki.json", params: { wiki: 'false' } wikied_post.reload expect(wikied_post.wiki).to eq(false) end end end describe '#post_type' do include_examples "action requires login", :put, "/posts/2/post_type.json" describe "when logged in" do before do sign_in(user) end let!(:post) { post_by_user } it "raises an error if the user doesn't have permission to change the post type" do put "/posts/#{post.id}/post_type.json", params: { post_type: 2 } expect(response).to be_forbidden end it "can change the post type" do sign_in(moderator) put "/posts/#{post.id}/post_type.json", params: { post_type: 2 } post.reload expect(post.post_type).to eq(2) end end end describe '#rebake' do include_examples "action requires login", :put, "/posts/2/rebake.json" describe "when logged in" do let!(:post) { post_by_user } it "raises an error if the user doesn't have permission to rebake the post" do sign_in(user) put "/posts/#{post.id}/rebake.json" expect(response).to be_forbidden end it "can rebake the post" do sign_in(moderator) put "/posts/#{post.id}/rebake.json" expect(response.status).to eq(200) end it "will invalidate broken images cache" do sign_in(moderator) post.custom_fields[Post::BROKEN_IMAGES] = ["https://example.com/image.jpg"].to_json post.save_custom_fields put "/posts/#{post.id}/rebake.json" post.reload expect(post.custom_fields[Post::BROKEN_IMAGES]).to be_nil end end end describe '#create' do include_examples 'action requires login', :post, "/posts.json" before do SiteSetting.min_first_post_typing_time = 0 SiteSetting.enable_whispers = true end context 'api' do it 'memoizes duplicate requests' do raw = "this is a test post 123 #{SecureRandom.hash}" title = "this is a title #{SecureRandom.hash}" master_key = Fabricate(:api_key).key post "/posts.json", params: { api_username: user.username, api_key: master_key, raw: raw, title: title, wpid: 1 } expect(response.status).to eq(200) original = response.body post "/posts.json", params: { api_username: user.username_lower, api_key: master_key, raw: raw, title: title, wpid: 2 } expect(response.status).to eq(200) expect(response.body).to eq(original) end it 'allows to create posts in import_mode' do Jobs.run_immediately! NotificationEmailer.enable post_1 = Fabricate(:post) master_key = Fabricate(:api_key).key post "/posts.json", params: { api_username: user.username, api_key: master_key, raw: 'this is test reply 1', topic_id: post_1.topic.id, reply_to_post_number: 1 } expect(response.status).to eq(200) expect(post_1.topic.user.notifications.count).to eq(1) post_1.topic.user.notifications.destroy_all post "/posts.json", params: { api_username: user.username, api_key: master_key, raw: 'this is test reply 2', topic_id: post_1.topic.id, reply_to_post_number: 1, import_mode: true } expect(response.status).to eq(200) expect(post_1.topic.user.notifications.count).to eq(0) post "/posts.json", params: { api_username: user.username, api_key: master_key, raw: 'this is test reply 3', topic_id: post_1.topic.id, reply_to_post_number: 1, import_mode: false } expect(response.status).to eq(200) expect(post_1.topic.user.notifications.count).to eq(1) end it 'prevents whispers for regular users' do post_1 = Fabricate(:post) user_key = ApiKey.create!(user: user).key post "/posts.json", params: { api_username: user.username, api_key: user_key, raw: 'this is test whisper', topic_id: post_1.topic.id, reply_to_post_number: 1, whisper: true } expect(response.status).to eq(403) end it 'will raise an error if specified category cannot be found' do user = Fabricate(:admin) master_key = Fabricate(:api_key).key post "/posts.json", params: { api_username: user.username, api_key: master_key, title: 'this is a test title', raw: 'this is test body', category: 'invalid' } expect(response.status).to eq(400) expect(JSON.parse(response.body)["errors"]).to include( I18n.t("invalid_params", message: "category") ) end end describe "when logged in" do before do sign_in(user) end context "fast typing" do before do SiteSetting.min_first_post_typing_time = 3000 SiteSetting.auto_silence_fast_typers_max_trust_level = 1 end it 'queues the post if min_first_post_typing_time is not met' do post "/posts.json", params: { raw: 'this is the test content', title: 'this is the test title for the topic', composer_open_duration_msecs: 204, typing_duration_msecs: 100, reply_to_post_number: 123 } expect(response.status).to eq(200) parsed = ::JSON.parse(response.body) expect(parsed["action"]).to eq("enqueued") user.reload expect(user).to be_silenced rp = ReviewableQueuedPost.find_by(created_by: user) expect(rp.payload['typing_duration_msecs']).to eq(100) expect(rp.payload['composer_open_duration_msecs']).to eq(204) expect(rp.payload['reply_to_post_number']).to eq(123) expect(rp.reviewable_scores.first.reason).to eq('fast_typer') expect(parsed['pending_post']).to be_present expect(parsed['pending_post']['id']).to eq(rp.id) expect(parsed['pending_post']['raw']).to eq("this is the test content") mod = moderator rp.perform(mod, :approve_post) user.reload expect(user).not_to be_silenced end it "doesn't enqueue posts when user first creates a topic" do user.user_stat.update_column(:topic_count, 1) Draft.set(user, "should_clear", 0, "{'a' : 'b'}") post "/posts.json", params: { raw: 'this is the test content', title: 'this is the test title for the topic', composer_open_duration_msecs: 204, typing_duration_msecs: 100, topic_id: topic.id, draft_key: "should_clear" } expect(response.status).to eq(200) parsed = ::JSON.parse(response.body) expect(parsed["action"]).not_to be_present expect { Draft.get(user, "should_clear", 0) }.to raise_error(Draft::OutOfSequence) end it "doesn't enqueue replies when the topic is closed" do topic = Fabricate(:closed_topic) post "/posts.json", params: { raw: 'this is the test content', title: 'this is the test title for the topic', topic_id: topic.id } expect(response).not_to be_successful parsed = ::JSON.parse(response.body) expect(parsed["action"]).not_to eq("enqueued") end it "doesn't enqueue replies when the post is too long" do SiteSetting.max_post_length = 10 post "/posts.json", params: { raw: 'this is the test content', title: 'this is the test title for the topic' } expect(response).not_to be_successful parsed = ::JSON.parse(response.body) expect(parsed["action"]).not_to eq("enqueued") end end it 'silences correctly based on auto_silence_first_post_regex' do SiteSetting.auto_silence_first_post_regex = "I love candy|i eat s[1-5]" post "/posts.json", params: { raw: 'this is the test content', title: 'when I eat s3 sometimes when not looking' } expect(response.status).to eq(200) parsed = ::JSON.parse(response.body) expect(parsed["action"]).to eq("enqueued") reviewable = ReviewableQueuedPost.find_by(created_by: user) score = reviewable.reviewable_scores.first expect(score.reason).to eq('auto_silence_regex') user.reload expect(user).to be_silenced end it "can send a message to a group" do group = Group.create(name: 'test_group', messageable_level: Group::ALIAS_LEVELS[:nobody]) user1 = user group.add(user1) post "/posts.json", params: { raw: 'I can haz a test', title: 'I loves my test', target_usernames: group.name, archetype: Archetype.private_message } expect(response).not_to be_successful # allow pm to this group group.update_columns(messageable_level: Group::ALIAS_LEVELS[:everyone]) post "/posts.json", params: { raw: 'I can haz a test', title: 'I loves my test', target_usernames: group.name, archetype: Archetype.private_message } expect(response.status).to eq(200) parsed = ::JSON.parse(response.body) post = Post.find(parsed['id']) expect(post.topic.topic_allowed_users.length).to eq(1) expect(post.topic.topic_allowed_groups.length).to eq(1) end it "returns the nested post with a param" do post "/posts.json", params: { raw: 'this is the test content', title: 'this is the test title for the topic', nested_post: true } expect(response.status).to eq(200) parsed = ::JSON.parse(response.body) expect(parsed['post']).to be_present expect(parsed['post']['cooked']).to be_present end it 'protects against dupes' do raw = "this is a test post 123 #{SecureRandom.hash}" title = "this is a title #{SecureRandom.hash}" expect do post "/posts.json", params: { raw: raw, title: title, wpid: 1 } end.to change { Post.count } expect(response.status).to eq(200) expect do post "/posts.json", params: { raw: raw, title: title, wpid: 2 } end.to_not change { Post.count } expect(response.status).to eq(422) end it 'can not create a post in a disallowed category' do category.set_permissions(staff: :full) category.save! post "/posts.json", params: { raw: 'this is the test content', title: 'this is the test title for the topic', category: category.id, meta_data: { xyz: 'abc' } } expect(response.status).to eq(403) end it 'can not create a post with a tag that is restricted' do SiteSetting.tagging_enabled = true tag = Fabricate(:tag) category.allowed_tags = [tag.name] category.save! post "/posts.json", params: { raw: 'this is the test content', title: 'this is the test title for the topic', tags: [tag.name], } expect(response.status).to eq(422) json = JSON.parse(response.body) expect(json['errors']).to be_present end it 'creates the post' do post "/posts.json", params: { raw: 'this is the test content', title: 'this is the test title for the topic', category: category.id, meta_data: { xyz: 'abc' } } expect(response.status).to eq(200) new_post = Post.last topic = new_post.topic expect(new_post.user).to eq(user) expect(new_post.raw).to eq('this is the test content') expect(topic.title).to eq('This is the test title for the topic') expect(topic.category).to eq(category) expect(topic.meta_data).to eq("xyz" => 'abc') end it 'can create an uncategorized topic' do title = 'this is the test title for the topic' expect do post "/posts.json", params: { raw: 'this is the test content', title: title, category: "" } expect(response.status).to eq(200) end.to change { Topic.count }.by(1) topic = Topic.last expect(topic.title).to eq(title.capitalize) expect(topic.category_id).to eq(SiteSetting.uncategorized_category_id) end it 'can create a reply to a post' do topic = Fabricate(:private_message_post, user: user).topic post_2 = Fabricate(:private_message_post, user: user, topic: topic) post "/posts.json", params: { raw: 'this is the test content', topic_id: topic.id, reply_to_post_number: post_2.post_number, image_sizes: { width: '100', height: '200' } } expect(response.status).to eq(200) new_post = Post.last topic = new_post.topic expect(new_post.user).to eq(user) expect(new_post.raw).to eq('this is the test content') expect(new_post.reply_to_post_number).to eq(post_2.post_number) job_args = Jobs::ProcessPost.jobs.first["args"].first expect(job_args["image_sizes"]).to eq("width" => '100', "height" => '200') end it 'creates a private post' do user_2 = Fabricate(:user) user_3 = Fabricate(:user) post "/posts.json", params: { raw: 'this is the test content', archetype: 'private_message', title: "this is some post", target_usernames: "#{user_2.username},#{user_3.username}" } expect(response.status).to eq(200) new_post = Post.last new_topic = Topic.last expect(new_post.user).to eq(user) expect(new_topic.private_message?).to eq(true) expect(new_topic.allowed_users).to contain_exactly(user, user_2, user_3) end context "when target_recipients not provided" do it "errors when creating a private post" do post "/posts.json", params: { raw: 'this is the test content', archetype: 'private_message', title: "this is some post", target_recipients: "" } expect(response.status).to eq(422) expect(JSON.parse(response.body)["errors"]).to include( I18n.t("activerecord.errors.models.topic.attributes.base.no_user_selected") ) end end context "errors" do it "does not succeed" do post "/posts.json", params: { raw: 'test' } expect(response).not_to be_successful expect(response.status).to eq(422) end it "it triggers flag_linked_posts_as_spam when the post creator returns spam" do SiteSetting.newuser_spam_host_threshold = 1 sign_in(Fabricate(:user, trust_level: 0)) post "/posts.json", params: { raw: 'this is the test content http://fakespamwebsite.com http://fakespamwebsite.com/spam http://fakespamwebsite.com/spammy', title: 'this is the test title for the topic', meta_data: { xyz: 'abc' } } expect(JSON.parse(response.body)["errors"]).to include(I18n.t(:spamming_host)) end context "allow_uncategorized_topics is false" do before do SiteSetting.allow_uncategorized_topics = false end it "cant create an uncategorized post" do post "/posts.json", params: { raw: "a new post with no category", title: "a new post with no category" } expect(response).not_to be_successful end context "as staff" do before do sign_in(admin) end it "cant create an uncategorized post" do post "/posts.json", params: { raw: "a new post with no category", title: "a new post with no category" } expect(response).not_to be_successful end end end end end describe 'shared draft' do fab!(:destination_category) { Fabricate(:category) } it "will raise an error for regular users" do post "/posts.json", params: { raw: 'this is the shared draft content', title: "this is the shared draft title", category: destination_category.id, shared_draft: 'true' } expect(response).not_to be_successful end describe "as a staff user" do before do sign_in(moderator) end it "will raise an error if there is no shared draft category" do post "/posts.json", params: { raw: 'this is the shared draft content', title: "this is the shared draft title", category: destination_category.id, shared_draft: 'true' } expect(response).not_to be_successful end context "with a shared category" do fab!(:shared_category) { Fabricate(:category) } before do SiteSetting.shared_drafts_category = shared_category.id end it "will work if the shared draft category is present" do post "/posts.json", params: { raw: 'this is the shared draft content', title: "this is the shared draft title", category: destination_category.id, shared_draft: 'true' } expect(response.status).to eq(200) result = JSON.parse(response.body) topic = Topic.find(result['topic_id']) expect(topic.category_id).to eq(shared_category.id) expect(topic.shared_draft.category_id).to eq(destination_category.id) end end end end describe 'warnings' do fab!(:user_2) { Fabricate(:user) } context 'as a staff user' do before do sign_in(admin) end it 'should be able to mark a topic as warning' do post "/posts.json", params: { raw: 'this is the test content', archetype: 'private_message', title: "this is some post", target_usernames: user_2.username, is_warning: true } expect(response.status).to eq(200) new_topic = Topic.last expect(new_topic.title).to eq('This is some post') expect(new_topic.is_official_warning?).to eq(true) end it 'should be able to mark a topic as not a warning' do post "/posts.json", params: { raw: 'this is the test content', archetype: 'private_message', title: "this is some post", target_usernames: user_2.username, is_warning: false } expect(response.status).to eq(200) new_topic = Topic.last expect(new_topic.title).to eq('This is some post') expect(new_topic.is_official_warning?).to eq(false) end end context 'as a normal user' do it 'should not be able to mark a topic as warning' do sign_in(user) post "/posts.json", params: { raw: 'this is the test content', archetype: 'private_message', title: "this is some post", target_usernames: user_2.username, is_warning: true } expect(response.status).to eq(200) new_topic = Topic.last expect(new_topic.title).to eq('This is some post') expect(new_topic.is_official_warning?).to eq(false) end end end context "topic bump" do shared_examples "it works" do let(:original_bumped_at) { 1.day.ago } let!(:topic) { Fabricate(:topic, bumped_at: original_bumped_at) } it "should be able to skip topic bumping" do post "/posts.json", params: { raw: 'this is the test content', topic_id: topic.id, no_bump: true } expect(response.status).to eq(200) expect(topic.reload.bumped_at).to be_within_one_second_of(original_bumped_at) end it "should be able to post with topic bumping" do post "/posts.json", params: { raw: 'this is the test content', topic_id: topic.id } expect(response.status).to eq(200) expect(topic.reload.bumped_at).to eq(topic.posts.last.created_at) end end context "admins" do before do sign_in(admin) end include_examples "it works" end context "moderators" do before do sign_in(moderator) end include_examples "it works" end context "TL4 users" do fab!(:trust_level_4) { Fabricate(:trust_level_4) } before do sign_in(trust_level_4) end include_examples "it works" end context "users" do fab!(:topic) { Fabricate(:topic) } [:user].each do |user| it "will raise an error for #{user}" do sign_in(Fabricate(user)) post "/posts.json", params: { raw: 'this is the test content', topic_id: topic.id, no_bump: true } expect(response.status).to eq(400) end end end end end describe '#revisions' do fab!(:post) { Fabricate(:post, version: 2) } let(:post_revision) { Fabricate(:post_revision, post: post) } it "throws an exception when revision is < 2" do get "/posts/#{post.id}/revisions/1.json" expect(response.status).to eq(400) end context "when edit history is not visible to the public" do before { SiteSetting.edit_history_visible_to_public = false } it "ensures anonymous cannot see the revisions" do get "/posts/#{post.id}/revisions/#{post_revision.number}.json" expect(response).to be_forbidden end it "ensures regular user cannot see the revisions" do sign_in(user) get "/posts/#{post.id}/revisions/#{post_revision.number}.json" expect(response).to be_forbidden end it "ensures staff can see the revisions" do sign_in(admin) get "/posts/#{post.id}/revisions/#{post_revision.number}.json" expect(response.status).to eq(200) end it "ensures poster can see the revisions" do user = Fabricate(:active_user) sign_in(user) post = Fabricate(:post, user: user, version: 3) pr = Fabricate(:post_revision, user: user, post: post) get "/posts/#{pr.post_id}/revisions/#{pr.number}.json" expect(response.status).to eq(200) end it "ensures trust level 4 can see the revisions" do sign_in(Fabricate(:user, trust_level: 4)) get "/posts/#{post_revision.post_id}/revisions/#{post_revision.number}.json" expect(response.status).to eq(200) end end context "when post is hidden" do before { post.hidden = true post.save } it "throws an exception for users" do sign_in(user) get "/posts/#{post.id}/revisions/#{post_revision.number}.json" expect(response.status).to eq(404) end it "works for admins" do sign_in(admin) get "/posts/#{post.id}/revisions/#{post_revision.number}.json" expect(response.status).to eq(200) end end context "when edit history is visible to everyone" do before { SiteSetting.edit_history_visible_to_public = true } it "ensures anyone can see the revisions" do get "/posts/#{post_revision.post_id}/revisions/#{post_revision.number}.json" expect(response.status).to eq(200) end end context "deleted post" do fab!(:deleted_post) { Fabricate(:post, user: admin, version: 3) } fab!(:deleted_post_revision) { Fabricate(:post_revision, user: admin, post: deleted_post) } before { deleted_post.trash!(admin) } it "also work on deleted post" do sign_in(admin) get "/posts/#{deleted_post_revision.post_id}/revisions/#{deleted_post_revision.number}.json" expect(response.status).to eq(200) end end context "deleted topic" do fab!(:deleted_topic) { Fabricate(:topic, user: admin) } fab!(:post) { Fabricate(:post, user: admin, topic: deleted_topic, version: 3) } fab!(:post_revision) { Fabricate(:post_revision, user: admin, post: post) } before { deleted_topic.trash!(admin) } it "also work on deleted topic" do sign_in(admin) get "/posts/#{post_revision.post_id}/revisions/#{post_revision.number}.json" expect(response.status).to eq(200) end end end describe '#revert' do include_examples 'action requires login', :put, "/posts/123/revisions/2/revert.json" fab!(:post) { Fabricate(:post, user: Fabricate(:user), raw: "Lorem ipsum dolor sit amet, cu nam libris tractatos, ancillae senserit ius ex") } let(:post_revision) { Fabricate(:post_revision, post: post, modifications: { "raw" => ["this is original post body.", "this is edited post body."] }) } let(:blank_post_revision) { Fabricate(:post_revision, post: post, modifications: { "edit_reason" => ["edit reason #1", "edit reason #2"] }) } let(:same_post_revision) { Fabricate(:post_revision, post: post, modifications: { "raw" => ["Lorem ipsum dolor sit amet, cu nam libris tractatos, ancillae senserit ius ex", "this is edited post body."] }) } let(:post_id) { post.id } let(:revision_id) { post_revision.number } describe 'when logged in as a regular user' do it "does not work" do sign_in(user) put "/posts/#{post_id}/revisions/#{revision_id}/revert.json" expect(response).to_not be_successful end end describe "when logged in as staff" do before do sign_in(moderator) end it "fails when revision is < 2" do put "/posts/#{post_id}/revisions/1/revert.json" expect(response.status).to eq(400) end it "fails when post_revision record is not found" do put "/posts/#{post_id}/revisions/#{revision_id + 1}/revert.json" expect(response).to_not be_successful end it "fails when post record is not found" do put "/posts/#{post_id + 1}/revisions/#{revision_id}/revert.json" expect(response).to_not be_successful end it "fails when revision is blank" do put "/posts/#{post_id}/revisions/#{blank_post_revision.number}/revert.json" expect(response.status).to eq(422) expect(JSON.parse(response.body)['errors']).to include(I18n.t('revert_version_same')) end it "fails when revised version is same as current version" do put "/posts/#{post_id}/revisions/#{same_post_revision.number}/revert.json" expect(response.status).to eq(422) expect(JSON.parse(response.body)['errors']).to include(I18n.t('revert_version_same')) end it "works!" do put "/posts/#{post_id}/revisions/#{revision_id}/revert.json" expect(response.status).to eq(200) end it "supports reverting posts in deleted topics" do first_post = post.topic.ordered_posts.first PostDestroyer.new(moderator, first_post).destroy put "/posts/#{post_id}/revisions/#{revision_id}/revert.json" expect(response.status).to eq(200) end end end describe '#expand_embed' do before do sign_in(user) end fab!(:post) { Fabricate(:post) } it "raises an error when you can't see the post" do post = Fabricate(:private_message_post) get "/posts/#{post.id}/expand-embed.json" expect(response).not_to be_successful end it "retrieves the body when you can see the post" do TopicEmbed.expects(:expanded_for).with(post).returns("full content") get "/posts/#{post.id}/expand-embed.json" expect(response.status).to eq(200) expect(::JSON.parse(response.body)['cooked']).to eq("full content") end end describe '#flagged_posts' do include_examples "action requires login", :get, "/posts/system/flagged.json" describe "when logged in" do it "raises an error if the user doesn't have permission to see the flagged posts" do sign_in(user) get "/posts/system/flagged.json" expect(response).to be_forbidden end it "can see the flagged posts when authorized" do sign_in(moderator) get "/posts/system/flagged.json" expect(response.status).to eq(200) end it "only shows agreed and deferred flags" do post_agreed = create_post(user: user) post_deferred = create_post(user: user) post_disagreed = create_post(user: user) r0 = PostActionCreator.spam(moderator, post_agreed).reviewable r1 = PostActionCreator.off_topic(moderator, post_deferred).reviewable r2 = PostActionCreator.inappropriate(moderator, post_disagreed).reviewable r0.perform(admin, :agree_and_keep) r1.perform(admin, :ignore) r2.perform(admin, :disagree) sign_in(Fabricate(:moderator)) get "/posts/#{user.username}/flagged.json" expect(response.status).to eq(200) expect(JSON.parse(response.body).length).to eq(2) end end end describe '#deleted_posts' do include_examples "action requires login", :get, "/posts/system/deleted.json" describe "when logged in" do it "raises an error if the user doesn't have permission to see the deleted posts" do sign_in(user) get "/posts/system/deleted.json" expect(response).to be_forbidden end it "can see the deleted posts when authorized" do sign_in(moderator) get "/posts/system/deleted.json" expect(response.status).to eq(200) end it "doesn't return secured categories for moderators if they don't have access" do Fabricate(:moderator) group = Fabricate(:group) group.add_owner(user) secured_category = Fabricate(:private_category, group: group) secured_post = create_post(user: user, category: secured_category) PostDestroyer.new(admin, secured_post).destroy sign_in(moderator) get "/posts/#{user.username}/deleted.json" expect(response.status).to eq(200) data = JSON.parse(response.body) expect(data.length).to eq(0) end it "doesn't return PMs for moderators" do Fabricate(:moderator) pm_post = create_post(user: user, archetype: 'private_message', target_usernames: [admin.username]) PostDestroyer.new(admin, pm_post).destroy sign_in(moderator) get "/posts/#{user.username}/deleted.json" expect(response.status).to eq(200) data = JSON.parse(response.body) expect(data.length).to eq(0) end it "only shows posts deleted by other users" do create_post(user: user) post_deleted_by_user = create_post(user: user) post_deleted_by_admin = create_post(user: user) PostDestroyer.new(user, post_deleted_by_user).destroy PostDestroyer.new(admin, post_deleted_by_admin).destroy sign_in(admin) get "/posts/#{user.username}/deleted.json" expect(response.status).to eq(200) data = JSON.parse(response.body) expect(data.length).to eq(1) expect(data[0]["id"]).to eq(post_deleted_by_admin.id) expect(data[0]["deleted_by"]["id"]).to eq(admin.id) end end end describe '#markdown_id' do it "can be viewed by anonymous" do post = Fabricate(:post, raw: "123456789") get "/posts/#{post.id}/raw.json" expect(response.status).to eq(200) expect(response.body).to eq("123456789") end end describe '#markdown_num' do it "can be viewed by anonymous" do topic = Fabricate(:topic) post = Fabricate(:post, topic: topic, post_number: 1, raw: "123456789") post.save get "/raw/#{topic.id}/1.json" expect(response.status).to eq(200) expect(response.body).to eq("123456789") end end describe '#short_link' do fab!(:topic) { Fabricate(:topic) } fab!(:post) { Fabricate(:post, topic: topic) } it "redirects to the topic" do get "/p/#{post.id}.json" expect(response).to be_redirect end it "returns a 403 when access is denied" do post = Fabricate(:private_message_post) get "/p/#{post.id}.json" expect(response).to be_forbidden end end describe '#user_posts_feed' do it 'returns public posts rss feed' do public_post private_post get "/u/#{user.username}/activity.rss" expect(response.status).to eq(200) body = response.body expect(body).to_not include(private_post.url) expect(body).to include(public_post.url) end it 'returns public posts as JSON' do public_post private_post get "/u/#{user.username}/activity.json" expect(response.status).to eq(200) body = response.body expect(body).to_not include(private_post.topic.slug) expect(body).to include(public_post.topic.slug) end end describe '#latest' do context 'private posts' do describe 'when not logged in' do it 'should return the right response' do Fabricate(:post) get "/private-posts.rss" expect(response.status).to eq(404) expect(response.body).to have_tag( "input", with: { value: "private_posts" } ) end end it 'returns private posts rss feed' do sign_in(admin) public_post private_post get "/private-posts.rss" expect(response.status).to eq(200) body = response.body expect(body).to include(private_post.url) expect(body).to_not include(public_post.url) end it 'returns private posts for json' do sign_in(admin) public_post private_post get "/private-posts.json" expect(response.status).to eq(200) json = ::JSON.parse(response.body) post_ids = json['private_posts'].map { |p| p['id'] } expect(post_ids).to include private_post.id expect(post_ids).to_not include public_post.id end end context 'public posts' do it 'returns public posts with topic rss feed' do public_post private_post get "/posts.rss" expect(response.status).to eq(200) body = response.body expect(body).to include(public_post.url) expect(body).to_not include(private_post.url) end it 'returns public posts with topic for json' do topicless_post.update topic_id: -100 public_post private_post topicless_post get "/posts.json" expect(response.status).to eq(200) json = ::JSON.parse(response.body) post_ids = json['latest_posts'].map { |p| p['id'] } expect(post_ids).to include public_post.id expect(post_ids).to_not include private_post.id expect(post_ids).to_not include topicless_post.id end end end describe '#cooked' do it 'returns the cooked conent' do post = Fabricate(:post, cooked: "WAt") get "/posts/#{post.id}/cooked.json" expect(response.status).to eq(200) json = ::JSON.parse(response.body) expect(json).to be_present expect(json['cooked']).to eq('WAt') end end describe '#raw_email' do include_examples "action requires login", :get, "/posts/2/raw-email.json" describe "when logged in" do let(:post) { Fabricate(:post, deleted_at: 2.hours.ago, user: Fabricate(:user), raw_email: 'email_content') } it "raises an error if the user doesn't have permission to view raw email" do sign_in(user) get "/posts/#{post.id}/raw-email.json" expect(response).to be_forbidden end it "can view raw email" do sign_in(moderator) get "/posts/#{post.id}/raw-email.json" expect(response.status).to eq(200) json = ::JSON.parse(response.body) expect(json['raw_email']).to eq('email_content') end end end describe "#locked" do before do sign_in(moderator) end it 'can lock and unlock the post' do put "/posts/#{public_post.id}/locked.json", params: { locked: "true" } expect(response.status).to eq(200) public_post.reload expect(public_post).to be_locked put "/posts/#{public_post.id}/locked.json", params: { locked: "false" } expect(response.status).to eq(200) public_post.reload expect(public_post).not_to be_locked end end describe "#notice" do before do sign_in(moderator) end it 'can create and remove notices' do put "/posts/#{public_post.id}/notice.json", params: { notice: "Hello *world*!\n\nhttps://github.com/discourse/discourse" } expect(response.status).to eq(200) public_post.reload expect(public_post.custom_fields[Post::NOTICE_TYPE]).to eq(Post.notices[:custom]) expect(public_post.custom_fields[Post::NOTICE_ARGS]).to include('Hello world!
') expect(public_post.custom_fields[Post::NOTICE_ARGS]).not_to include('onebox') put "/posts/#{public_post.id}/notice.json", params: { notice: nil } expect(response.status).to eq(200) public_post.reload expect(public_post.custom_fields[Post::NOTICE_TYPE]).to eq(nil) expect(public_post.custom_fields[Post::NOTICE_ARGS]).to eq(nil) end end describe Plugin::Instance do describe '#add_permitted_post_create_param' do fab!(:user) { Fabricate(:user) } let(:instance) { Plugin::Instance.new } let(:request) do Proc.new { post "/posts.json", params: { raw: 'this is the test content', title: 'this is the test title for the topic', composer_open_duration_msecs: 204, typing_duration_msecs: 100, reply_to_post_number: 123, string_arg: '123', hash_arg: { key1: 'val' }, array_arg: ['1', '2', '3'] } } end before do sign_in(user) SiteSetting.min_first_post_typing_time = 0 end it 'allows strings to be added' do request.call expect(@controller.send(:create_params)).not_to include(string_arg: '123') instance.add_permitted_post_create_param(:string_arg) request.call expect(@controller.send(:create_params)).to include(string_arg: '123') end it 'allows hashes to be added' do instance.add_permitted_post_create_param(:hash_arg) request.call expect(@controller.send(:create_params)).not_to include(hash_arg: { key1: 'val' }) instance.add_permitted_post_create_param(:hash_arg, :hash) request.call expect(@controller.send(:create_params)).to include(hash_arg: { key1: 'val' }) end it 'allows strings to be added' do instance.add_permitted_post_create_param(:array_arg) request.call expect(@controller.send(:create_params)).not_to include(array_arg: ['1', '2', '3']) instance.add_permitted_post_create_param(:array_arg, :array) request.call expect(@controller.send(:create_params)).to include(array_arg: ['1', '2', '3']) end end end end