discourse/spec/requests/posts_controller_spec.rb
Alan Guo Xiang Tan 4cb7472376
SECURITY: Prevent arbitrary topic custom fields from being set
Why this change?

The `PostsController#create` action allows arbitrary topic custom fields
to be set by any user that can create a topic. Without any restrictions,
this opens us up to potential security issues where plugins may be using
topic custom fields in security sensitive areas.

What does this change do?

1. This change introduces the `register_editable_topic_custom_field` plugin
API which allows plugins to register topic custom fields that are
editable either by staff users only or all users. The registered
editable topic custom fields are stored in `DiscoursePluginRegistry` and
is called by a new method `Topic#editable_custom_fields` which is then
used in the `PostsController#create` controller action. When an unpermitted custom fields is present in the `meta_data` params,
a 400 response code is returned.

2. Removes all reference to `meta_data` on a topic as it is confusing
   since we actually mean topic custom fields instead.
2023-10-16 10:34:35 -04:00

2972 lines
92 KiB
Ruby

# frozen_string_literal: true
RSpec.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
it "returns 404 when post's topic is deleted" do
post.topic.destroy!
get url
expect(response.status).to eq(404)
end
context "with deleted post" do
before { post.trash!(user) }
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
context "with category group moderator" do
fab!(:group_user) { Fabricate(:group_user) }
let(:user_gm) { group_user.user }
let(:group) { group_user.group }
before do
SiteSetting.enable_category_group_moderation = true
sign_in(user_gm)
end
it "can find posts in the allowed category" do
post.topic.category.update!(reviewable_by_group_id: group.id, topic_id: topic.id)
get url
expect(response.status).to eq(200)
end
it "can't find posts outside of the allowed category" do
get url
expect(response.status).to eq(404)
end
end
end
end
RSpec.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
RSpec.describe PostsController do
fab!(:admin) { Fabricate(:admin) }
fab!(:moderator) { Fabricate(:moderator) }
fab!(:user) { Fabricate(:user) }
fab!(:user_trust_level_0) { Fabricate(:trust_level_0) }
fab!(:user_trust_level_1) { Fabricate(:trust_level_1) }
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) { Fabricate(:topic, archetype: Archetype.private_message, category_id: nil) }
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 = response.parsed_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 = response.parsed_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 = response.parsed_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 allowlisted 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 = response.parsed_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 "#reply_ids" do
include_examples "finding and showing post" do
let(:url) { "/posts/#{post.id}/reply-ids.json" }
end
it "returns ids of post's replies" do
post = Fabricate(:post)
reply1 = Fabricate(:post, topic: post.topic, reply_to_post_number: post.post_number)
reply2 = Fabricate(:post, topic: post.topic, reply_to_post_number: post.post_number)
PostReply.create(post_id: post.id, reply_post_id: reply1.id)
PostReply.create(post_id: post.id, reply_post_id: reply2.id)
get "/posts/#{post.id}/reply-ids.json"
expect(response.status).to eq(200)
expect(response.parsed_body).to eq(
[{ "id" => reply1.id, "level" => 1 }, { "id" => reply2.id, "level" => 1 }],
)
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 = response.parsed_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 "raises an error when the self deletions are disabled" do
SiteSetting.max_post_deletions_per_day = 0
post = Fabricate(:post, user: user, topic: topic, 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
context "with permanently destroy" do
let!(:post) { Fabricate(:post, topic_id: topic.id, post_number: 3) }
before { SiteSetting.can_permanently_delete = true }
it "does not work for a post that was not deleted yet" do
sign_in(admin)
delete "/posts/#{post.id}.json", params: { force_destroy: true }
expect(response.status).to eq(403)
end
it "needs some time to pass to permanently delete a topic" do
sign_in(admin)
delete "/posts/#{post.id}.json"
expect(response.status).to eq(200)
expect(post.reload.deleted_by_id).to eq(admin.id)
delete "/posts/#{post.id}.json", params: { force_destroy: true }
expect(response.status).to eq(403)
post.update!(deleted_at: 10.minutes.ago)
delete "/posts/#{post.id}.json", params: { force_destroy: true }
expect(response.status).to eq(200)
expect { post.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
it "needs two users to permanently delete a topic" do
sign_in(admin)
delete "/posts/#{post.id}.json"
expect(response.status).to eq(200)
expect(post.reload.deleted_by_id).to eq(admin.id)
sign_in(Fabricate(:admin))
delete "/posts/#{post.id}.json", params: { force_destroy: true }
expect(response.status).to eq(200)
expect { post.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
it "moderators cannot permanently delete topics" do
sign_in(admin)
delete "/posts/#{post.id}.json"
expect(response.status).to eq(200)
expect(post.reload.deleted_by_id).to eq(admin.id)
sign_in(moderator)
delete "/posts/#{post.id}.json", params: { force_destroy: true }
expect(response.status).to eq(403)
end
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) do
Fabricate(
:post,
topic: post1.topic,
user: poster,
post_number: 3,
reply_to_post_number: post1.post_number,
)
end
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: [12_345] }
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 { PostReply.create(post_id: post1.id, reply_post_id: post2.id) }
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 "when 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 "raises an error when self deletion/recovery is disabled" do
SiteSetting.max_post_deletions_per_day = 0
post = Fabricate(:post, user: user, topic: 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 { sign_in(user) }
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(response.parsed_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(response.parsed_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 "updates post's raw attribute" do
put "/posts/#{post.id}.json", params: { post: { raw: "edited body " } }
expect(response.status).to eq(200)
expect(response.parsed_body["post"]["raw"]).to eq("edited body")
expect(post.reload.raw).to eq("edited body")
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 { sign_in(moderator) }
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
created_at = freeze_time 1.day.ago
post = Fabricate(:post, post_type: Post.types[:whisper], user: user)
unfreeze_time
put "/posts/#{post.id}.json", params: update_params
expect(response.status).to eq(200)
expect(post.topic.reload.bumped_at).to eq_time(created_at)
end
end
describe "when logged in as group moderator" do
fab!(:topic) { Fabricate(:topic, category: category) }
fab!(:post) { Fabricate(:post, user: user, topic: topic) }
fab!(:group_user) { Fabricate(:group_user) }
let(:user_gm) { group_user.user }
let(:group) { group_user.group }
before do
SiteSetting.enable_category_group_moderation = true
post.topic.category.update!(reviewable_by_group_id: group.id, topic_id: topic.id)
sign_in(user_gm)
end
it "allows updating the category description" do
put "/posts/#{post.id}.json", params: update_params
expect(response.status).to eq(200)
post.reload
expect(post.raw).to eq("edited body")
expect(UserHistory.where(action: UserHistory.actions[:post_edit]).count).to eq(1)
end
it "can not update category descriptions in other categories" do
second_category = Fabricate(:category)
topic.update!(category: second_category)
put "/posts/#{post.id}.json", params: update_params
expect(response.status).to eq(403)
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.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
describe "with Post.plugin_permitted_update_params" do
before do
plugin = Plugin::Instance.new
plugin.add_permitted_post_update_param(:random_number) do |post, value|
post.custom_fields[:random_number] = value
post.save
end
end
after { DiscoursePluginRegistry.reset! }
it "calls blocks passed into `add_permitted_post_update_param`" do
sign_in(post.user)
put "/posts/#{post.id}.json",
params: {
post: {
raw: "this is a random post",
raw_old: post.raw,
random_number: 244,
},
}
expect(response.status).to eq(200)
expect(post.reload.custom_fields[:random_number]).to eq("244")
end
end
end
describe "#destroy_bookmark" do
fab!(:post) { Fabricate(:post) }
fab!(:bookmark) { Fabricate(:bookmark, user: user, bookmarkable: post) }
before { sign_in(user) }
it "deletes the bookmark" do
bookmark_id = bookmark.id
delete "/posts/#{post.id}/bookmark.json"
expect(Bookmark.find_by(id: bookmark_id)).to eq(nil)
end
context "when the user still has bookmarks in the topic" do
before { Fabricate(:bookmark, user: user, bookmarkable: Fabricate(:post, topic: post.topic)) }
it "marks topic_bookmarked as true" do
delete "/posts/#{post.id}/bookmark.json"
expect(response.parsed_body["topic_bookmarked"]).to eq(true)
end
end
end
describe "#wiki" do
include_examples "action requires login", :put, "/posts/2/wiki.json"
describe "when logged in" do
before { sign_in(user) }
let!(:post) { post_by_user }
it "returns 400 when wiki parameter is not present" do
sign_in(admin)
put "/posts/#{post.id}/wiki.json", params: {}
expect(response.status).to eq(400)
end
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 { sign_in(moderator) }
let!(:post) { post_by_user }
it "raises an error if the user doesn't have permission to change the post type" do
sign_in(user)
put "/posts/#{post.id}/post_type.json", params: { post_type: 2 }
expect(response).to be_forbidden
end
it "returns 400 if post_type parameter is not present" do
put "/posts/#{post.id}/post_type.json", params: {}
expect(response.status).to eq(400)
end
it "returns 400 if post_type parameters is invalid" do
put "/posts/#{post.id}/post_type.json", params: { post_type: -1 }
expect(response.status).to eq(400)
end
it "can change the post type" do
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)
PostHotlinkedMedia.create!(
url: "https://example.com/image.jpg",
post: post,
status: "download_failed",
)
put "/posts/#{post.id}/rebake.json"
post.reload
expect(post.post_hotlinked_media).to eq([])
end
end
end
describe "#create" do
include_examples "action requires login", :post, "/posts.json"
before do
SiteSetting.min_first_post_typing_time = 0
SiteSetting.whispers_allowed_groups = "#{Group::AUTO_GROUPS[:staff]}"
end
context "with 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: {
raw: raw,
title: title,
wpid: 1,
},
headers: {
HTTP_API_USERNAME: user.username,
HTTP_API_KEY: master_key,
}
expect(response.status).to eq(200)
original = response.body
post "/posts.json",
params: {
raw: raw,
title: title,
wpid: 2,
},
headers: {
HTTP_API_USERNAME: user.username_lower,
HTTP_API_KEY: master_key,
}
expect(response.status).to eq(200)
expect(response.body).to eq(original)
end
it "returns a valid JSON response when the post is enqueued" do
SiteSetting.approve_unless_trust_level = 4
master_key = Fabricate(:api_key).key
post "/posts.json",
params: {
raw: "this is test post #{SecureRandom.alphanumeric}",
title: "this is a test title #{SecureRandom.alphanumeric}",
},
headers: {
HTTP_API_USERNAME: user.username,
HTTP_API_KEY: master_key,
}
expect(response.status).to eq(200)
expect(response.parsed_body["action"]).to eq("enqueued")
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: {
raw: "this is test reply 1",
topic_id: post_1.topic.id,
reply_to_post_number: 1,
},
headers: {
HTTP_API_USERNAME: user.username,
HTTP_API_KEY: master_key,
}
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: {
raw: "this is test reply 2",
topic_id: post_1.topic.id,
reply_to_post_number: 1,
import_mode: true,
},
headers: {
HTTP_API_USERNAME: user.username,
HTTP_API_KEY: master_key,
}
expect(response.status).to eq(200)
expect(post_1.topic.user.notifications.count).to eq(0)
post "/posts.json",
params: {
raw: "this is test reply 3",
topic_id: post_1.topic.id,
reply_to_post_number: 1,
import_mode: false,
},
headers: {
HTTP_API_USERNAME: user.username,
HTTP_API_KEY: master_key,
}
expect(response.status).to eq(200)
expect(post_1.topic.user.notifications.count).to eq(1)
end
it "allows a topic to be created with an external_id" do
master_key = Fabricate(:api_key).key
post "/posts.json",
params: {
raw: "this is the test content",
title: "this is some post",
external_id: "external_id",
},
headers: {
HTTP_API_USERNAME: user.username,
HTTP_API_KEY: master_key,
}
expect(response.status).to eq(200)
new_topic = Topic.last
expect(new_topic.external_id).to eq("external_id")
end
it "prevents whispers for regular users" do
post_1 = Fabricate(:post)
user_key = ApiKey.create!(user: user).key
post "/posts.json",
params: {
raw: "this is test whisper",
topic_id: post_1.topic.id,
reply_to_post_number: 1,
whisper: true,
},
headers: {
HTTP_API_USERNAME: user.username,
HTTP_API_KEY: user_key,
}
expect(response.status).to eq(403)
end
it "does not advance draft" do
Draft.set(user, Draft::NEW_TOPIC, 0, "test")
user_key = ApiKey.create!(user: user).key
post "/posts.json",
params: {
title: "this is a test topic",
raw: "this is test whisper",
},
headers: {
HTTP_API_USERNAME: user.username,
HTTP_API_KEY: user_key,
}
expect(response.status).to eq(200)
expect(Draft.get(user, Draft::NEW_TOPIC, 0)).to eq("test")
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: {
title: "this is a test title",
raw: "this is test body",
category: "invalid",
},
headers: {
HTTP_API_USERNAME: user.username,
HTTP_API_KEY: master_key,
}
expect(response.status).to eq(400)
expect(response.parsed_body["errors"]).to include(
I18n.t("invalid_params", message: "category"),
)
end
it "will raise an error if specified embed_url is invalid" do
user = Fabricate(:admin)
master_key = Fabricate(:api_key).key
post "/posts.json",
params: {
title: "this is a test title",
raw: "this is test body",
embed_url: "/test.txt",
},
headers: {
HTTP_API_USERNAME: user.username,
HTTP_API_KEY: master_key,
}
expect(response.status).to eq(422)
end
it "creates unlisted topic with admin master key" do
master_key = Fabricate(:api_key).key
expect do
post "/posts.json",
params: {
raw: "this is a test title",
title: "this is test body",
unlist_topic: true,
},
headers: {
HTTP_API_USERNAME: admin.username,
HTTP_API_KEY: master_key,
}
end.to change { Topic.count }.by(1)
expect(response.status).to eq(200)
expect(Topic.find(response.parsed_body["topic_id"]).visible).to eq(false)
end
it "prevents creation of unlisted topic with non-admin key" do
user_key = ApiKey.create!(user: user).key
expect do
post "/posts.json",
params: {
raw: "this is a test title",
title: "this is test body",
unlist_topic: true,
},
headers: {
HTTP_API_USERNAME: user.username,
HTTP_API_KEY: user_key,
}
end.not_to change { Topic.count }
expect(response.status).to eq(422)
expect(response.parsed_body["errors"]).to include(
I18n.t("activerecord.errors.models.topic.attributes.base.unable_to_unlist"),
)
end
end
describe "when logged in" do
fab!(:user) { Fabricate(:user) }
before { sign_in(user) }
context "when 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 = response.parsed_body
expect(parsed["action"]).to eq("enqueued")
user.reload
expect(user).to be_silenced
rp = ReviewableQueuedPost.find_by(target_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
topic = Fabricate(:post, user: user).topic
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 = response.parsed_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 = response.parsed_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 = response.parsed_body
expect(parsed["action"]).not_to eq("enqueued")
end
it "doesn't enqueue replies when the post is too long (including a html comment)" do
SiteSetting.max_post_length = 10
raw = "A post <!-- " + ("a" * 3000) + "-->"
post "/posts.json", params: { raw: raw, title: "this is the test title for the topic" }
expect(response).not_to be_successful
parsed = response.parsed_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 = response.parsed_body
expect(parsed["action"]).to eq("enqueued")
reviewable = ReviewableQueuedPost.find_by(target_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 "silences correctly based on silence watched words" do
SiteSetting.watched_words_regular_expressions = true
WatchedWord.create!(action: WatchedWord.actions[:silence], word: "I love candy")
WatchedWord.create!(action: WatchedWord.actions[:silence], word: "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 = response.parsed_body
expect(parsed["action"]).to eq("enqueued")
reviewable = ReviewableQueuedPost.find_by(target_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.refresh_automatic_groups!
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_recipients: 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_recipients: "test_Group",
archetype: Archetype.private_message,
}
expect(response.status).to eq(200)
parsed = response.parsed_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 "can send a message to a group with caps" do
Group.refresh_automatic_groups!
group = Group.create(name: "Test_group", messageable_level: Group::ALIAS_LEVELS[:nobody])
user1 = user
group.add(user1)
# 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_recipients: "test_Group",
archetype: Archetype.private_message,
}
expect(response.status).to eq(200)
parsed = response.parsed_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 = response.parsed_body
expect(parsed["post"]).to be_present
expect(parsed["post"]["raw"]).to eq("this is the test content")
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 "cannot 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,
}
expect(response.status).to eq(403)
end
it "cannot 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 = response.parsed_body
expect(json["errors"]).to be_present
end
it "cannot create a post with a tag when tagging is disabled" do
SiteSetting.tagging_enabled = false
tag = Fabricate(:tag)
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 = response.parsed_body
expect(json["errors"]).to be_present
end
it "cannot create a post with a tag without tagging permission" do
SiteSetting.tagging_enabled = true
SiteSetting.min_trust_level_to_tag_topics = 4
tag = Fabricate(:tag)
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 = response.parsed_body
expect(json["errors"]).to be_present
end
it "can create a post with a tag when tagging is enabled" do
SiteSetting.tagging_enabled = true
tag = Fabricate(:tag)
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(200)
expect(Post.last.topic.tags.count).to eq(1)
end
it "creates the topic and post with the right attributes" do
post "/posts.json",
params: {
raw: "this is the test content",
title: "this is the test title for the topic",
category: category.id,
}
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.visible).to eq(true)
end
context "when adding custom fields to topic via the `topic_custom_fields` param" do
it "should return a 400 response code when no custom fields has been permitted" do
sign_in(user)
post "/posts.json",
params: {
raw: "this is the test content",
title: "this is the test title for the topic",
category: category.id,
topic_custom_fields: {
xyz: "abc",
abc: "xyz",
},
}
expect(response.status).to eq(400)
expect(Topic.last.custom_fields).to eq({})
end
context "when custom fields has been permitted" do
fab!(:plugin) do
plugin = Plugin::Instance.new
plugin.register_editable_topic_custom_field(:xyz)
plugin.register_editable_topic_custom_field(:abc, staff_only: true)
plugin
end
it "should return a 400 response when trying to add a staff ony custom field for a non-staff user" do
sign_in(user)
post "/posts.json",
params: {
raw: "this is the test content",
title: "this is the test title for the topic",
category: category.id,
topic_custom_fields: {
abc: "xyz",
},
}
expect(response.status).to eq(400)
expect(Topic.last.custom_fields).to eq({})
end
it "should add custom fields to topic that is permitted for a non-staff user" do
sign_in(user)
post "/posts.json",
params: {
raw: "this is the test content",
title: "this is the test title for the topic",
category: category.id,
topic_custom_fields: {
xyz: "abc",
},
}
expect(response.status).to eq(200)
expect(Topic.last.custom_fields).to eq({ "xyz" => "abc" })
end
it "should add custom fields to topic that is permitted for a non-staff user via the deprecated `meta_data` param" do
sign_in(user)
Discourse.expects(:deprecate).with(
"the :meta_data param is deprecated, use the :topic_custom_fields param instead",
anything,
)
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)
expect(Topic.last.custom_fields).to eq({ "xyz" => "abc" })
end
it "should add custom fields to topic that is permitted for a staff user and public user" do
sign_in(Fabricate(:admin))
post "/posts.json",
params: {
raw: "this is the test content",
title: "this is the test title for the topic",
category: category.id,
topic_custom_fields: {
xyz: "abc",
abc: "xyz",
},
}
expect(response.status).to eq(200)
expect(Topic.last.custom_fields).to eq({ "xyz" => "abc", "abc" => "xyz" })
end
end
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, username: "foo_bar")
# In certain edge cases, it's possible to end up with a username
# containing characters that would normally fail to validate
user_4 = Fabricate(:user, username: "Iyi_Iyi")
user_4.update_attribute(:username, "İyi_İyi")
user_4.update_attribute(:username_lower, "İyi_İyi".downcase)
Group.refresh_automatic_groups!
post "/posts.json",
params: {
raw: "this is the test content",
archetype: "private_message",
title: "this is some post",
target_recipients: "#{user_2.username},Foo_Bar,İyi_İyi",
}
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, user_4)
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(response.parsed_body["errors"]).to include(
I18n.t("activerecord.errors.models.topic.attributes.base.no_user_selected"),
)
end
end
context "when topic_id is set" do
fab!(:topic) { Fabricate(:topic) }
it "errors when creating a private post" do
user_2 = Fabricate(:user)
post "/posts.json",
params: {
raw: "this is the test content",
archetype: "private_message",
title: "this is some post",
target_recipients: user_2.username,
topic_id: topic.id,
}
expect(response.status).to eq(422)
expect(response.parsed_body["errors"]).to include(I18n.t("create_pm_on_existing_topic"))
end
end
context "with 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",
}
expect(response.parsed_body["errors"]).to include(I18n.t(:spamming_host))
end
context "when allow_uncategorized_topics is false" do
before { SiteSetting.allow_uncategorized_topics = false }
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 { sign_in(admin) }
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
context "when `enable_user_status` site setting is enabled" do
fab!(:user_to_mention) { Fabricate(:user) }
before { SiteSetting.enable_user_status = true }
it "does not return mentioned users when `enable_user_status` site setting is disabled" do
SiteSetting.enable_user_status = false
post "/posts.json",
params: {
raw: "I am mentioning @#{user_to_mention.username}",
topic_id: topic.id,
}
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["mentioned_users"]).to eq(nil)
end
it "returns mentioned users" do
user_to_mention.set_status!("off to dentist", "tooth")
post "/posts.json",
params: {
raw: "I am mentioning @#{user_to_mention.username}",
topic_id: topic.id,
}
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["mentioned_users"].length).to be(1)
mentioned_user = json["mentioned_users"][0]
expect(mentioned_user["id"]).to be(user_to_mention.id)
expect(mentioned_user["name"]).to eq(user_to_mention.name)
expect(mentioned_user["username"]).to eq(user_to_mention.username)
status = mentioned_user["status"]
expect(status).to be_present
expect(status["emoji"]).to eq(user_to_mention.user_status.emoji)
expect(status["description"]).to eq(user_to_mention.user_status.description)
end
it "returns an empty list of mentioned users if nobody was mentioned" do
post "/posts.json", params: { raw: "No mentions here", topic_id: topic.id }
expect(response.status).to eq(200)
expect(response.parsed_body["mentioned_users"].length).to be(0)
end
it "returns an empty list of mentioned users if an nonexistent user was mentioned" do
post "/posts.json", params: { raw: "Mentioning a @stranger", topic_id: topic.id }
expect(response.status).to eq(200)
expect(response.parsed_body["mentioned_users"].length).to be(0)
end
end
end
context "with topic unlisting" do
context "when logged in as staff" do
before { sign_in(admin) }
it "creates an unlisted topic" do
expect do
post "/posts.json",
params: {
raw: "this is the test content",
title: "this is the test title for the topic",
unlist_topic: true,
}
end.to change { Topic.count }.by(1)
expect(response.status).to eq(200)
expect(Topic.find(response.parsed_body["topic_id"]).visible).to eq(false)
end
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
it "prevents creation of an unlisted topic" do
expect do
post "/posts.json",
params: {
raw: "this is the test content",
title: "this is the test title for the topic",
unlist_topic: true,
}
end.not_to change { Topic.count }
expect(response.status).to eq(422)
expect(response.parsed_body["errors"]).to include(
I18n.t("activerecord.errors.models.topic.attributes.base.unable_to_unlist"),
)
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 { sign_in(moderator) }
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 { SiteSetting.shared_drafts_category = shared_category.id }
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 = response.parsed_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) }
before { Group.refresh_automatic_groups! }
context "as a staff user" do
before { sign_in(admin) }
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_recipients: 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_recipients: 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_recipients: 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 "with topic bump" do
shared_examples "it works" do
it "should be able to skip topic bumping" do
original_bumped_at = 1.day.ago
topic = Fabricate(:topic, bumped_at: original_bumped_at)
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 eq_time(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_time(topic.posts.last.created_at)
end
end
context "with admins" do
before { sign_in(admin) }
include_examples "it works"
end
context "with moderators" do
before { sign_in(moderator) }
include_examples "it works"
end
context "with TL4 users" do
fab!(:trust_level_4) { Fabricate(:trust_level_4) }
before { sign_in(trust_level_4) }
include_examples "it works"
end
context "with 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
context "with featured links" do
it "allows to create topics with featured links" do
sign_in(user_trust_level_1)
post "/posts.json",
params: {
title: "this is the test title for the topic",
raw: "this is the test content",
featured_link: "https://discourse.org",
}
expect(response.status).to eq(200)
end
it "doesn't allow TL0 users to create topics with featured links" do
sign_in(user_trust_level_0)
post "/posts.json",
params: {
title: "this is the test title for the topic",
raw: "this is the test content",
featured_link: "https://discourse.org",
}
expect(response.status).to eq(422)
end
it "doesn't allow to create topics with featured links if featured links are disabled in settings" do
SiteSetting.topic_featured_link_enabled = false
sign_in(user_trust_level_1)
post "/posts.json",
params: {
title: "this is the test title for the topic",
raw: "this is the test content",
featured_link: "https://discourse.org",
}
expect(response.status).to eq(422)
end
it "doesn't allow to create topics with featured links in the category with forbidden feature links" do
category = Fabricate(:category, topic_featured_link_allowed: false)
sign_in(user_trust_level_1)
post "/posts.json",
params: {
title: "this is the test title for the topic",
raw: "this is the test content",
featured_link: "https://discourse.org",
category: category.id,
}
expect(response.status).to eq(422)
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 cannot 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(403)
end
end
context "when post is hidden" do
before do
post.hidden = true
post.save
end
it "throws an exception for users" do
sign_in(user)
get "/posts/#{post.id}/revisions/#{post_revision.number}.json"
expect(response.status).to eq(403)
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 "with 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 "with 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
context "with a tagged topic" do
let(:tag) { Fabricate(:tag) }
it "works" do
SiteSetting.tagging_enabled = true
post_revision.post.topic.update(tags: [tag])
get "/posts/#{post_revision.post_id}/revisions/latest.json"
expect(response.status).to eq(200)
SiteSetting.tagging_enabled = false
get "/posts/#{post_revision.post_id}/revisions/latest.json"
expect(response.status).to eq(200)
end
end
end
describe "#permanently_delete_revisions" do
before { SiteSetting.can_permanently_delete = true }
fab!(:post) do
Fabricate(
:post,
user: Fabricate(:user),
raw: "Lorem ipsum dolor sit amet, cu nam libris tractatos, ancillae senserit ius ex",
)
end
fab!(:post_with_no_revisions) do
Fabricate(
:post,
user: Fabricate(:user),
raw: "Lorem ipsum dolor sit amet, cu nam libris tractatos, ancillae senserit ius ex",
)
end
fab!(:post_revision) { Fabricate(:post_revision, post: post) }
fab!(:post_revision_2) { Fabricate(:post_revision, post: post) }
let(:post_id) { post.id }
describe "when logged in as a regular user" do
it "does not delete revisions" do
sign_in(user)
delete "/posts/#{post_id}/revisions/permanently_delete.json"
expect(response).to_not be_successful
end
end
describe "when logged in as staff" do
before { sign_in(admin) }
it "fails when post record is not found" do
delete "/posts/#{post_id + 1}/revisions/permanently_delete.json"
expect(response).to_not be_successful
end
it "fails when no post revisions are found" do
delete "/posts/#{post_with_no_revisions.id}/revisions/permanently_delete.json"
expect(response).to_not be_successful
end
it "fails when 'can_permanently_delete' setting is false" do
SiteSetting.can_permanently_delete = false
delete "/posts/#{post_id}/revisions/permanently_delete.json"
expect(response).to_not be_successful
end
it "permanently deletes revisions from post and adds a staff log" do
delete "/posts/#{post_id}/revisions/permanently_delete.json"
expect(response.status).to eq(200)
# It creates a staff log
logs =
UserHistory.find_by(
action: UserHistory.actions[:permanently_delete_post_revisions],
acting_user_id: admin.id,
post_id: post_id,
)
expect(logs).to be_present
# ensure post revisions are deleted
expect(PostRevision.where(post: post)).to eq([])
end
end
end
describe "#revert" do
include_examples "action requires login", :put, "/posts/123/revisions/2/revert.json"
fab!(:post) do
Fabricate(
:post,
user: Fabricate(:user),
raw: "Lorem ipsum dolor sit amet, cu nam libris tractatos, ancillae senserit ius ex",
)
end
let(:post_revision) do
Fabricate(
:post_revision,
post: post,
modifications: {
"raw" => ["this is original post body.", "this is edited post body."],
},
)
end
let(:blank_post_revision) do
Fabricate(
:post_revision,
post: post,
modifications: {
"edit_reason" => ["edit reason #1", "edit reason #2"],
},
)
end
let(:same_post_revision) do
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.",
],
},
)
end
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 { sign_in(moderator) }
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(response.parsed_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(response.parsed_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 { sign_in(user) }
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(response.parsed_body["cooked"]).to eq("full content")
end
end
describe "#deleted_posts" do
include_examples "action requires login", :get, "/posts/system/deleted.json"
describe "when logged in" do
before { Group.refresh_automatic_groups! }
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
describe "when limit params is invalid" do
before { sign_in(moderator) }
include_examples "invalid limit params",
"/posts/system/deleted.json",
described_class::DELETED_POSTS_MAX_LIMIT
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 "does not raise if topic has been permanently deleted" do
post = Fabricate(:post, user: admin)
PostDestroyer.new(admin, post).destroy
post.update!(topic_id: -1000)
sign_in(admin)
get "/posts/#{admin.username}/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 = response.parsed_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 = response.parsed_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 = response.parsed_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
it "renders a 404 page" do
get "/posts/0/raw"
expect(response.status).to eq(404)
expect(response.body).to include(I18n.t("page_not_found.title"))
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
it "can show whole topics" do
topic = Fabricate(:topic)
post = Fabricate(:post, topic: topic, post_number: 1, raw: "123456789")
post_2 = Fabricate(:post, topic: topic, post_number: 2, raw: "abcdefghij")
post.save
get "/raw/#{topic.id}"
expect(response.status).to eq(200)
expect(response.body).to include("123456789", "abcdefghij")
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 for JSON format" do
post = Fabricate(:private_message_post)
get "/p/#{post.id}.json"
expect(response).to be_forbidden
end
it "returns a 403 when access is denied for HTML format" do
post = Fabricate(:private_message_post)
get "/p/#{post.id}"
expect(response).to be_forbidden
expect(response.body).to have_tag("body.no-ember")
end
it "renders a 404 page" do
get "/p/0"
expect(response.status).to eq(404)
expect(response.body).to include(I18n.t("page_not_found.title"))
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 "doesn't include posts from hidden topics" do
public_post.topic.update!(visible: false)
get "/u/#{user.username}/activity.rss"
expect(response.status).to eq(200)
body = response.body
expect(body).not_to include(public_post.url)
end
it "excludes small actions" do
small_action = Fabricate(:small_action, user: user)
get "/u/#{user.username}/activity.rss"
expect(response.status).to eq(200)
body = response.body
expect(body).not_to include(small_action.canonical_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
it "returns 404 if `hide_profile_and_presence` user option is checked" do
user.user_option.update_columns(hide_profile_and_presence: true)
get "/u/#{user.username}/activity.rss"
expect(response.status).to eq(404)
get "/u/#{user.username}/activity.json"
expect(response.status).to eq(404)
end
it "succeeds when `allow_users_to_hide_profile` is false" do
user.user_option.update_columns(hide_profile_and_presence: true)
SiteSetting.allow_users_to_hide_profile = false
get "/u/#{user.username}/activity.rss"
expect(response.status).to eq(200)
get "/u/#{user.username}/activity.json"
expect(response.status).to eq(200)
end
end
describe "#latest" do
context "with 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 = response.parsed_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 "with 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
# we cache in redis, in rare cases this can cause a flaky test
PostsHelper.clear_canonical_cache!(public_post)
expect(body).to include(public_post.canonical_url)
expect(body).to_not include(private_post.url)
end
it "doesn't include posts from hidden topics" do
public_post.topic.update!(visible: false)
get "/posts.rss"
expect(response.status).to eq(200)
body = response.body
# we cache in redis, in rare cases this can cause a flaky test
PostsHelper.clear_canonical_cache!(public_post)
expect(body).not_to include(public_post.canonical_url)
end
it "excludes small actions" do
small_action = Fabricate(:small_action)
get "/posts.rss"
expect(response.status).to eq(200)
body = response.body
expect(body).not_to include(small_action.canonical_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 = response.parsed_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 content" do
post = Fabricate(:post, cooked: "WAt")
get "/posts/#{post.id}/cooked.json"
expect(response.status).to eq(200)
json = response.parsed_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) do
Fabricate(
:post,
deleted_at: 2.hours.ago,
user: Fabricate(:user),
raw_email: "email_content",
)
end
it "returns 403 when trying to view raw as user that created the post" do
sign_in(post.user)
get "/posts/#{post.id}/raw-email.json"
expect(response.status).to eq(403)
end
it "returns 403 when trying to view raw email as a normal user" do
sign_in(user)
get "/posts/#{post.id}/raw-email.json"
expect(response.status).to eq(403)
end
it "can view raw email" do
sign_in(moderator)
get "/posts/#{post.id}/raw-email.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["raw_email"]).to eq("email_content")
end
end
end
describe "#locked" do
before { sign_in(moderator) }
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
it "can create and remove notices as a moderator" do
sign_in(moderator)
raw_notice = "Hello *world*!\n\nhttps://github.com/discourse/discourse"
put "/posts/#{public_post.id}/notice.json", params: { notice: raw_notice }
expect(response.status).to eq(200)
expect(public_post.reload.custom_fields[Post::NOTICE]).to eq(
"type" => Post.notices[:custom],
"raw" => raw_notice,
"cooked" => PrettyText.cook(raw_notice, features: { onebox: false }),
)
expect(UserHistory.where(action: UserHistory.actions[:post_staff_note_create]).count).to eq(1)
put "/posts/#{public_post.id}/notice.json", params: { notice: nil }
expect(response.status).to eq(200)
expect(public_post.reload.custom_fields[Post::NOTICE]).to eq(nil)
expect(UserHistory.where(action: UserHistory.actions[:post_staff_note_destroy]).count).to eq(
1,
)
end
describe "group moderators" do
fab!(:group_user) { Fabricate(:group_user) }
let(:user) { group_user.user }
let(:group) { group_user.group }
before do
SiteSetting.enable_category_group_moderation = true
topic.category.update!(reviewable_by_group_id: group.id)
sign_in(user)
end
it "can create and remove notices as a group moderator" do
raw_notice = "Hello *world*!\n\nhttps://github.com/discourse/discourse"
put "/posts/#{public_post.id}/notice.json", params: { notice: raw_notice }
expect(response.status).to eq(200)
expect(public_post.reload.custom_fields[Post::NOTICE]).to eq(
"type" => Post.notices[:custom],
"raw" => raw_notice,
"cooked" => PrettyText.cook(raw_notice, features: { onebox: false }),
)
put "/posts/#{public_post.id}/notice.json", params: { notice: nil }
expect(response.status).to eq(200)
expect(public_post.reload.custom_fields[Post::NOTICE]).to eq(nil)
end
it "prevents a group moderator from altering notes outside of their category" do
moderatable_group = Fabricate(:group)
topic.category.update!(reviewable_by_group_id: moderatable_group.id)
put "/posts/#{public_post.id}/notice.json", params: { notice: "Hello" }
expect(response.status).to eq(404)
end
it "prevents a normal user from altering notes" do
group_user.destroy!
put "/posts/#{public_post.id}/notice.json", params: { notice: "Hello" }
expect(response.status).to eq(404)
end
end
end
describe "#pending" do
subject(:request) { get "/posts/#{user.username}/pending.json" }
context "when user is not logged in" do
it_behaves_like "action requires login", :get, "/posts/system/pending.json"
end
context "when user is logged in" do
let(:pending_posts) { response.parsed_body["pending_posts"] }
before { sign_in(current_user) }
context "when current user is the same as user" do
let(:current_user) { user }
context "when there are existing pending posts" do
let!(:owner_pending_posts) do
Fabricate.times(2, :reviewable_queued_post, created_by: user)
end
let!(:other_pending_post) { Fabricate(:reviewable_queued_post) }
let(:expected_keys) do
%w[
avatar_template
category_id
created_at
created_by_id
name
raw_text
title
topic_id
topic_url
username
]
end
it "returns user's pending posts" do
request
expect(pending_posts).to all include "id" => be_in(owner_pending_posts.map(&:id))
expect(pending_posts).to all include(*expected_keys)
end
end
context "when there aren't any pending posts" do
it "returns an empty array" do
request
expect(pending_posts).to be_empty
end
end
end
context "when current user is a staff member" do
let(:current_user) { moderator }
context "when there are existing pending posts" do
let!(:owner_pending_posts) do
Fabricate.times(2, :reviewable_queued_post, created_by: user)
end
let!(:other_pending_post) { Fabricate(:reviewable_queued_post) }
let(:expected_keys) do
%w[
avatar_template
category_id
created_at
created_by_id
name
raw_text
title
topic_id
topic_url
username
]
end
it "returns user's pending posts" do
request
expect(pending_posts).to all include "id" => be_in(owner_pending_posts.map(&:id))
expect(pending_posts).to all include(*expected_keys)
end
end
context "when there aren't any pending posts" do
it "returns an empty array" do
request
expect(pending_posts).to be_empty
end
end
end
context "when current user is another user" do
let(:current_user) { Fabricate(:user) }
it "does not allow access" do
request
expect(response).to have_http_status :not_found
end
end
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 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,
string_arg: "123",
hash_arg: {
key1: "val",
},
array_arg: %w[1 2 3],
}
end
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: %w[1 2 3])
instance.add_permitted_post_create_param(:array_arg, :array)
request.call
expect(@controller.send(:create_params)).to include(array_arg: %w[1 2 3])
end
end
end
end