mirror of
https://github.com/discourse/discourse.git
synced 2024-12-01 21:19:41 -06:00
20268385a5
Previously in some cases we would queue logging of invalid post numbers The impact would be we would miss logging an incoming link and would leak an error.
2439 lines
74 KiB
Ruby
2439 lines
74 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe TopicsController do
|
|
let(:topic) { Fabricate(:topic) }
|
|
let(:user) { Fabricate(:user) }
|
|
|
|
describe '#wordpress' do
|
|
let!(:user) { sign_in(Fabricate(:moderator)) }
|
|
let(:p1) { Fabricate(:post, user: user) }
|
|
let(:topic) { p1.topic }
|
|
let!(:p2) { Fabricate(:post, topic: topic, user: user) }
|
|
|
|
it "returns the JSON in the format our wordpress plugin needs" do
|
|
SiteSetting.external_system_avatars_enabled = false
|
|
|
|
get "/t/#{topic.id}/wordpress.json", params: { best: 3 }
|
|
|
|
expect(response.status).to eq(200)
|
|
json = ::JSON.parse(response.body)
|
|
|
|
# The JSON has the data the wordpress plugin needs
|
|
expect(json['id']).to eq(topic.id)
|
|
expect(json['posts_count']).to eq(2)
|
|
expect(json['filtered_posts_count']).to eq(2)
|
|
|
|
# Posts
|
|
expect(json['posts'].size).to eq(1)
|
|
post = json['posts'][0]
|
|
expect(post['id']).to eq(p2.id)
|
|
expect(post['username']).to eq(user.username)
|
|
expect(post['avatar_template']).to eq("#{Discourse.base_url_no_prefix}#{user.avatar_template}")
|
|
expect(post['name']).to eq(user.name)
|
|
expect(post['created_at']).to be_present
|
|
expect(post['cooked']).to eq(p2.cooked)
|
|
|
|
# Participants
|
|
expect(json['participants'].size).to eq(1)
|
|
participant = json['participants'][0]
|
|
expect(participant['id']).to eq(user.id)
|
|
expect(participant['username']).to eq(user.username)
|
|
expect(participant['avatar_template']).to eq("#{Discourse.base_url_no_prefix}#{user.avatar_template}")
|
|
end
|
|
end
|
|
|
|
describe '#move_posts' do
|
|
before do
|
|
SiteSetting.min_topic_title_length = 2
|
|
SiteSetting.tagging_enabled = true
|
|
end
|
|
|
|
it 'needs you to be logged in' do
|
|
post "/t/111/move-posts.json", params: {
|
|
title: 'blah',
|
|
post_ids: [1, 2, 3]
|
|
}
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
describe 'moving to a new topic' do
|
|
let(:user) { Fabricate(:user) }
|
|
let(:moderator) { Fabricate(:moderator) }
|
|
let(:p1) { Fabricate(:post, user: user, post_number: 1) }
|
|
let(:p2) { Fabricate(:post, user: user, post_number: 2, topic: p1.topic) }
|
|
let!(:topic) { p1.topic }
|
|
|
|
it "raises an error without post_ids" do
|
|
sign_in(moderator)
|
|
post "/t/#{topic.id}/move-posts.json", params: { title: 'blah' }
|
|
expect(response.status).to eq(400)
|
|
end
|
|
|
|
it "raises an error when the user doesn't have permission to move the posts" do
|
|
sign_in(user)
|
|
|
|
post "/t/#{topic.id}/move-posts.json", params: {
|
|
title: 'blah', post_ids: [p1.post_number, p2.post_number]
|
|
}
|
|
|
|
expect(response).to be_forbidden
|
|
end
|
|
|
|
it "raises an error when the OP is not a regular post" do
|
|
sign_in(moderator)
|
|
p2 = Fabricate(:post, topic: topic, post_number: 2, post_type: Post.types[:whisper])
|
|
p3 = Fabricate(:post, topic: topic, post_number: 3)
|
|
|
|
post "/t/#{topic.id}/move-posts.json", params: {
|
|
title: 'blah', post_ids: [p2.id, p3.id]
|
|
}
|
|
expect(response.status).to eq(422)
|
|
|
|
result = ::JSON.parse(response.body)
|
|
|
|
expect(result['errors']).to be_present
|
|
end
|
|
|
|
context 'success' do
|
|
before { sign_in(Fabricate(:admin)) }
|
|
|
|
it "returns success" do
|
|
expect do
|
|
post "/t/#{topic.id}/move-posts.json", params: {
|
|
title: 'Logan is a good movie',
|
|
post_ids: [p2.id],
|
|
category_id: 123,
|
|
tags: ["tag1", "tag2"]
|
|
}
|
|
end.to change { Topic.count }.by(1)
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
result = ::JSON.parse(response.body)
|
|
|
|
expect(result['success']).to eq(true)
|
|
expect(result['url']).to eq(Topic.last.relative_url)
|
|
expect(Tag.all.pluck(:name)).to contain_exactly("tag1", "tag2")
|
|
end
|
|
|
|
describe 'when topic has been deleted' do
|
|
it 'should still be able to move posts' do
|
|
PostDestroyer.new(Fabricate(:admin), topic.first_post).destroy
|
|
|
|
expect(topic.reload.deleted_at).to_not be_nil
|
|
|
|
expect do
|
|
post "/t/#{topic.id}/move-posts.json", params: {
|
|
title: 'Logan is a good movie',
|
|
post_ids: [p2.id],
|
|
category_id: 123
|
|
}
|
|
end.to change { Topic.count }.by(1)
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
result = JSON.parse(response.body)
|
|
|
|
expect(result['success']).to eq(true)
|
|
expect(result['url']).to eq(Topic.last.relative_url)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'failure' do
|
|
it "returns JSON with a false success" do
|
|
sign_in(moderator)
|
|
post "/t/#{topic.id}/move-posts.json", params: {
|
|
post_ids: [p2.id]
|
|
}
|
|
expect(response.status).to eq(200)
|
|
result = ::JSON.parse(response.body)
|
|
expect(result['success']).to eq(false)
|
|
expect(result['url']).to be_blank
|
|
end
|
|
end
|
|
|
|
describe "moving replied posts" do
|
|
context 'success' do
|
|
it "moves the child posts too" do
|
|
user = sign_in(Fabricate(:moderator))
|
|
p1 = Fabricate(:post, topic: topic, user: user)
|
|
p2 = Fabricate(:post, topic: topic, user: user, reply_to_post_number: p1.post_number)
|
|
PostReply.create(post_id: p1.id, reply_id: p2.id)
|
|
|
|
post "/t/#{topic.id}/move-posts.json", params: {
|
|
title: 'new topic title',
|
|
post_ids: [p1.id],
|
|
reply_post_ids: [p1.id]
|
|
}
|
|
expect(response.status).to eq(200)
|
|
|
|
p1.reload
|
|
p2.reload
|
|
|
|
new_topic_id = JSON.parse(response.body)["url"].split("/").last.to_i
|
|
new_topic = Topic.find(new_topic_id)
|
|
expect(p1.topic.id).to eq(new_topic.id)
|
|
expect(p2.topic.id).to eq(new_topic.id)
|
|
expect(p2.reply_to_post_number).to eq(p1.post_number)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'moving to an existing topic' do
|
|
let!(:user) { sign_in(Fabricate(:moderator)) }
|
|
let(:p1) { Fabricate(:post, user: user) }
|
|
let(:topic) { p1.topic }
|
|
let(:dest_topic) { Fabricate(:topic) }
|
|
let(:p2) { Fabricate(:post, user: user, topic: topic) }
|
|
|
|
context 'success' do
|
|
it "returns success" do
|
|
user
|
|
post "/t/#{topic.id}/move-posts.json", params: {
|
|
post_ids: [p2.id],
|
|
destination_topic_id: dest_topic.id
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
result = ::JSON.parse(response.body)
|
|
expect(result['success']).to eq(true)
|
|
expect(result['url']).to be_present
|
|
end
|
|
|
|
it "triggers an event on merge" do
|
|
begin
|
|
called = false
|
|
|
|
assert = -> (original_topic, destination_topic) do
|
|
called = true
|
|
expect(original_topic).to eq(topic)
|
|
expect(destination_topic).to eq(dest_topic)
|
|
end
|
|
|
|
DiscourseEvent.on(:topic_merged, &assert)
|
|
|
|
post "/t/#{topic.id}/move-posts.json", params: {
|
|
post_ids: [p2.id],
|
|
destination_topic_id: dest_topic.id
|
|
}
|
|
|
|
expect(called).to eq(true)
|
|
expect(response.status).to eq(200)
|
|
ensure
|
|
DiscourseEvent.off(:topic_merged, &assert)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'failure' do
|
|
let(:p2) { Fabricate(:post, user: user) }
|
|
it "returns JSON with a false success" do
|
|
post "/t/#{topic.id}/move-posts.json", params: {
|
|
post_ids: [p2.id]
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
result = ::JSON.parse(response.body)
|
|
expect(result['success']).to eq(false)
|
|
expect(result['url']).to be_blank
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#merge_topic' do
|
|
it 'needs you to be logged in' do
|
|
post "/t/111/merge-topic.json", params: {
|
|
destination_topic_id: 345
|
|
}
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
describe 'moving to a new topic' do
|
|
let(:moderator) { Fabricate(:moderator) }
|
|
let(:user) { Fabricate(:user) }
|
|
let(:p1) { Fabricate(:post, user: user) }
|
|
let(:topic) { p1.topic }
|
|
|
|
it "raises an error without destination_topic_id" do
|
|
sign_in(moderator)
|
|
post "/t/#{topic.id}/merge-topic.json"
|
|
expect(response.status).to eq(400)
|
|
end
|
|
|
|
it "raises an error when the user doesn't have permission to merge" do
|
|
sign_in(user)
|
|
post "/t/111/merge-topic.json", params: { destination_topic_id: 345 }
|
|
expect(response).to be_forbidden
|
|
end
|
|
|
|
let(:dest_topic) { Fabricate(:topic) }
|
|
|
|
context 'moves all the posts to the destination topic' do
|
|
it "returns success" do
|
|
sign_in(moderator)
|
|
post "/t/#{topic.id}/merge-topic.json", params: {
|
|
destination_topic_id: dest_topic.id
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
result = ::JSON.parse(response.body)
|
|
expect(result['success']).to eq(true)
|
|
expect(result['url']).to be_present
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#change_post_owners' do
|
|
it 'needs you to be logged in' do
|
|
post "/t/111/change-owner.json", params: {
|
|
username: 'user_a',
|
|
post_ids: [1, 2, 3]
|
|
}
|
|
expect(response).to be_forbidden
|
|
end
|
|
|
|
describe 'forbidden to moderators' do
|
|
before do
|
|
sign_in(Fabricate(:moderator))
|
|
end
|
|
it 'correctly denies' do
|
|
post "/t/111/change-owner.json", params: {
|
|
topic_id: 111, username: 'user_a', post_ids: [1, 2, 3]
|
|
}
|
|
expect(response).to be_forbidden
|
|
end
|
|
end
|
|
|
|
describe 'forbidden to trust_level_4s' do
|
|
before do
|
|
sign_in(Fabricate(:trust_level_4))
|
|
end
|
|
|
|
it 'correctly denies' do
|
|
post "/t/111/change-owner.json", params: {
|
|
topic_id: 111, username: 'user_a', post_ids: [1, 2, 3]
|
|
}
|
|
expect(response).to be_forbidden
|
|
end
|
|
end
|
|
|
|
describe 'changing ownership' do
|
|
let!(:editor) { sign_in(Fabricate(:admin)) }
|
|
let(:topic) { Fabricate(:topic) }
|
|
let(:user_a) { Fabricate(:user) }
|
|
let(:p1) { Fabricate(:post, topic: topic) }
|
|
let(:p2) { Fabricate(:post, topic: topic) }
|
|
|
|
it "raises an error with a parameter missing" do
|
|
[
|
|
{ post_ids: [1, 2, 3] },
|
|
{ username: 'user_a' }
|
|
].each do |params|
|
|
post "/t/111/change-owner.json", params: params
|
|
expect(response.status).to eq(400)
|
|
end
|
|
end
|
|
|
|
it "changes the topic and posts ownership" do
|
|
post "/t/#{topic.id}/change-owner.json", params: {
|
|
username: user_a.username_lower, post_ids: [p1.id]
|
|
}
|
|
topic.reload
|
|
p1.reload
|
|
expect(response.status).to eq(200)
|
|
expect(topic.user.username).to eq(user_a.username)
|
|
expect(p1.user.username).to eq(user_a.username)
|
|
end
|
|
|
|
it "changes multiple posts" do
|
|
post "/t/#{topic.id}/change-owner.json", params: {
|
|
username: user_a.username_lower, post_ids: [p1.id, p2.id]
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
p1.reload
|
|
p2.reload
|
|
|
|
expect(p1.user).to_not eq(nil)
|
|
expect(p1.reload.user).to eq(p2.reload.user)
|
|
end
|
|
|
|
it "works with deleted users" do
|
|
deleted_user = Fabricate(:user)
|
|
t2 = Fabricate(:topic, user: deleted_user)
|
|
p3 = Fabricate(:post, topic: t2, user: deleted_user)
|
|
|
|
UserDestroyer.new(editor).destroy(deleted_user, delete_posts: true, context: 'test', delete_as_spammer: true)
|
|
|
|
post "/t/#{t2.id}/change-owner.json", params: {
|
|
username: user_a.username_lower, post_ids: [p3.id]
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
t2.reload
|
|
p3.reload
|
|
expect(t2.deleted_at).to be_nil
|
|
expect(p3.user).to eq(user_a)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#change_timestamps' do
|
|
let(:params) { { timestamp: Time.zone.now } }
|
|
|
|
it 'needs you to be logged in' do
|
|
put "/t/1/change-timestamp.json", params: params
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
[:moderator, :trust_level_4].each do |user|
|
|
describe "forbidden to #{user}" do
|
|
let!(user) { sign_in(Fabricate(user)) }
|
|
|
|
it 'correctly denies' do
|
|
put "/t/1/change-timestamp.json", params: params
|
|
expect(response).to be_forbidden
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'changing timestamps' do
|
|
let!(:admin) { sign_in(Fabricate(:admin)) }
|
|
let(:old_timestamp) { Time.zone.now }
|
|
let(:new_timestamp) { old_timestamp - 1.day }
|
|
let!(:topic) { Fabricate(:topic, created_at: old_timestamp) }
|
|
let!(:p1) { Fabricate(:post, topic: topic, created_at: old_timestamp) }
|
|
let!(:p2) { Fabricate(:post, topic: topic, created_at: old_timestamp + 1.day) }
|
|
|
|
it 'should update the timestamps of selected posts' do
|
|
# try to see if we fail with invalid first
|
|
put "/t/1/change-timestamp.json"
|
|
expect(response.status).to eq(400)
|
|
|
|
put "/t/#{topic.id}/change-timestamp.json", params: {
|
|
timestamp: new_timestamp.to_f
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(topic.reload.created_at).to be_within_one_second_of(new_timestamp)
|
|
expect(p1.reload.created_at).to be_within_one_second_of(new_timestamp)
|
|
expect(p2.reload.created_at).to be_within_one_second_of(old_timestamp)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#clear_pin' do
|
|
it 'needs you to be logged in' do
|
|
put "/t/1/clear-pin.json"
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
context 'when logged in' do
|
|
let(:topic) { Fabricate(:topic) }
|
|
let(:pm) { Fabricate(:private_message_topic) }
|
|
let(:user) { Fabricate(:user) }
|
|
before do
|
|
sign_in(user)
|
|
end
|
|
|
|
it "fails when the user can't see the topic" do
|
|
put "/t/#{pm.id}/clear-pin.json"
|
|
expect(response).to be_forbidden
|
|
end
|
|
|
|
describe 'when the user can see the topic' do
|
|
it "succeeds" do
|
|
expect do
|
|
put "/t/#{topic.id}/clear-pin.json"
|
|
end.to change { TopicUser.where(topic_id: topic.id, user_id: user.id).count }.by(1)
|
|
expect(response.status).to eq(200)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#status' do
|
|
it 'needs you to be logged in' do
|
|
put "/t/1/status.json", params: {
|
|
status: 'visible', enabled: true
|
|
}
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
describe 'when logged in' do
|
|
let(:user) { Fabricate(:user) }
|
|
let(:moderator) { Fabricate(:moderator) }
|
|
let(:topic) { Fabricate(:topic) }
|
|
before do
|
|
sign_in(moderator)
|
|
end
|
|
|
|
it "raises an exception if you can't change it" do
|
|
sign_in(user)
|
|
put "/t/#{topic.id}/status.json", params: {
|
|
status: 'visible', enabled: 'true'
|
|
}
|
|
expect(response).to be_forbidden
|
|
end
|
|
|
|
it 'requires the status parameter' do
|
|
put "/t/#{topic.id}/status.json", params: { enabled: true }
|
|
expect(response.status).to eq(400)
|
|
end
|
|
|
|
it 'requires the enabled parameter' do
|
|
put "/t/#{topic.id}/status.json", params: { status: 'visible' }
|
|
expect(response.status).to eq(400)
|
|
end
|
|
|
|
it 'raises an error with a status not in the whitelist' do
|
|
put "/t/#{topic.id}/status.json", params: {
|
|
status: 'title', enabled: 'true'
|
|
}
|
|
expect(response.status).to eq(400)
|
|
end
|
|
|
|
it 'should update the status of the topic correctly' do
|
|
topic = Fabricate(:topic, user: user, closed: true)
|
|
Fabricate(:topic_timer, topic: topic, status_type: TopicTimer.types[:open])
|
|
|
|
put "/t/#{topic.id}/status.json", params: {
|
|
status: 'closed', enabled: 'false'
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(topic.reload.closed).to eq(false)
|
|
expect(topic.topic_timers).to eq([])
|
|
|
|
body = JSON.parse(response.body)
|
|
|
|
expect(body['topic_status_update']).to eq(nil)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#destroy_timings' do
|
|
it 'needs you to be logged in' do
|
|
delete "/t/1/timings.json"
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
def topic_user_post_timings_count(user, topic)
|
|
[TopicUser, PostTiming].map do |klass|
|
|
klass.where(user: user, topic: topic).count
|
|
end
|
|
end
|
|
|
|
context 'for last post only' do
|
|
|
|
it 'should allow you to retain topic timing but remove last post only' do
|
|
post1 = create_post
|
|
topic = post1.topic
|
|
|
|
post2 = create_post(topic_id: topic.id)
|
|
|
|
PostTiming.create!(topic: topic, user: user, post_number: 1, msecs: 100)
|
|
PostTiming.create!(topic: topic, user: user, post_number: 2, msecs: 100)
|
|
|
|
TopicUser.create!(
|
|
topic: topic,
|
|
user: user,
|
|
last_read_post_number: 2,
|
|
highest_seen_post_number: 2
|
|
)
|
|
|
|
sign_in(user)
|
|
|
|
delete "/t/#{topic.id}/timings.json?last=1"
|
|
|
|
expect(PostTiming.where(topic: topic, user: user, post_number: 2).exists?).to eq(false)
|
|
expect(PostTiming.where(topic: topic, user: user, post_number: 1).exists?).to eq(true)
|
|
|
|
expect(TopicUser.where(topic: topic, user: user, last_read_post_number: 1, highest_seen_post_number: 1).exists?).to eq(true)
|
|
|
|
PostDestroyer.new(Fabricate(:admin), post2).destroy
|
|
|
|
delete "/t/#{topic.id}/timings.json?last=1"
|
|
|
|
expect(PostTiming.where(topic: topic, user: user, post_number: 1).exists?).to eq(false)
|
|
expect(TopicUser.where(topic: topic, user: user, last_read_post_number: nil, highest_seen_post_number: nil).exists?).to eq(true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
context 'when logged in' do
|
|
before do
|
|
@user = sign_in(Fabricate(:user))
|
|
@topic = Fabricate(:topic, user: @user)
|
|
Fabricate(:post, user: @user, topic: @topic, post_number: 2)
|
|
TopicUser.create!(topic: @topic, user: @user)
|
|
PostTiming.create!(topic: @topic, user: @user, post_number: 2, msecs: 1000)
|
|
end
|
|
|
|
it 'deletes the forum topic user and post timings records' do
|
|
expect do
|
|
delete "/t/#{@topic.id}/timings.json"
|
|
end.to change { topic_user_post_timings_count(@user, @topic) }.from([1, 1]).to([0, 0])
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#mute/unmute' do
|
|
it 'needs you to be logged in' do
|
|
put "/t/99/mute.json"
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
it 'needs you to be logged in' do
|
|
put "/t/99/unmute.json"
|
|
expect(response.status).to eq(403)
|
|
end
|
|
end
|
|
|
|
describe '#recover' do
|
|
it "won't allow us to recover a topic when we're not logged in" do
|
|
put "/t/1/recover.json"
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
describe 'when logged in' do
|
|
let(:user) { Fabricate(:user) }
|
|
let(:moderator) { Fabricate(:moderator) }
|
|
let(:topic) { Fabricate(:topic, user: user, deleted_at: Time.now, deleted_by: moderator) }
|
|
let!(:post) { Fabricate(:post, user: user, topic: topic, post_number: 1, deleted_at: Time.now, deleted_by: moderator) }
|
|
|
|
describe 'without access' do
|
|
it "raises an exception when the user doesn't have permission to delete the topic" do
|
|
sign_in(user)
|
|
put "/t/#{topic.id}/recover.json"
|
|
expect(response).to be_forbidden
|
|
end
|
|
end
|
|
|
|
context 'with permission' do
|
|
before do
|
|
sign_in(moderator)
|
|
end
|
|
|
|
it 'succeeds' do
|
|
put "/t/#{topic.id}/recover.json"
|
|
topic.reload
|
|
post.reload
|
|
expect(response.status).to eq(200)
|
|
expect(topic.trashed?).to be_falsey
|
|
expect(post.trashed?).to be_falsey
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#delete' do
|
|
it "won't allow us to delete a topic when we're not logged in" do
|
|
delete "/t/1.json"
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
describe 'when logged in' do
|
|
let(:user) { Fabricate(:user) }
|
|
let(:moderator) { Fabricate(:moderator) }
|
|
let(:topic) { Fabricate(:topic, user: user) }
|
|
let!(:post) { Fabricate(:post, topic: topic, user: user, post_number: 1) }
|
|
|
|
describe 'without access' do
|
|
it "raises an exception when the user doesn't have permission to delete the topic" do
|
|
sign_in(user)
|
|
delete "/t/#{topic.id}.json"
|
|
expect(response).to be_forbidden
|
|
end
|
|
end
|
|
|
|
describe 'with permission' do
|
|
before do
|
|
sign_in(moderator)
|
|
end
|
|
|
|
it 'succeeds' do
|
|
delete "/t/#{topic.id}.json"
|
|
expect(response.status).to eq(200)
|
|
topic.reload
|
|
expect(topic.trashed?).to be_truthy
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#id_for_slug' do
|
|
let(:topic) { Fabricate(:post).topic }
|
|
let(:pm) { Fabricate(:private_message_topic) }
|
|
|
|
it "returns JSON for the slug" do
|
|
get "/t/id_for/#{topic.slug}.json"
|
|
expect(response.status).to eq(200)
|
|
json = ::JSON.parse(response.body)
|
|
expect(json['topic_id']).to eq(topic.id)
|
|
expect(json['url']).to eq(topic.url)
|
|
expect(json['slug']).to eq(topic.slug)
|
|
end
|
|
|
|
it "returns invalid access if the user can't see the topic" do
|
|
get "/t/id_for/#{pm.slug}.json"
|
|
expect(response).to be_forbidden
|
|
end
|
|
end
|
|
|
|
describe '#update' do
|
|
it "won't allow us to update a topic when we're not logged in" do
|
|
put "/t/1.json", params: { slug: 'xyz' }
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
describe 'when logged in' do
|
|
let(:topic) { Fabricate(:topic, user: user) }
|
|
|
|
before do
|
|
Fabricate(:post, topic: topic)
|
|
SiteSetting.editing_grace_period = 0
|
|
sign_in(user)
|
|
end
|
|
|
|
it 'can not change category to a disallowed category' do
|
|
category = Fabricate(:category)
|
|
category.set_permissions(staff: :full)
|
|
category.save!
|
|
|
|
put "/t/#{topic.id}.json", params: { category_id: category.id }
|
|
|
|
expect(response.status).to eq(403)
|
|
expect(topic.reload.category_id).not_to eq(category.id)
|
|
end
|
|
|
|
it 'can not move to a category that requires topic approval' do
|
|
category = Fabricate(:category)
|
|
category.custom_fields[Category::REQUIRE_TOPIC_APPROVAL] = true
|
|
category.save!
|
|
|
|
put "/t/#{topic.id}.json", params: { category_id: category.id }
|
|
|
|
expect(response.status).to eq(403)
|
|
expect(topic.reload.category_id).not_to eq(category.id)
|
|
end
|
|
|
|
describe 'without permission' do
|
|
it "raises an exception when the user doesn't have permission to update the topic" do
|
|
topic.update!(archived: true)
|
|
put "/t/#{topic.slug}/#{topic.id}.json"
|
|
|
|
expect(response.status).to eq(403)
|
|
end
|
|
end
|
|
|
|
describe 'with permission' do
|
|
it 'succeeds' do
|
|
put "/t/#{topic.slug}/#{topic.id}.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(::JSON.parse(response.body)['basic_topic']).to be_present
|
|
end
|
|
|
|
it "can update a topic to an uncategorized topic" do
|
|
topic.update!(category: Fabricate(:category))
|
|
|
|
put "/t/#{topic.slug}/#{topic.id}.json", params: {
|
|
category_id: ""
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(topic.reload.category_id).to eq(SiteSetting.uncategorized_category_id)
|
|
end
|
|
|
|
it 'allows a change of title' do
|
|
put "/t/#{topic.slug}/#{topic.id}.json", params: {
|
|
title: 'This is a new title for the topic'
|
|
}
|
|
|
|
topic.reload
|
|
expect(topic.title).to eq('This is a new title for the topic')
|
|
end
|
|
|
|
it "returns errors with invalid titles" do
|
|
put "/t/#{topic.slug}/#{topic.id}.json", params: {
|
|
title: 'asdf'
|
|
}
|
|
|
|
expect(response.status).to eq(422)
|
|
expect(JSON.parse(response.body)['errors']).to be_present
|
|
end
|
|
|
|
it "returns errors when the rate limit is exceeded" do
|
|
EditRateLimiter.any_instance.expects(:performed!).raises(RateLimiter::LimitExceeded.new(60))
|
|
|
|
put "/t/#{topic.slug}/#{topic.id}.json", params: {
|
|
title: 'This is a new title for the topic'
|
|
}
|
|
|
|
expect(response.status).to eq(429)
|
|
end
|
|
|
|
it "returns errors with invalid categories" do
|
|
put "/t/#{topic.slug}/#{topic.id}.json", params: {
|
|
category_id: -1
|
|
}
|
|
|
|
expect(response.status).to eq(422)
|
|
end
|
|
|
|
it "doesn't call the PostRevisor when there is no changes" do
|
|
expect do
|
|
put "/t/#{topic.slug}/#{topic.id}.json", params: {
|
|
category_id: topic.category_id
|
|
}
|
|
end.not_to change(PostRevision.all, :count)
|
|
|
|
expect(response.status).to eq(200)
|
|
end
|
|
|
|
context 'when topic is private' do
|
|
before do
|
|
topic.update!(
|
|
archetype: Archetype.private_message,
|
|
category: nil,
|
|
allowed_users: [topic.user]
|
|
)
|
|
end
|
|
|
|
context 'when there are no changes' do
|
|
it 'does not call the PostRevisor' do
|
|
expect do
|
|
put "/t/#{topic.slug}/#{topic.id}.json", params: {
|
|
category_id: topic.category_id
|
|
}
|
|
end.not_to change(PostRevision.all, :count)
|
|
|
|
expect(response.status).to eq(200)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "allow_uncategorized_topics is false" do
|
|
before do
|
|
SiteSetting.allow_uncategorized_topics = false
|
|
end
|
|
|
|
it "can add a category to an uncategorized topic" do
|
|
category = Fabricate(:category)
|
|
|
|
put "/t/#{topic.slug}/#{topic.id}.json", params: {
|
|
category_id: category.id
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(topic.reload.category).to eq(category)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#show' do
|
|
let(:private_topic) { Fabricate(:private_message_topic) }
|
|
let(:topic) { Fabricate(:post).topic }
|
|
|
|
let!(:p1) { Fabricate(:post, user: topic.user) }
|
|
let!(:p2) { Fabricate(:post, user: topic.user) }
|
|
|
|
describe 'when topic is not allowed' do
|
|
it 'should return the right response' do
|
|
sign_in(user)
|
|
|
|
get "/t/#{private_topic.id}.json"
|
|
|
|
expect(response.status).to eq(403)
|
|
expect(response.body).to eq(I18n.t('invalid_access'))
|
|
end
|
|
end
|
|
|
|
it 'correctly renders canoicals' do
|
|
get "/t/#{topic.id}", params: { slug: topic.slug }
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(css_select("link[rel=canonical]").length).to eq(1)
|
|
expect(response.headers["Cache-Control"]).to eq("no-cache, no-store")
|
|
end
|
|
|
|
it 'returns 301 even if slug does not match URL' do
|
|
# in the past we had special logic for unlisted topics
|
|
# we would require slug unless you made a json call
|
|
# this was not really providing any security
|
|
#
|
|
# we no longer require a topic be visible to perform url correction
|
|
# if you need to properly hide a topic for users use a secure category
|
|
# or a PM
|
|
topic = Fabricate(:topic, visible: false)
|
|
Fabricate(:post, topic: topic)
|
|
|
|
get "/t/#{topic.id}.json", params: { slug: topic.slug }
|
|
expect(response.status).to eq(200)
|
|
|
|
get "/t/#{topic.id}.json", params: { slug: "just-guessing" }
|
|
expect(response.status).to eq(301)
|
|
|
|
get "/t/#{topic.slug}.json"
|
|
expect(response.status).to eq(301)
|
|
end
|
|
|
|
it 'shows a topic correctly' do
|
|
get "/t/#{topic.slug}/#{topic.id}.json"
|
|
expect(response.status).to eq(200)
|
|
end
|
|
|
|
it 'return 404 for an invalid page' do
|
|
get "/t/#{topic.slug}/#{topic.id}.json", params: { page: 2 }
|
|
expect(response.status).to eq(404)
|
|
end
|
|
|
|
it 'can find a topic given a slug in the id param' do
|
|
get "/t/#{topic.slug}"
|
|
expect(response).to redirect_to(topic.relative_url)
|
|
end
|
|
|
|
it 'can find a topic when a slug has a number in front' do
|
|
another_topic = Fabricate(:post).topic
|
|
|
|
topic.update_column(:slug, "#{another_topic.id}-reasons-discourse-is-awesome")
|
|
get "/t/#{another_topic.id}-reasons-discourse-is-awesome"
|
|
|
|
expect(response).to redirect_to(topic.relative_url)
|
|
end
|
|
|
|
it 'keeps the post_number parameter around when redirecting' do
|
|
get "/t/#{topic.slug}", params: { post_number: 42 }
|
|
expect(response).to redirect_to(topic.relative_url + "/42")
|
|
end
|
|
|
|
it 'keeps the page around when redirecting' do
|
|
get "/t/#{topic.slug}", params: {
|
|
post_number: 42, page: 123
|
|
}
|
|
|
|
expect(response).to redirect_to(topic.relative_url + "/42?page=123")
|
|
end
|
|
|
|
it 'does not accept page params as an array' do
|
|
get "/t/#{topic.slug}", params: {
|
|
post_number: 42, page: [2]
|
|
}
|
|
|
|
expect(response).to redirect_to("#{topic.relative_url}/42?page=1")
|
|
end
|
|
|
|
it 'returns 404 when an invalid slug is given and no id' do
|
|
get "/t/nope-nope.json"
|
|
|
|
expect(response.status).to eq(404)
|
|
end
|
|
|
|
it 'returns a 404 when slug and topic id do not match a topic' do
|
|
get "/t/made-up-topic-slug/123456.json"
|
|
expect(response.status).to eq(404)
|
|
end
|
|
|
|
it 'returns a 404 for an ID that is larger than postgres limits' do
|
|
get "/t/made-up-topic-slug/5014217323220164041.json"
|
|
|
|
expect(response.status).to eq(404)
|
|
end
|
|
|
|
context 'a topic with nil slug exists' do
|
|
before do
|
|
nil_slug_topic = Fabricate(:topic)
|
|
Topic.connection.execute("update topics set slug=null where id = #{nil_slug_topic.id}") # can't find a way to set slug column to null using the model
|
|
end
|
|
|
|
it 'returns a 404 when slug and topic id do not match a topic' do
|
|
get "/t/made-up-topic-slug/123123.json"
|
|
expect(response.status).to eq(404)
|
|
end
|
|
end
|
|
|
|
context 'permission errors' do
|
|
let(:allowed_user) { Fabricate(:user) }
|
|
let(:allowed_group) { Fabricate(:group) }
|
|
let(:secure_category) do
|
|
c = Fabricate(:category)
|
|
c.permissions = [[allowed_group, :full]]
|
|
c.save
|
|
allowed_user.groups = [allowed_group]
|
|
allowed_user.save
|
|
c
|
|
end
|
|
let(:normal_topic) { Fabricate(:topic) }
|
|
let(:secure_topic) { Fabricate(:topic, category: secure_category) }
|
|
let(:private_topic) { Fabricate(:private_message_topic, user: allowed_user) }
|
|
let(:deleted_topic) { Fabricate(:deleted_topic) }
|
|
let(:deleted_secure_topic) { Fabricate(:topic, category: secure_category, deleted_at: 1.day.ago) }
|
|
let(:deleted_private_topic) { Fabricate(:private_message_topic, user: allowed_user, deleted_at: 1.day.ago) }
|
|
let(:nonexist_topic_id) { Topic.last.id + 10000 }
|
|
|
|
shared_examples "various scenarios" do |expected|
|
|
expected.each do |key, value|
|
|
it "returns #{value} for #{key}" do
|
|
slug = key == :nonexist ? "garbage-slug" : eval(key.to_s).slug
|
|
topic_id = key == :nonexist ? nonexist_topic_id : eval(key.to_s).id
|
|
get "/t/#{slug}/#{topic_id}.json"
|
|
expect(response.status).to eq(value)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'anonymous' do
|
|
expected = {
|
|
normal_topic: 200,
|
|
secure_topic: 403,
|
|
private_topic: 403,
|
|
deleted_topic: 410,
|
|
deleted_secure_topic: 403,
|
|
deleted_private_topic: 403,
|
|
nonexist: 404
|
|
}
|
|
include_examples "various scenarios", expected
|
|
end
|
|
|
|
context 'anonymous with login required' do
|
|
before do
|
|
SiteSetting.login_required = true
|
|
end
|
|
expected = {
|
|
normal_topic: 302,
|
|
secure_topic: 302,
|
|
private_topic: 302,
|
|
deleted_topic: 302,
|
|
deleted_secure_topic: 302,
|
|
deleted_private_topic: 302,
|
|
nonexist: 302
|
|
}
|
|
include_examples "various scenarios", expected
|
|
end
|
|
|
|
context 'normal user' do
|
|
before do
|
|
sign_in(Fabricate(:user))
|
|
end
|
|
|
|
expected = {
|
|
normal_topic: 200,
|
|
secure_topic: 403,
|
|
private_topic: 403,
|
|
deleted_topic: 410,
|
|
deleted_secure_topic: 403,
|
|
deleted_private_topic: 403,
|
|
nonexist: 404
|
|
}
|
|
include_examples "various scenarios", expected
|
|
end
|
|
|
|
context 'allowed user' do
|
|
before do
|
|
sign_in(allowed_user)
|
|
end
|
|
|
|
expected = {
|
|
normal_topic: 200,
|
|
secure_topic: 200,
|
|
private_topic: 200,
|
|
deleted_topic: 410,
|
|
deleted_secure_topic: 410,
|
|
deleted_private_topic: 410,
|
|
nonexist: 404
|
|
}
|
|
include_examples "various scenarios", expected
|
|
end
|
|
|
|
context 'moderator' do
|
|
before do
|
|
sign_in(Fabricate(:moderator))
|
|
end
|
|
|
|
expected = {
|
|
normal_topic: 200,
|
|
secure_topic: 403,
|
|
private_topic: 403,
|
|
deleted_topic: 200,
|
|
deleted_secure_topic: 403,
|
|
deleted_private_topic: 403,
|
|
nonexist: 404
|
|
}
|
|
include_examples "various scenarios", expected
|
|
end
|
|
|
|
context 'admin' do
|
|
before do
|
|
sign_in(Fabricate(:admin))
|
|
end
|
|
|
|
expected = {
|
|
normal_topic: 200,
|
|
secure_topic: 200,
|
|
private_topic: 200,
|
|
deleted_topic: 200,
|
|
deleted_secure_topic: 200,
|
|
deleted_private_topic: 200,
|
|
nonexist: 404
|
|
}
|
|
include_examples "various scenarios", expected
|
|
end
|
|
end
|
|
|
|
it 'records a view' do
|
|
expect do
|
|
get "/t/#{topic.slug}/#{topic.id}.json"
|
|
end.to change(TopicViewItem, :count).by(1)
|
|
end
|
|
|
|
it 'records a view to invalid post_number' do
|
|
user = Fabricate(:user)
|
|
|
|
expect do
|
|
get "/t/#{topic.slug}/#{topic.id}/#{256**4}", params: {
|
|
u: user.username
|
|
}
|
|
expect(response.status).to eq(200)
|
|
end.to change { IncomingLink.count }.by(1)
|
|
|
|
end
|
|
|
|
it 'records incoming links' do
|
|
user = Fabricate(:user)
|
|
|
|
expect do
|
|
get "/t/#{topic.slug}/#{topic.id}", params: {
|
|
u: user.username
|
|
}
|
|
end.to change { IncomingLink.count }.by(1)
|
|
end
|
|
|
|
context 'print' do
|
|
it "doesn't renders the print view when disabled" do
|
|
SiteSetting.max_prints_per_hour_per_user = 0
|
|
|
|
get "/t/#{topic.slug}/#{topic.id}/print"
|
|
|
|
expect(response).to be_forbidden
|
|
end
|
|
|
|
it 'renders the print view when enabled' do
|
|
SiteSetting.max_prints_per_hour_per_user = 10
|
|
get "/t/#{topic.slug}/#{topic.id}/print", headers: { HTTP_USER_AGENT: "Rails Testing" }
|
|
|
|
expect(response.status).to eq(200)
|
|
body = response.body
|
|
|
|
expect(body).to have_tag(:body, class: 'crawler')
|
|
expect(body).to_not have_tag(:meta, with: { name: 'fragment' })
|
|
end
|
|
|
|
it "uses the application layout when there's no param" do
|
|
SiteSetting.max_prints_per_hour_per_user = 10
|
|
get "/t/#{topic.slug}/#{topic.id}", headers: { HTTP_USER_AGENT: "Rails Testing" }
|
|
|
|
body = response.body
|
|
|
|
expect(body).to have_tag(:script, src: '/assets/application.js')
|
|
expect(body).to have_tag(:meta, with: { name: 'fragment' })
|
|
end
|
|
end
|
|
|
|
it 'records redirects' do
|
|
get "/t/#{topic.id}", headers: { HTTP_REFERER: "http://twitter.com" }
|
|
get "/t/#{topic.slug}/#{topic.id}", headers: { HTTP_REFERER: nil }
|
|
|
|
link = IncomingLink.first
|
|
expect(link.referer).to eq('http://twitter.com')
|
|
end
|
|
|
|
it 'tracks a visit for all html requests' do
|
|
sign_in(user)
|
|
get "/t/#{topic.slug}/#{topic.id}"
|
|
topic_user = TopicUser.where(user: user, topic: topic).first
|
|
expect(topic_user.last_visited_at).to eq(topic_user.first_visited_at)
|
|
end
|
|
|
|
context 'consider for a promotion' do
|
|
before do
|
|
SiteSetting.tl1_requires_topics_entered = 0
|
|
SiteSetting.tl1_requires_read_posts = 0
|
|
SiteSetting.tl1_requires_time_spent_mins = 0
|
|
SiteSetting.tl1_requires_time_spent_mins = 0
|
|
end
|
|
|
|
it "reviews the user for a promotion if they're new" do
|
|
sign_in(user)
|
|
user.update_column(:trust_level, TrustLevel[0])
|
|
get "/t/#{topic.slug}/#{topic.id}.json"
|
|
user.reload
|
|
expect(user.trust_level).to eq(1)
|
|
end
|
|
end
|
|
|
|
context 'filters' do
|
|
def extract_post_stream
|
|
json = JSON.parse(response.body)
|
|
json["post_stream"]["posts"].map { |post| post["id"] }
|
|
end
|
|
|
|
before do
|
|
TopicView.stubs(:chunk_size).returns(2)
|
|
@post_ids = topic.posts.pluck(:id)
|
|
3.times do
|
|
@post_ids << Fabricate(:post, topic: topic).id
|
|
end
|
|
end
|
|
|
|
it 'grabs the correct set of posts' do
|
|
get "/t/#{topic.slug}/#{topic.id}.json"
|
|
expect(response.status).to eq(200)
|
|
expect(extract_post_stream).to eq(@post_ids[0..1])
|
|
|
|
get "/t/#{topic.slug}/#{topic.id}.json", params: { page: 1 }
|
|
expect(response.status).to eq(200)
|
|
expect(extract_post_stream).to eq(@post_ids[0..1])
|
|
|
|
get "/t/#{topic.slug}/#{topic.id}.json", params: { page: 2 }
|
|
expect(response.status).to eq(200)
|
|
expect(extract_post_stream).to eq(@post_ids[2..3])
|
|
|
|
post_number = topic.posts.pluck(:post_number).sort[3]
|
|
get "/t/#{topic.slug}/#{topic.id}/#{post_number}.json"
|
|
expect(response.status).to eq(200)
|
|
expect(extract_post_stream).to eq(@post_ids[-2..-1])
|
|
end
|
|
end
|
|
|
|
context "when 'login required' site setting has been enabled" do
|
|
before { SiteSetting.login_required = true }
|
|
|
|
context 'and the user is logged in' do
|
|
before { sign_in(Fabricate(:coding_horror)) }
|
|
|
|
it 'shows the topic' do
|
|
get "/t/#{topic.slug}/#{topic.id}.json"
|
|
expect(response.status).to eq(200)
|
|
end
|
|
end
|
|
|
|
context 'and the user is not logged in' do
|
|
let(:api_key) { topic.user.generate_api_key(topic.user) }
|
|
|
|
it 'redirects to the login page' do
|
|
get "/t/#{topic.slug}/#{topic.id}.json"
|
|
|
|
expect(response).to redirect_to login_path
|
|
end
|
|
|
|
it 'shows the topic if valid api key is provided' do
|
|
get "/t/#{topic.slug}/#{topic.id}.json", params: { api_key: api_key.key }
|
|
|
|
expect(response.status).to eq(200)
|
|
topic.reload
|
|
expect(topic.views).to eq(1)
|
|
end
|
|
|
|
it 'returns 403 for an invalid key' do
|
|
[:json, :html].each do |format|
|
|
get "/t/#{topic.slug}/#{topic.id}.#{format}", params: { api_key: "bad" }
|
|
|
|
expect(response.code.to_i).to be(403)
|
|
expect(response.body).to include(I18n.t("invalid_access"))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
it "is included for unlisted topics" do
|
|
topic = Fabricate(:topic, visible: false)
|
|
get "/t/#{topic.slug}/#{topic.id}.json"
|
|
|
|
expect(response.headers['X-Robots-Tag']).to eq('noindex')
|
|
end
|
|
|
|
it "is not included for normal topics" do
|
|
topic = Fabricate(:topic, visible: true)
|
|
get "/t/#{topic.slug}/#{topic.id}.json"
|
|
|
|
expect(response.headers['X-Robots-Tag']).to eq(nil)
|
|
end
|
|
|
|
it "doesn't store an incoming link when there's no referer" do
|
|
expect {
|
|
get "/t/#{topic.id}.json"
|
|
}.not_to change(IncomingLink, :count)
|
|
expect(response.status).to eq(200)
|
|
end
|
|
|
|
it "doesn't raise an error on a very long link" do
|
|
get "/t/#{topic.id}.json", headers: { HTTP_REFERER: "http://#{'a' * 2000}.com" }
|
|
expect(response.status).to eq(200)
|
|
end
|
|
|
|
describe "has_escaped_fragment?" do
|
|
context "when the SiteSetting is disabled" do
|
|
it "uses the application layout even with an escaped fragment param" do
|
|
SiteSetting.enable_escaped_fragments = false
|
|
|
|
get "/t/#{topic.slug}/#{topic.id}", params: {
|
|
_escaped_fragment_: 'true'
|
|
}
|
|
|
|
body = response.body
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(body).to have_tag(:script, with: { src: '/assets/application.js' })
|
|
expect(body).to_not have_tag(:meta, with: { name: 'fragment' })
|
|
end
|
|
end
|
|
|
|
context "when the SiteSetting is enabled" do
|
|
before do
|
|
SiteSetting.enable_escaped_fragments = true
|
|
end
|
|
|
|
it "uses the application layout when there's no param" do
|
|
get "/t/#{topic.slug}/#{topic.id}"
|
|
|
|
body = response.body
|
|
|
|
expect(body).to have_tag(:script, with: { src: '/assets/application.js' })
|
|
expect(body).to have_tag(:meta, with: { name: 'fragment' })
|
|
end
|
|
|
|
it "uses the crawler layout when there's an _escaped_fragment_ param" do
|
|
get "/t/#{topic.slug}/#{topic.id}", params: {
|
|
_escaped_fragment_: true
|
|
}, headers: { HTTP_USER_AGENT: "Rails Testing" }
|
|
|
|
body = response.body
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(body).to have_tag(:body, with: { class: 'crawler' })
|
|
expect(body).to_not have_tag(:meta, with: { name: 'fragment' })
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'clear_notifications' do
|
|
it 'correctly clears notifications if specified via cookie' do
|
|
Discourse.stubs(:base_uri).returns("/eviltrout")
|
|
notification = Fabricate(:notification)
|
|
sign_in(notification.user)
|
|
|
|
cookies['cn'] = "2828,100,#{notification.id}"
|
|
|
|
get "/t/#{topic.id}.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(response.cookies['cn']).to eq(nil)
|
|
expect(response.headers['Set-Cookie']).to match(/^cn=;.*path=\/eviltrout/)
|
|
|
|
notification.reload
|
|
expect(notification.read).to eq(true)
|
|
end
|
|
|
|
it 'correctly clears notifications if specified via header' do
|
|
notification = Fabricate(:notification)
|
|
sign_in(notification.user)
|
|
|
|
get "/t/#{topic.id}.json", headers: { "Discourse-Clear-Notifications" => "2828,100,#{notification.id}" }
|
|
|
|
expect(response.status).to eq(200)
|
|
notification.reload
|
|
expect(notification.read).to eq(true)
|
|
end
|
|
end
|
|
|
|
describe "set_locale" do
|
|
def headers(locale)
|
|
{ HTTP_ACCEPT_LANGUAGE: locale }
|
|
end
|
|
|
|
context "allow_user_locale disabled" do
|
|
context "accept-language header differs from default locale" do
|
|
before do
|
|
SiteSetting.allow_user_locale = false
|
|
SiteSetting.default_locale = "en"
|
|
end
|
|
|
|
context "with an anonymous user" do
|
|
it "uses the default locale" do
|
|
get "/t/#{topic.id}.json", headers: headers("fr")
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(I18n.locale).to eq(:en)
|
|
end
|
|
end
|
|
|
|
context "with a logged in user" do
|
|
it "it uses the default locale" do
|
|
user = Fabricate(:user, locale: :fr)
|
|
sign_in(user)
|
|
|
|
get "/t/#{topic.id}.json", headers: headers("fr")
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(I18n.locale).to eq(:en)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "set_locale_from_accept_language_header enabled" do
|
|
context "accept-language header differs from default locale" do
|
|
before do
|
|
SiteSetting.allow_user_locale = true
|
|
SiteSetting.set_locale_from_accept_language_header = true
|
|
SiteSetting.default_locale = "en"
|
|
end
|
|
|
|
context "with an anonymous user" do
|
|
it "uses the locale from the headers" do
|
|
get "/t/#{topic.id}.json", headers: headers("fr")
|
|
expect(response.status).to eq(200)
|
|
expect(I18n.locale).to eq(:fr)
|
|
end
|
|
end
|
|
|
|
context "with a logged in user" do
|
|
it "uses the user's preferred locale" do
|
|
user = Fabricate(:user, locale: :fr)
|
|
sign_in(user)
|
|
|
|
get "/t/#{topic.id}.json", headers: headers("fr")
|
|
expect(response.status).to eq(200)
|
|
expect(I18n.locale).to eq(:fr)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "the preferred locale includes a region" do
|
|
it "returns the locale and region separated by an underscore" do
|
|
SiteSetting.allow_user_locale = true
|
|
SiteSetting.set_locale_from_accept_language_header = true
|
|
SiteSetting.default_locale = "en"
|
|
|
|
get "/t/#{topic.id}.json", headers: headers("zh-CN")
|
|
expect(response.status).to eq(200)
|
|
expect(I18n.locale).to eq(:zh_CN)
|
|
end
|
|
end
|
|
|
|
context 'accept-language header is not set' do
|
|
it 'uses the site default locale' do
|
|
SiteSetting.allow_user_locale = true
|
|
SiteSetting.default_locale = 'en'
|
|
|
|
get "/t/#{topic.id}.json", headers: headers("")
|
|
expect(response.status).to eq(200)
|
|
expect(I18n.locale).to eq(:en)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "read only header" do
|
|
it "returns no read only header by default" do
|
|
get "/t/#{topic.id}.json"
|
|
expect(response.status).to eq(200)
|
|
expect(response.headers['Discourse-Readonly']).to eq(nil)
|
|
end
|
|
|
|
it "returns a readonly header if the site is read only" do
|
|
Discourse.received_readonly!
|
|
get "/t/#{topic.id}.json"
|
|
expect(response.status).to eq(200)
|
|
expect(response.headers['Discourse-Readonly']).to eq('true')
|
|
end
|
|
end
|
|
|
|
describe "image only topic" do
|
|
it "uses image alt tag for meta description" do
|
|
post = Fabricate(:post, raw: "![image_description|690x405](upload://sdtr5O5xaxf0iEOxICxL36YRj86.png)")
|
|
|
|
get post.topic.url
|
|
|
|
body = response.body
|
|
expect(body).to have_tag(:meta, with: { name: 'description', content: '[image_description]' })
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#post_ids' do
|
|
let(:post) { Fabricate(:post) }
|
|
let(:topic) { post.topic }
|
|
|
|
before do
|
|
TopicView.stubs(:chunk_size).returns(1)
|
|
end
|
|
|
|
it 'returns the right post ids' do
|
|
post2 = Fabricate(:post, topic: topic)
|
|
post3 = Fabricate(:post, topic: topic)
|
|
|
|
get "/t/#{topic.id}/post_ids.json", params: {
|
|
post_number: post.post_number
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
body = JSON.parse(response.body)
|
|
|
|
expect(body["post_ids"]).to eq([post2.id, post3.id])
|
|
end
|
|
|
|
describe 'filtering by post number with filters' do
|
|
describe 'username filters' do
|
|
let(:user) { Fabricate(:user) }
|
|
let(:post) { Fabricate(:post, user: user) }
|
|
let!(:post2) { Fabricate(:post, topic: topic, user: user) }
|
|
let!(:post3) { Fabricate(:post, topic: topic) }
|
|
|
|
it 'should return the right posts' do
|
|
get "/t/#{topic.id}/post_ids.json", params: {
|
|
post_number: post.post_number,
|
|
username_filters: post2.user.username
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
body = JSON.parse(response.body)
|
|
|
|
expect(body["post_ids"]).to eq([post2.id])
|
|
end
|
|
end
|
|
|
|
describe 'summary filter' do
|
|
let!(:post2) { Fabricate(:post, topic: topic, percent_rank: 0.2) }
|
|
let!(:post3) { Fabricate(:post, topic: topic) }
|
|
|
|
it 'should return the right posts' do
|
|
get "/t/#{topic.id}/post_ids.json", params: {
|
|
post_number: post.post_number,
|
|
filter: 'summary'
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
body = JSON.parse(response.body)
|
|
|
|
expect(body["post_ids"]).to eq([post2.id])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#posts' do
|
|
let(:post) { Fabricate(:post) }
|
|
let(:topic) { post.topic }
|
|
|
|
it 'returns first post of the topic' do
|
|
get "/t/#{topic.id}/posts.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
body = JSON.parse(response.body)
|
|
|
|
expect(body["post_stream"]["posts"].first["id"]).to eq(post.id)
|
|
end
|
|
|
|
describe 'filtering by post number with filters' do
|
|
describe 'username filters' do
|
|
let!(:post2) { Fabricate(:post, topic: topic, user: Fabricate(:user)) }
|
|
let!(:post3) { Fabricate(:post, topic: topic) }
|
|
|
|
it 'should return the right posts' do
|
|
TopicView.stubs(:chunk_size).returns(2)
|
|
|
|
get "/t/#{topic.id}/posts.json", params: {
|
|
post_number: post.post_number,
|
|
username_filters: post2.user.username,
|
|
asc: true
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
body = JSON.parse(response.body)
|
|
|
|
expect(body["post_stream"]["posts"].first["id"]).to eq(post2.id)
|
|
end
|
|
end
|
|
|
|
describe 'summary filter' do
|
|
let!(:post2) { Fabricate(:post, topic: topic, percent_rank: 0.2) }
|
|
let!(:post3) { Fabricate(:post, topic: topic) }
|
|
|
|
it 'should return the right posts' do
|
|
TopicView.stubs(:chunk_size).returns(2)
|
|
|
|
get "/t/#{topic.id}/posts.json", params: {
|
|
post_number: post.post_number,
|
|
filter: 'summary',
|
|
asc: true
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
body = JSON.parse(response.body)
|
|
|
|
expect(body["post_stream"]["posts"].first["id"]).to eq(post2.id)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#feed' do
|
|
let(:topic) { Fabricate(:post).topic }
|
|
|
|
it 'renders rss of the topic' do
|
|
get "/t/foo/#{topic.id}.rss"
|
|
expect(response.status).to eq(200)
|
|
expect(response.content_type).to eq('application/rss+xml')
|
|
end
|
|
|
|
it 'renders rss of the topic correctly with subfolder' do
|
|
GlobalSetting.stubs(:relative_url_root).returns('/forum')
|
|
Discourse.stubs(:base_uri).returns("/forum")
|
|
get "/t/foo/#{topic.id}.rss"
|
|
expect(response.status).to eq(200)
|
|
expect(response.body).to_not include("/forum/forum")
|
|
expect(response.body).to include("http://test.localhost/forum/t/#{topic.slug}")
|
|
end
|
|
end
|
|
|
|
describe '#invite_group' do
|
|
let(:admins) { Group[:admins] }
|
|
|
|
let!(:admin) { sign_in(Fabricate(:admin)) }
|
|
|
|
before do
|
|
admins.messageable_level = Group::ALIAS_LEVELS[:everyone]
|
|
admins.save!
|
|
end
|
|
|
|
it "disallows inviting a group to a topic" do
|
|
topic = Fabricate(:topic)
|
|
post "/t/#{topic.id}/invite-group.json", params: {
|
|
group: 'admins'
|
|
}
|
|
|
|
expect(response.status).to eq(422)
|
|
end
|
|
|
|
it "allows inviting a group to a PM" do
|
|
topic = Fabricate(:private_message_topic)
|
|
post "/t/#{topic.id}/invite-group.json", params: {
|
|
group: 'admins'
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(topic.allowed_groups.first.id).to eq(admins.id)
|
|
end
|
|
end
|
|
|
|
describe '#make_banner' do
|
|
it 'needs you to be a staff member' do
|
|
sign_in(Fabricate(:user))
|
|
put "/t/99/make-banner.json"
|
|
expect(response).to be_forbidden
|
|
end
|
|
|
|
describe 'when logged in' do
|
|
it "changes the topic archetype to 'banner'" do
|
|
topic = Fabricate(:topic, user: sign_in(Fabricate(:admin)))
|
|
|
|
put "/t/#{topic.id}/make-banner.json"
|
|
expect(response.status).to eq(200)
|
|
topic.reload
|
|
expect(topic.archetype).to eq(Archetype.banner)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#remove_banner' do
|
|
it 'needs you to be a staff member' do
|
|
sign_in(Fabricate(:user))
|
|
put "/t/99/remove-banner.json"
|
|
expect(response).to be_forbidden
|
|
end
|
|
|
|
describe 'when logged in' do
|
|
it "resets the topic archetype" do
|
|
topic = Fabricate(:topic, user: sign_in(Fabricate(:admin)), archetype: Archetype.banner)
|
|
|
|
put "/t/#{topic.id}/remove-banner.json"
|
|
expect(response.status).to eq(200)
|
|
topic.reload
|
|
expect(topic.archetype).to eq(Archetype.default)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#remove_allowed_user' do
|
|
it 'admin can be removed from a pm' do
|
|
admin = sign_in(Fabricate(:admin))
|
|
user = Fabricate(:user)
|
|
pm = create_post(user: user, archetype: 'private_message', target_usernames: [user.username, admin.username])
|
|
|
|
put "/t/#{pm.topic_id}/remove-allowed-user.json", params: {
|
|
username: admin.username
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(TopicAllowedUser.where(topic_id: pm.topic_id, user_id: admin.id).first).to eq(nil)
|
|
end
|
|
end
|
|
|
|
describe '#bulk' do
|
|
it 'needs you to be logged in' do
|
|
put "/topics/bulk.json"
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
describe "when logged in" do
|
|
let!(:user) { sign_in(Fabricate(:user)) }
|
|
let(:operation) { { type: 'change_category', category_id: '1' } }
|
|
let(:topic_ids) { [1, 2, 3] }
|
|
|
|
it "requires a list of topic_ids or filter" do
|
|
put "/topics/bulk.json", params: { operation: operation }
|
|
expect(response.status).to eq(400)
|
|
end
|
|
|
|
it "requires an operation param" do
|
|
put "/topics/bulk.json", params: { topic_ids: topic_ids }
|
|
expect(response.status).to eq(400)
|
|
end
|
|
|
|
it "requires a type field for the operation param" do
|
|
put "/topics/bulk.json", params: { topic_ids: topic_ids, operation: {} }
|
|
expect(response.status).to eq(400)
|
|
end
|
|
|
|
it "can find unread" do
|
|
# mark all unread muted
|
|
put "/topics/bulk.json", params: {
|
|
filter: 'unread', operation: { type: :change_notification_level, notification_level_id: 0 }
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
end
|
|
|
|
it "delegates work to `TopicsBulkAction`" do
|
|
topics_bulk_action = mock
|
|
TopicsBulkAction.expects(:new).with(user, topic_ids, operation, group: nil).returns(topics_bulk_action)
|
|
topics_bulk_action.expects(:perform!)
|
|
|
|
put "/topics/bulk.json", params: {
|
|
topic_ids: topic_ids, operation: operation
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#remove_bookmarks' do
|
|
it "should remove bookmarks properly from non first post" do
|
|
bookmark = PostActionType.types[:bookmark]
|
|
user = sign_in(Fabricate(:user))
|
|
|
|
post = create_post
|
|
post2 = create_post(topic_id: post.topic_id)
|
|
|
|
PostAction.act(user, post2, bookmark)
|
|
|
|
put "/t/#{post.topic_id}/bookmark.json"
|
|
expect(PostAction.where(user_id: user.id, post_action_type: bookmark).count).to eq(2)
|
|
|
|
put "/t/#{post.topic_id}/remove_bookmarks.json"
|
|
expect(PostAction.where(user_id: user.id, post_action_type: bookmark).count).to eq(0)
|
|
end
|
|
|
|
it "should disallow bookmarks on posts you have no access to" do
|
|
sign_in(Fabricate(:user))
|
|
user = Fabricate(:user)
|
|
pm = create_post(user: user, archetype: 'private_message', target_usernames: [user.username])
|
|
|
|
put "/t/#{pm.topic_id}/bookmark.json"
|
|
expect(response).to be_forbidden
|
|
end
|
|
end
|
|
|
|
describe '#reset_new' do
|
|
let(:user) { sign_in(Fabricate(:user)) }
|
|
it 'needs you to be logged in' do
|
|
put "/topics/reset-new.json"
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
it "updates the `new_since` date" do
|
|
old_date = 2.years.ago
|
|
|
|
user.user_stat.update_column(:new_since, old_date)
|
|
|
|
put "/topics/reset-new.json"
|
|
expect(response.status).to eq(200)
|
|
user.reload
|
|
expect(user.user_stat.new_since.to_date).not_to eq(old_date.to_date)
|
|
end
|
|
end
|
|
|
|
describe '#feature_stats' do
|
|
it "works" do
|
|
get "/topics/feature_stats.json", params: { category_id: 1 }
|
|
|
|
expect(response.status).to eq(200)
|
|
json = JSON.parse(response.body)
|
|
expect(json["pinned_in_category_count"]).to eq(0)
|
|
expect(json["pinned_globally_count"]).to eq(0)
|
|
expect(json["banner_count"]).to eq(0)
|
|
end
|
|
|
|
it "allows unlisted banner topic" do
|
|
Fabricate(:topic, category_id: 1, archetype: Archetype.banner, visible: false)
|
|
|
|
get "/topics/feature_stats.json", params: { category_id: 1 }
|
|
json = JSON.parse(response.body)
|
|
expect(json["banner_count"]).to eq(1)
|
|
end
|
|
end
|
|
|
|
describe '#excerpts' do
|
|
it "can correctly get excerpts" do
|
|
first_post = create_post(raw: 'This is the first post :)', title: 'This is a test title I am making yay')
|
|
second_post = create_post(raw: 'This is second post', topic: first_post.topic)
|
|
|
|
random_post = Fabricate(:post)
|
|
|
|
get "/t/#{first_post.topic_id}/excerpts.json", params: {
|
|
post_ids: [first_post.id, second_post.id, random_post.id]
|
|
}
|
|
|
|
json = JSON.parse(response.body)
|
|
json.sort! { |a, b| a["post_id"] <=> b["post_id"] }
|
|
|
|
# no random post
|
|
expect(json.length).to eq(2)
|
|
# keep emoji images
|
|
expect(json[0]["excerpt"]).to match(/emoji/)
|
|
expect(json[0]["excerpt"]).to match(/first post/)
|
|
expect(json[0]["username"]).to eq(first_post.user.username)
|
|
expect(json[0]["post_id"]).to eq(first_post.id)
|
|
|
|
expect(json[1]["excerpt"]).to match(/second post/)
|
|
end
|
|
end
|
|
|
|
describe '#convert_topic' do
|
|
it 'needs you to be logged in' do
|
|
put "/t/111/convert-topic/private.json"
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
describe 'converting public topic to private message' do
|
|
let(:user) { Fabricate(:user) }
|
|
let(:topic) { Fabricate(:topic, user: user) }
|
|
|
|
it "raises an error when the user doesn't have permission to convert topic" do
|
|
sign_in(Fabricate(:user))
|
|
put "/t/#{topic.id}/convert-topic/private.json"
|
|
expect(response).to be_forbidden
|
|
end
|
|
|
|
context "success" do
|
|
it "returns success" do
|
|
sign_in(Fabricate(:admin))
|
|
put "/t/#{topic.id}/convert-topic/private.json"
|
|
|
|
topic.reload
|
|
expect(topic.archetype).to eq(Archetype.private_message)
|
|
expect(response.status).to eq(200)
|
|
|
|
result = ::JSON.parse(response.body)
|
|
expect(result['success']).to eq(true)
|
|
expect(result['url']).to be_present
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'converting private message to public topic' do
|
|
let(:user) { Fabricate(:user) }
|
|
let(:topic) { Fabricate(:private_message_topic, user: user) }
|
|
|
|
it "raises an error when the user doesn't have permission to convert topic" do
|
|
sign_in(Fabricate(:user))
|
|
put "/t/#{topic.id}/convert-topic/public.json"
|
|
expect(response).to be_forbidden
|
|
end
|
|
|
|
context "success" do
|
|
it "returns success" do
|
|
sign_in(Fabricate(:admin))
|
|
put "/t/#{topic.id}/convert-topic/public.json"
|
|
|
|
topic.reload
|
|
expect(topic.archetype).to eq(Archetype.default)
|
|
expect(response.status).to eq(200)
|
|
|
|
result = ::JSON.parse(response.body)
|
|
expect(result['success']).to eq(true)
|
|
expect(result['url']).to be_present
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#timings' do
|
|
let(:post_1) { Fabricate(:post, topic: topic) }
|
|
|
|
it 'should record the timing' do
|
|
sign_in(user)
|
|
|
|
post "/topics/timings.json", params: {
|
|
topic_id: topic.id,
|
|
topic_time: 5,
|
|
timings: { post_1.post_number => 2 }
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
post_timing = PostTiming.first
|
|
|
|
expect(post_timing.topic).to eq(topic)
|
|
expect(post_timing.user).to eq(user)
|
|
expect(post_timing.msecs).to eq(2)
|
|
end
|
|
end
|
|
|
|
describe '#timer' do
|
|
context 'when a user is not logged in' do
|
|
it 'should return the right response' do
|
|
post "/t/#{topic.id}/timer.json", params: {
|
|
time: '24',
|
|
status_type: TopicTimer.types[1]
|
|
}
|
|
expect(response.status).to eq(403)
|
|
end
|
|
end
|
|
|
|
context 'when does not have permission' do
|
|
it 'should return the right response' do
|
|
sign_in(user)
|
|
|
|
post "/t/#{topic.id}/timer.json", params: {
|
|
time: '24',
|
|
status_type: TopicTimer.types[1]
|
|
}
|
|
|
|
expect(response.status).to eq(403)
|
|
expect(JSON.parse(response.body)["error_type"]).to eq('invalid_access')
|
|
end
|
|
end
|
|
|
|
context 'when logged in as an admin' do
|
|
let(:admin) { Fabricate(:admin) }
|
|
|
|
before do
|
|
sign_in(admin)
|
|
end
|
|
|
|
it 'should be able to create a topic status update' do
|
|
post "/t/#{topic.id}/timer.json", params: {
|
|
time: 24,
|
|
status_type: TopicTimer.types[1]
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
topic_status_update = TopicTimer.last
|
|
|
|
expect(topic_status_update.topic).to eq(topic)
|
|
|
|
expect(topic_status_update.execute_at)
|
|
.to be_within(1.second).of(24.hours.from_now)
|
|
|
|
json = JSON.parse(response.body)
|
|
|
|
expect(DateTime.parse(json['execute_at']))
|
|
.to be_within(1.seconds).of(DateTime.parse(topic_status_update.execute_at.to_s))
|
|
|
|
expect(json['duration']).to eq(topic_status_update.duration)
|
|
expect(json['closed']).to eq(topic.reload.closed)
|
|
end
|
|
|
|
it 'should be able to delete a topic status update' do
|
|
Fabricate(:topic_timer, topic: topic)
|
|
|
|
post "/t/#{topic.id}/timer.json", params: {
|
|
time: nil,
|
|
status_type: TopicTimer.types[1]
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(topic.reload.public_topic_timer).to eq(nil)
|
|
|
|
json = JSON.parse(response.body)
|
|
|
|
expect(json['execute_at']).to eq(nil)
|
|
expect(json['duration']).to eq(nil)
|
|
expect(json['closed']).to eq(topic.closed)
|
|
end
|
|
|
|
describe 'publishing topic to category in the future' do
|
|
it 'should be able to create the topic status update' do
|
|
post "/t/#{topic.id}/timer.json", params: {
|
|
time: 24,
|
|
status_type: TopicTimer.types[3],
|
|
category_id: topic.category_id
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
topic_status_update = TopicTimer.last
|
|
|
|
expect(topic_status_update.topic).to eq(topic)
|
|
|
|
expect(topic_status_update.execute_at)
|
|
.to be_within(1.second).of(24.hours.from_now)
|
|
|
|
expect(topic_status_update.status_type)
|
|
.to eq(TopicTimer.types[:publish_to_category])
|
|
|
|
json = JSON.parse(response.body)
|
|
|
|
expect(json['category_id']).to eq(topic.category_id)
|
|
end
|
|
end
|
|
|
|
describe 'invalid status type' do
|
|
it 'should raise the right error' do
|
|
post "/t/#{topic.id}/timer.json", params: {
|
|
time: 10,
|
|
status_type: 'something'
|
|
}
|
|
expect(response.status).to eq(400)
|
|
expect(response.body).to include('status_type')
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#invite' do
|
|
describe 'when not logged in' do
|
|
it "should return the right response" do
|
|
post "/t/#{topic.id}/invite.json", params: {
|
|
email: 'jake@adventuretime.ooo'
|
|
}
|
|
|
|
expect(response.status).to eq(403)
|
|
end
|
|
end
|
|
|
|
describe 'when logged in' do
|
|
before do
|
|
sign_in(user)
|
|
end
|
|
|
|
describe 'as a valid user' do
|
|
let(:topic) { Fabricate(:topic, user: user) }
|
|
|
|
it 'should return the right response' do
|
|
user.update!(trust_level: TrustLevel[2])
|
|
|
|
expect do
|
|
post "/t/#{topic.id}/invite.json", params: {
|
|
email: 'someguy@email.com'
|
|
}
|
|
end.to change { Invite.where(invited_by_id: user.id).count }.by(1)
|
|
|
|
expect(response.status).to eq(200)
|
|
end
|
|
end
|
|
|
|
describe 'when user is a group manager' do
|
|
let(:group) { Fabricate(:group).tap { |g| g.add_owner(user) } }
|
|
let(:private_category) { Fabricate(:private_category, group: group) }
|
|
|
|
let(:group_private_topic) do
|
|
Fabricate(:topic, category: private_category, user: user)
|
|
end
|
|
|
|
let(:recipient) { 'jake@adventuretime.ooo' }
|
|
|
|
it "should attach group to the invite" do
|
|
|
|
post "/t/#{group_private_topic.id}/invite.json", params: {
|
|
user: recipient
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(Invite.find_by(email: recipient).groups).to eq([group])
|
|
end
|
|
end
|
|
|
|
describe 'when topic id is invalid' do
|
|
it 'should return the right response' do
|
|
post "/t/999/invite.json", params: {
|
|
email: Fabricate(:user).email
|
|
}
|
|
|
|
expect(response.status).to eq(400)
|
|
end
|
|
end
|
|
|
|
it 'requires an email parameter' do
|
|
post "/t/#{topic.id}/invite.json"
|
|
expect(response.status).to eq(400)
|
|
end
|
|
|
|
describe "when PM has reached maximum allowed numbers of recipients" do
|
|
let(:user2) { Fabricate(:user) }
|
|
let(:pm) { Fabricate(:private_message_topic, user: user) }
|
|
|
|
let(:moderator) { Fabricate(:moderator) }
|
|
let(:moderator_pm) { Fabricate(:private_message_topic, user: moderator) }
|
|
|
|
before do
|
|
SiteSetting.max_allowed_message_recipients = 2
|
|
end
|
|
|
|
it "doesn't allow normal users to invite" do
|
|
post "/t/#{pm.id}/invite.json", params: {
|
|
user: user2.username
|
|
}
|
|
expect(response.status).to eq(422)
|
|
expect(JSON.parse(response.body)["errors"]).to contain_exactly(
|
|
I18n.t("pm_reached_recipients_limit", recipients_limit: SiteSetting.max_allowed_message_recipients)
|
|
)
|
|
end
|
|
|
|
it "allows staff to bypass limits" do
|
|
sign_in(moderator)
|
|
post "/t/#{moderator_pm.id}/invite.json", params: {
|
|
user: user2.username
|
|
}
|
|
expect(response.status).to eq(200)
|
|
expect(moderator_pm.reload.topic_allowed_users.count).to eq(3)
|
|
end
|
|
end
|
|
|
|
describe 'when user does not have permission to invite to the topic' do
|
|
let(:topic) { Fabricate(:private_message_topic) }
|
|
|
|
it "should return the right response" do
|
|
post "/t/#{topic.id}/invite.json", params: {
|
|
user: user.username
|
|
}
|
|
|
|
expect(response.status).to eq(403)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "when inviting a group to a topic" do
|
|
let(:group) { Fabricate(:group) }
|
|
|
|
before do
|
|
sign_in(Fabricate(:admin))
|
|
end
|
|
|
|
it "should work correctly" do
|
|
email = 'hiro@from.heros'
|
|
|
|
post "/t/#{topic.id}/invite.json", params: {
|
|
email: email, group_ids: group.id
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
groups = Invite.find_by(email: email).groups
|
|
expect(groups.count).to eq(1)
|
|
expect(groups.first.id).to eq(group.id)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'invite_group' do
|
|
let(:admins) { Group[:admins] }
|
|
let(:pm) { Fabricate(:private_message_topic) }
|
|
|
|
def invite_group(topic, expected_status)
|
|
post "/t/#{topic.id}/invite-group.json", params: { group: admins.name }
|
|
expect(response.status).to eq(expected_status)
|
|
end
|
|
|
|
before do
|
|
admins.update!(messageable_level: Group::ALIAS_LEVELS[:everyone])
|
|
end
|
|
|
|
describe 'as an anon user' do
|
|
it 'should be forbidden' do
|
|
invite_group(pm, 403)
|
|
end
|
|
end
|
|
|
|
describe 'as a normal user' do
|
|
let!(:user) { sign_in(Fabricate(:user)) }
|
|
|
|
describe 'when user does not have permission to view the topic' do
|
|
it 'should be forbidden' do
|
|
invite_group(pm, 403)
|
|
end
|
|
end
|
|
|
|
describe 'when user has permission to view the topic' do
|
|
before do
|
|
pm.allowed_users << user
|
|
end
|
|
|
|
it 'should allow user to invite group to topic' do
|
|
invite_group(pm, 200)
|
|
expect(pm.allowed_groups.first.id).to eq(admins.id)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'as an admin user' do
|
|
let!(:admin) { sign_in(Fabricate(:admin)) }
|
|
|
|
it "disallows inviting a group to a topic" do
|
|
topic = Fabricate(:topic)
|
|
invite_group(topic, 422)
|
|
end
|
|
|
|
it "allows inviting a group to a PM" do
|
|
invite_group(pm, 200)
|
|
expect(pm.allowed_groups.first.id).to eq(admins.id)
|
|
end
|
|
end
|
|
|
|
context "when PM has reached maximum allowed numbers of recipients" do
|
|
let(:group) { Fabricate(:group, messageable_level: 99) }
|
|
let(:pm) { Fabricate(:private_message_topic, user: user) }
|
|
|
|
let(:moderator) { Fabricate(:moderator) }
|
|
let(:moderator_pm) { Fabricate(:private_message_topic, user: moderator) }
|
|
|
|
before do
|
|
SiteSetting.max_allowed_message_recipients = 2
|
|
end
|
|
|
|
it "doesn't allow normal users to invite" do
|
|
post "/t/#{pm.id}/invite-group.json", params: {
|
|
group: group.name
|
|
}
|
|
expect(response.status).to eq(422)
|
|
expect(JSON.parse(response.body)["errors"]).to contain_exactly(
|
|
I18n.t("pm_reached_recipients_limit", recipients_limit: SiteSetting.max_allowed_message_recipients)
|
|
)
|
|
end
|
|
|
|
it "allows staff to bypass limits" do
|
|
sign_in(moderator)
|
|
post "/t/#{moderator_pm.id}/invite-group.json", params: {
|
|
group: group.name
|
|
}
|
|
expect(response.status).to eq(200)
|
|
expect(moderator_pm.reload.topic_allowed_users.count + moderator_pm.topic_allowed_groups.count).to eq(3)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'shared drafts' do
|
|
let(:shared_drafts_category) { Fabricate(:category) }
|
|
let(:category) { Fabricate(:category) }
|
|
|
|
before do
|
|
SiteSetting.shared_drafts_category = shared_drafts_category.id
|
|
end
|
|
|
|
describe "#update_shared_draft" do
|
|
let(:other_cat) { Fabricate(:category) }
|
|
let(:category) { Fabricate(:category) }
|
|
let(:topic) { Fabricate(:topic, category: shared_drafts_category, visible: false) }
|
|
|
|
context "anonymous" do
|
|
it "doesn't allow staff to update the shared draft" do
|
|
put "/t/#{topic.id}/shared-draft.json", params: { category_id: other_cat.id }
|
|
expect(response.code.to_i).to eq(403)
|
|
end
|
|
end
|
|
|
|
context "as a moderator" do
|
|
let(:moderator) { Fabricate(:moderator) }
|
|
before do
|
|
sign_in(moderator)
|
|
end
|
|
|
|
context "with a shared draft" do
|
|
let!(:shared_draft) { Fabricate(:shared_draft, topic: topic, category: category) }
|
|
it "allows staff to update the category id" do
|
|
put "/t/#{topic.id}/shared-draft.json", params: { category_id: other_cat.id }
|
|
expect(response.status).to eq(200)
|
|
topic.reload
|
|
expect(topic.shared_draft.category_id).to eq(other_cat.id)
|
|
end
|
|
end
|
|
|
|
context "without a shared draft" do
|
|
it "allows staff to update the category id" do
|
|
put "/t/#{topic.id}/shared-draft.json", params: { category_id: other_cat.id }
|
|
expect(response.status).to eq(200)
|
|
topic.reload
|
|
expect(topic.shared_draft.category_id).to eq(other_cat.id)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#publish" do
|
|
let(:category) { Fabricate(:category) }
|
|
let(:topic) { Fabricate(:topic, category: shared_drafts_category, visible: false) }
|
|
let(:shared_draft) { Fabricate(:shared_draft, topic: topic, category: category) }
|
|
let(:moderator) { Fabricate(:moderator) }
|
|
|
|
it "fails for anonymous users" do
|
|
put "/t/#{topic.id}/publish.json", params: { destination_category_id: category.id }
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
it "fails as a regular user" do
|
|
sign_in(Fabricate(:user))
|
|
put "/t/#{topic.id}/publish.json", params: { destination_category_id: category.id }
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
context "as staff" do
|
|
before do
|
|
sign_in(moderator)
|
|
end
|
|
|
|
it "will publish the topic" do
|
|
put "/t/#{topic.id}/publish.json", params: { destination_category_id: category.id }
|
|
expect(response.status).to eq(200)
|
|
json = ::JSON.parse(response.body)['basic_topic']
|
|
|
|
result = Topic.find(json['id'])
|
|
expect(result.category_id).to eq(category.id)
|
|
expect(result.visible).to eq(true)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "crawler" do
|
|
|
|
context "when not a crawler" do
|
|
it "renders with the application layout" do
|
|
get topic.url
|
|
|
|
body = response.body
|
|
|
|
expect(body).to have_tag(:script, with: { src: '/assets/application.js' })
|
|
expect(body).to have_tag(:meta, with: { name: 'fragment' })
|
|
end
|
|
end
|
|
|
|
context "when a crawler" do
|
|
it "renders with the crawler layout, and handles proper pagination" do
|
|
|
|
page1_time = 3.months.ago
|
|
page2_time = 2.months.ago
|
|
page3_time = 1.month.ago
|
|
|
|
freeze_time page1_time
|
|
|
|
topic = Fabricate(:topic)
|
|
Fabricate(:post, topic: topic)
|
|
Fabricate(:post, topic: topic)
|
|
|
|
freeze_time page2_time
|
|
Fabricate(:post, topic: topic)
|
|
Fabricate(:post, topic: topic)
|
|
|
|
freeze_time page3_time
|
|
Fabricate(:post, topic: topic)
|
|
|
|
# ugly, but no inteface to set this and we don't want to create
|
|
# 100 posts to test this thing
|
|
TopicView.stubs(:chunk_size).returns(2)
|
|
|
|
user_agent = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
|
|
|
|
get topic.url, env: { "HTTP_USER_AGENT" => user_agent }
|
|
|
|
body = response.body
|
|
|
|
expect(body).to have_tag(:body, with: { class: 'crawler' })
|
|
expect(body).to_not have_tag(:meta, with: { name: 'fragment' })
|
|
expect(body).to include('<link rel="next" href="' + topic.relative_url + "?page=2")
|
|
|
|
expect(response.headers['Last-Modified']).to eq(page1_time.httpdate)
|
|
|
|
get topic.url + "?page=2", env: { "HTTP_USER_AGENT" => user_agent }
|
|
body = response.body
|
|
|
|
expect(response.headers['Last-Modified']).to eq(page2_time.httpdate)
|
|
|
|
expect(body).to include('<link rel="prev" href="' + topic.relative_url)
|
|
expect(body).to include('<link rel="next" href="' + topic.relative_url + "?page=3")
|
|
|
|
get topic.url + "?page=3", env: { "HTTP_USER_AGENT" => user_agent }
|
|
body = response.body
|
|
|
|
expect(response.headers['Last-Modified']).to eq(page3_time.httpdate)
|
|
expect(body).to include('<link rel="prev" href="' + topic.relative_url + "?page=2")
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
describe "#reset_bump_date" do
|
|
context "errors" do
|
|
let(:topic) { Fabricate(:topic) }
|
|
|
|
it "needs you to be logged in" do
|
|
put "/t/#{topic.id}/reset-bump-date.json"
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
[:user, :trust_level_4].each do |user|
|
|
it "denies access for #{user}" do
|
|
sign_in(Fabricate(user))
|
|
put "/t/#{topic.id}/reset-bump-date.json"
|
|
expect(response.status).to eq(403)
|
|
end
|
|
end
|
|
|
|
it "should fail for non-existend topic" do
|
|
sign_in(Fabricate(:admin))
|
|
put "/t/1/reset-bump-date.json"
|
|
expect(response.status).to eq(404)
|
|
end
|
|
end
|
|
|
|
[:admin, :moderator].each do |user|
|
|
it "should reset bumped_at as #{user}" do
|
|
sign_in(Fabricate(user))
|
|
topic = Fabricate(:topic, bumped_at: 1.hour.ago)
|
|
timestamp = 1.day.ago
|
|
Fabricate(:post, topic: topic, created_at: timestamp)
|
|
|
|
put "/t/#{topic.id}/reset-bump-date.json"
|
|
expect(response.status).to eq(200)
|
|
expect(topic.reload.bumped_at).to be_within_one_second_of(timestamp)
|
|
end
|
|
end
|
|
end
|
|
end
|