mirror of
https://github.com/discourse/discourse.git
synced 2024-11-30 11:55:09 +08:00
f1d547c301
We now show if a queued or flagged post is a reply to another when in the review queue. It's especially helpful for queued posts where normally they are linked to the topic where they are created, and you have no context about the reply. Note that this will only apply to new queued posts going forward. Previously queued posts will not show the "in reply to"
1793 lines
54 KiB
Ruby
1793 lines
54 KiB
Ruby
# 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: '<p>Car 54, where are you?</p>') }
|
|
|
|
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_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 to update when edit time limit expired' do
|
|
SiteSetting.post_edit_time_limit = 5
|
|
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) { user.generate_api_key(user) }
|
|
let(:master_key) { ApiKey.create_master_key }
|
|
|
|
# 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 = ApiKey.create_master_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 = ApiKey.create_master_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: SecureRandom.hex).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 = ApiKey.create_master_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 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 '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 "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
|
|
end
|