mirror of
https://github.com/discourse/discourse.git
synced 2024-12-30 07:15:49 +08:00
976aca68f6
We've seen in some communities abuse of user profile where bios and other fields are used in malicious ways, such as malware distribution. A common pattern between all the abuse cases we've seen is that the malicious actors tend to have 0 posts and have a low trust level. To eliminate this abuse vector, or at least make it much less effective, we're making the following changes to user profiles: 1. Anonymous, TL0 and TL1 users cannot see any user profiles for users with 0 posts except for staff users 2. Anonymous and TL0 users can only see profiles of TL1 users and above Users can always see their own profile, and they can still hide their profiles via the "Hide my public profile" preference. Staff can always see any user's profile. Internal topic: t/142853.
1760 lines
57 KiB
Ruby
1760 lines
57 KiB
Ruby
# frozen_string_literal: true
|
||
|
||
RSpec.describe ListController do
|
||
fab!(:user) { Fabricate(:user, refresh_auto_groups: true) }
|
||
fab!(:topic) { Fabricate(:topic, user: user) }
|
||
fab!(:group) { Fabricate(:group, name: "AwesomeGroup") }
|
||
fab!(:admin)
|
||
|
||
before do
|
||
admin # to skip welcome wizard at home page `/`
|
||
SiteSetting.top_menu = "latest|new|unread|categories"
|
||
end
|
||
|
||
describe "#index" do
|
||
context "when params are invalid" do
|
||
it "should return a 400 response when `page` param is a string that represent a negative integer" do
|
||
get "/latest?page=-1"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `page` param is a string larger than maximum integer value" do
|
||
get "/latest?page=2147483648"
|
||
expect(response.status).to eq(400)
|
||
|
||
get "/latest?page=1111111111111111111111111111111111111111"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `before` param is not a string represetning an integer" do
|
||
get "/latest?before[1]=haxx"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `bumped_before` param is not a string representing an integer" do
|
||
get "/latest?bumped_before[1]=haxx"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `topic_ids` param is not a string representing an integer" do
|
||
get "/latest?topic_ids[1]=haxx"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `category` param is not a string representing an integer" do
|
||
get "/latest?category[1]=haxx"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `order` param is not a string" do
|
||
get "/latest?order[1]=haxx"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `ascending` param is not a string that is either `true` or `false`" do
|
||
get "/latest?ascending=maybe"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `min_posts` param is a string that does not represent an integer" do
|
||
get "/latest?min_posts=bob"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `max_posts` param is a string that does not represent an integer" do
|
||
get "/latest?max_posts=bob"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `max_posts` param is a string larger than maximum integer value" do
|
||
get "/latest?max_posts=1111111111111111111111111111111111111111"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `status` param is not a string" do
|
||
get "/latest?status%5Bsomehash%5D=something"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `filter` param is not a string" do
|
||
get "/latest?filter%5Bsomehash%5D=something"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `state` param is not a string" do
|
||
get "/latest?state%5Bsomehash%5D=something"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `search` param is not a string" do
|
||
get "/latest?search%5Bsomehash%5D=something"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `q` param is not a string" do
|
||
get "/latest?q%5Bsomehash%5D=something"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `f` param is not a string" do
|
||
get "/latest?f%5Bsomehash%5D=something"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `subset` param is not a string" do
|
||
get "/latest?subset%5Bsomehash%5D=something"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `group_name` param is not a string" do
|
||
get "/latest?group_name%5Bsomehash%5D=something"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `tags` param is not an array or string" do
|
||
get "/latest?tags[1]=hello"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `filter` param is not a string" do
|
||
get "/latest?filter%5Bsomehash%5D=something"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `match_all_tags` param is not a string that is either `true` or `false`" do
|
||
get "/latest?match_all_tags=something"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `no_subcategories` param is not a string that is either `true` or `false`" do
|
||
get "/latest?no_subcategories=something"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `no_tags` param is not a string that is either `true` or `false`" do
|
||
get "/latest?no_tags=something"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
it "should return a 400 response when `exclude_tag` param is not a string" do
|
||
get "/latest?exclude_tag%5Bsomehash%5D=something"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
end
|
||
|
||
it "returns 200 for legit requests" do
|
||
get "/latest.json?no_definitions=true&no_subcategories=false&page=1&_=1534296100767"
|
||
expect(response.status).to eq(200)
|
||
|
||
get "/latest.json?max_posts=12"
|
||
expect(response.status).to eq(200)
|
||
|
||
get "/latest.json?min_posts=0"
|
||
expect(response.status).to eq(200)
|
||
|
||
get "/latest?page=0"
|
||
expect(response.status).to eq(200)
|
||
|
||
get "/latest?page=1"
|
||
expect(response.status).to eq(200)
|
||
|
||
get "/latest.json?page=2147483647"
|
||
expect(response.status).to eq(200)
|
||
|
||
get "/latest?search="
|
||
expect(response.status).to eq(200)
|
||
|
||
get "/latest.json?topic_ids%5B%5D=14583&topic_ids%5B%5D=14584"
|
||
expect(response.status).to eq(200)
|
||
|
||
get "/latest.json?topic_ids=14583%2C14584"
|
||
expect(response.status).to eq(200)
|
||
|
||
get "/latest?tags[]=hello"
|
||
expect(response.status).to eq(200)
|
||
end
|
||
|
||
(Discourse.anonymous_filters - [:categories]).each do |filter|
|
||
context "with #{filter}" do
|
||
it "succeeds" do
|
||
get "/#{filter}"
|
||
expect(response.status).to eq(200)
|
||
end
|
||
end
|
||
end
|
||
|
||
it "allows users to filter on a set of topic ids" do
|
||
p = create_post
|
||
|
||
get "/latest.json", params: { topic_ids: "#{p.topic_id}" }
|
||
expect(response.status).to eq(200)
|
||
parsed = response.parsed_body
|
||
expect(parsed["topic_list"]["topics"].length).to eq(1)
|
||
end
|
||
|
||
it "shows correct title if topic list is set for homepage" do
|
||
get "/latest"
|
||
|
||
expect(response.body).to have_tag "title", text: "Discourse"
|
||
|
||
SiteSetting.short_site_description = "Best community"
|
||
get "/latest"
|
||
|
||
expect(response.body).to have_tag "title", text: "Discourse - Best community"
|
||
end
|
||
|
||
it "returns structured data" do
|
||
get "/latest"
|
||
|
||
expect(response.status).to eq(200)
|
||
topic_list = Nokogiri.HTML5(response.body).css(".topic-list")
|
||
first_item = topic_list.css('[itemprop="itemListElement"]')
|
||
expect(first_item.css('[itemprop="position"]')[0]["content"]).to eq("1")
|
||
expect(first_item.css('[itemprop="url"]')[0]["href"]).to eq(topic.url)
|
||
end
|
||
|
||
it "does not result in N+1 queries when topics have tags and tagging_enabled site setting is enabled" do
|
||
SiteSetting.tagging_enabled = true
|
||
tag = Fabricate(:tag)
|
||
topic.tags << tag
|
||
|
||
# warm up
|
||
get "/latest.json"
|
||
expect(response.status).to eq(200)
|
||
|
||
initial_sql_queries_count =
|
||
track_sql_queries do
|
||
get "/latest.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
body = response.parsed_body
|
||
|
||
expect(body["topic_list"]["topics"].map { |t| t["id"] }).to contain_exactly(topic.id)
|
||
expect(body["topic_list"]["topics"][0]["tags"]).to contain_exactly(tag.name)
|
||
end.count
|
||
|
||
tag2 = Fabricate(:tag)
|
||
topic2 = Fabricate(:topic, tags: [tag2])
|
||
|
||
new_sql_queries_count =
|
||
track_sql_queries do
|
||
get "/latest.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
body = response.parsed_body
|
||
|
||
expect(body["topic_list"]["topics"].map { |t| t["id"] }).to contain_exactly(
|
||
topic.id,
|
||
topic2.id,
|
||
)
|
||
|
||
expect(body["topic_list"]["topics"][0]["tags"]).to contain_exactly(tag2.name)
|
||
expect(body["topic_list"]["topics"][1]["tags"]).to contain_exactly(tag.name)
|
||
end.count
|
||
|
||
expect(new_sql_queries_count).to eq(initial_sql_queries_count)
|
||
end
|
||
|
||
it "does not N+1 queries when topic featured users have different primary groups" do
|
||
user.update!(primary_group: group)
|
||
|
||
# warm up
|
||
get "/latest.json"
|
||
expect(response.status).to eq(200)
|
||
|
||
initial_sql_queries_count =
|
||
track_sql_queries do
|
||
get "/latest.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
body = response.parsed_body
|
||
|
||
expect(body["topic_list"]["topics"].map { |t| t["id"] }).to contain_exactly(topic.id)
|
||
expect(
|
||
body["topic_list"]["topics"][0]["posters"].map { |p| p["user_id"] },
|
||
).to contain_exactly(user.id)
|
||
end.count
|
||
|
||
group2 = Fabricate(:group)
|
||
user2 = Fabricate(:user, primary_group: group2)
|
||
topic.update!(last_post_user_id: user2.id)
|
||
|
||
group3 = Fabricate(:group)
|
||
user3 = Fabricate(:user, flair_group: group3)
|
||
topic.update!(featured_user3_id: user3.id)
|
||
|
||
new_sql_queries_count =
|
||
track_sql_queries do
|
||
get "/latest.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
body = response.parsed_body
|
||
|
||
expect(body["topic_list"]["topics"].map { |t| t["id"] }).to contain_exactly(topic.id)
|
||
expect(
|
||
body["topic_list"]["topics"][0]["posters"].map { |p| p["user_id"] },
|
||
).to contain_exactly(user.id, user2.id, user3.id)
|
||
end.count
|
||
|
||
expect(new_sql_queries_count).to be <= initial_sql_queries_count
|
||
end
|
||
|
||
context "with topics with tags" do
|
||
let(:tag_group) { Fabricate.build(:tag_group) }
|
||
let(:tag_group_permission) { Fabricate.build(:tag_group_permission, tag_group: tag_group) }
|
||
let(:restricted_tag) { Fabricate(:tag) }
|
||
let(:public_tag) { Fabricate(:tag) }
|
||
|
||
before do
|
||
tag_group.tag_group_permissions << tag_group_permission
|
||
tag_group.save!
|
||
tag_group_permission.tag_group.tags << restricted_tag
|
||
topic.tags << [public_tag, restricted_tag]
|
||
end
|
||
|
||
it "does not show hidden tags" do
|
||
get "/latest"
|
||
|
||
expect(response.status).to eq(200)
|
||
expect(response.body).to include(public_tag.name)
|
||
expect(response.body).not_to include(restricted_tag.name)
|
||
end
|
||
end
|
||
|
||
context "with lazy load categories enabled" do
|
||
fab!(:category)
|
||
fab!(:subcategory) { Fabricate(:category, parent_category: category) }
|
||
|
||
before { topic.update!(category: subcategory) }
|
||
|
||
it "returns categories and parent categories if true" do
|
||
SiteSetting.lazy_load_categories_groups = "#{Group::AUTO_GROUPS[:everyone]}"
|
||
|
||
get "/latest.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
expect(response.parsed_body["topic_list"]["topics"].length).to eq(1)
|
||
expect(response.parsed_body["topic_list"]["topics"][0]["id"]).to eq(topic.id)
|
||
expect(response.parsed_body["topic_list"]["categories"].length).to eq(2)
|
||
expect(response.parsed_body["topic_list"]["categories"].map { |c| c["id"] }).to eq(
|
||
[category.id, subcategory.id],
|
||
)
|
||
end
|
||
|
||
it "does not return categories if not true" do
|
||
SiteSetting.lazy_load_categories_groups = ""
|
||
|
||
get "/latest.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
expect(response.parsed_body["topic_list"]["topics"].length).to eq(1)
|
||
expect(response.parsed_body["topic_list"]["topics"][0]["id"]).to eq(topic.id)
|
||
expect(response.parsed_body["topic_list"]["categories"]).to eq(nil)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "categories and X" do
|
||
let(:category) { Fabricate(:category_with_definition) }
|
||
let(:sub_category) { Fabricate(:category_with_definition, parent_category: category) }
|
||
|
||
it "returns top topics" do
|
||
Fabricate(:topic, like_count: 1000, posts_count: 100)
|
||
TopTopic.refresh!
|
||
|
||
get "/categories_and_top.json"
|
||
data = response.parsed_body
|
||
expect(data["topic_list"]["topics"].length).to eq(1)
|
||
|
||
get "/categories_and_latest.json"
|
||
data = response.parsed_body
|
||
expect(data["topic_list"]["topics"].length).to eq(2)
|
||
end
|
||
|
||
it "returns topics from subcategories when no_subcategories=false" do
|
||
Fabricate(:topic, category: sub_category)
|
||
get "/c/#{category.slug}/#{category.id}/l/latest.json?no_subcategories=false"
|
||
expect(response.parsed_body["topic_list"]["topics"].length).to eq(2)
|
||
end
|
||
end
|
||
|
||
describe "titles for crawler layout" do
|
||
it "has no title for the default URL" do
|
||
topic
|
||
filter = Discourse.anonymous_filters[0]
|
||
get "/#{filter}", params: { _escaped_fragment_: "true" }
|
||
|
||
expect(response.body).to include(I18n.t("rss_description.posts"))
|
||
|
||
expect(response.body).to_not include(I18n.t("js.filters.with_topics", filter: filter))
|
||
end
|
||
|
||
it "has a title for non-default URLs" do
|
||
topic
|
||
filter = Discourse.anonymous_filters[1]
|
||
get "/#{filter}", params: { _escaped_fragment_: "true" }
|
||
|
||
expect(response.body).to include(I18n.t("js.filters.with_topics", filter: filter))
|
||
end
|
||
end
|
||
|
||
describe "filter private messages by tag" do
|
||
fab!(:user)
|
||
fab!(:moderator)
|
||
fab!(:admin)
|
||
let(:tag) { Fabricate(:tag) }
|
||
let(:private_message) { Fabricate(:private_message_topic, user: admin) }
|
||
|
||
before do
|
||
SiteSetting.tagging_enabled = true
|
||
SiteSetting.pm_tags_allowed_for_groups = "1|2|3"
|
||
Fabricate(:topic_tag, tag: tag, topic: private_message)
|
||
end
|
||
|
||
it "should fail for non-staff users" do
|
||
sign_in(user)
|
||
get "/topics/private-messages-tags/#{user.username}/#{tag.name}.json"
|
||
expect(response.status).to eq(404)
|
||
end
|
||
|
||
it "should fail for staff users if empty" do
|
||
SiteSetting.pm_tags_allowed_for_groups = ""
|
||
|
||
[moderator, admin].each do |user|
|
||
sign_in(user)
|
||
get "/topics/private-messages-tags/#{user.username}/#{tag.name}.json"
|
||
expect(response.status).to eq(404)
|
||
end
|
||
end
|
||
|
||
it "should be success for staff users" do
|
||
[moderator, admin].each do |user|
|
||
sign_in(user)
|
||
get "/topics/private-messages-tags/#{user.username}/#{tag.name}.json"
|
||
expect(response.status).to eq(200)
|
||
end
|
||
end
|
||
|
||
it "should work for tag with unicode name" do
|
||
unicode_tag = Fabricate(:tag, name: "hello-🇺🇸")
|
||
Fabricate(:topic_tag, tag: unicode_tag, topic: private_message)
|
||
|
||
sign_in(admin)
|
||
get "/topics/private-messages-tags/#{admin.username}/#{UrlHelper.encode_component(unicode_tag.name)}.json"
|
||
expect(response.status).to eq(200)
|
||
expect(response.parsed_body["topic_list"]["topics"].first["id"]).to eq(private_message.id)
|
||
end
|
||
|
||
it "should work for users who are allowed and direct links" do
|
||
SiteSetting.pm_tags_allowed_for_groups = group.name
|
||
group.add(user)
|
||
sign_in(user)
|
||
|
||
get "/u/#{user.username}/messages/tags/#{tag.name}"
|
||
|
||
expect(response.status).to eq(200)
|
||
end
|
||
end
|
||
|
||
describe "#private_messages_group" do
|
||
describe "when user not in personal_message_enabled_groups group" do
|
||
let!(:topic) { Fabricate(:private_message_topic, allowed_groups: [group]) }
|
||
|
||
before do
|
||
group.add(user)
|
||
SiteSetting.personal_message_enabled_groups = Group::AUTO_GROUPS[:staff]
|
||
end
|
||
|
||
it "should display group private messages for an admin" do
|
||
sign_in(Fabricate(:admin))
|
||
|
||
get "/topics/private-messages-group/#{user.username}/#{group.name}.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(response.parsed_body["topic_list"]["topics"].first["id"]).to eq(topic.id)
|
||
end
|
||
|
||
it "should display moderator group private messages for a moderator" do
|
||
moderator = Fabricate(:moderator)
|
||
group = Group.find(Group::AUTO_GROUPS[:moderators])
|
||
Fabricate(:private_message_topic, allowed_groups: [group])
|
||
|
||
sign_in(moderator)
|
||
|
||
get "/topics/private-messages-group/#{moderator.username}/#{group.name}.json"
|
||
expect(response.status).to eq(200)
|
||
end
|
||
|
||
it "should not display group private messages for a moderator's group" do
|
||
moderator = Fabricate(:moderator)
|
||
sign_in(moderator)
|
||
|
||
get "/topics/private-messages-group/#{user.username}/#{group.name}.json"
|
||
|
||
expect(response.status).to eq(404)
|
||
end
|
||
|
||
it "should sort group private messages by posts_count" do
|
||
topic2 = Fabricate(:private_message_topic, allowed_groups: [group])
|
||
topic3 = Fabricate(:private_message_topic, allowed_groups: [group])
|
||
2.times { Fabricate(:post, topic: topic2) }
|
||
Fabricate(:post, topic: topic3)
|
||
|
||
sign_in(Fabricate(:admin))
|
||
|
||
get "/topics/private-messages-group/#{user.username}/#{group.name}.json",
|
||
params: {
|
||
order: "posts",
|
||
}
|
||
|
||
expect(response.status).to eq(200)
|
||
expect(response.parsed_body["topic_list"]["topics"].map { |t| t["id"] }).to eq(
|
||
[topic2.id, topic3.id, topic.id],
|
||
)
|
||
end
|
||
end
|
||
|
||
describe "with unicode_usernames" do
|
||
before do
|
||
group.add(user)
|
||
sign_in(user)
|
||
SiteSetting.unicode_usernames = false
|
||
end
|
||
|
||
it "should return the right response when user does not belong to group" do
|
||
Fabricate(:private_message_topic, allowed_groups: [group])
|
||
|
||
group.remove(user)
|
||
|
||
get "/topics/private-messages-group/#{user.username}/#{group.name}.json"
|
||
|
||
expect(response.status).to eq(404)
|
||
end
|
||
|
||
it "should return the right response" do
|
||
topic = Fabricate(:private_message_topic, allowed_groups: [group])
|
||
get "/topics/private-messages-group/#{user.username}/awesomegroup.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(response.parsed_body["topic_list"]["topics"].first["id"]).to eq(topic.id)
|
||
end
|
||
end
|
||
|
||
describe "with unicode_usernames" do
|
||
before do
|
||
sign_in(user)
|
||
SiteSetting.unicode_usernames = true
|
||
end
|
||
|
||
it "Returns a 200 with unicode group name" do
|
||
unicode_group = Fabricate(:group, name: "群群组")
|
||
unicode_group.add(user)
|
||
topic = Fabricate(:private_message_topic, allowed_groups: [unicode_group])
|
||
get "/topics/private-messages-group/#{user.username}/#{UrlHelper.encode_component(unicode_group.name)}.json"
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(response.parsed_body["topic_list"]["topics"].first["id"]).to eq(topic.id)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "#group_topics" do
|
||
%i[user user2].each do |user|
|
||
let(user) do
|
||
user = Fabricate(:user)
|
||
group.add(user)
|
||
user
|
||
end
|
||
end
|
||
|
||
let!(:topic) { Fabricate(:topic, user: user) }
|
||
let!(:topic2) { Fabricate(:topic, user: user2) }
|
||
let!(:another_topic) { Fabricate(:topic) }
|
||
|
||
describe "when an invalid group name is given" do
|
||
it "should return the right response" do
|
||
get "/topics/groups/something.json"
|
||
|
||
expect(response.status).to eq(404)
|
||
end
|
||
end
|
||
|
||
describe "for an anon user" do
|
||
describe "public visible group" do
|
||
it "should return the right response" do
|
||
get "/topics/groups/#{group.name}.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
expect(response.parsed_body["topic_list"]).to be_present
|
||
end
|
||
end
|
||
|
||
describe "group restricted to logged-on-users" do
|
||
before { group.update!(visibility_level: Group.visibility_levels[:logged_on_users]) }
|
||
|
||
it "should return the right response" do
|
||
get "/topics/groups/#{group.name}.json"
|
||
|
||
expect(response.status).to eq(403)
|
||
end
|
||
end
|
||
|
||
describe "restricted group" do
|
||
before { group.update!(visibility_level: Group.visibility_levels[:staff]) }
|
||
|
||
it "should return the right response" do
|
||
get "/topics/groups/#{group.name}.json"
|
||
|
||
expect(response.status).to eq(403)
|
||
end
|
||
end
|
||
|
||
describe "group members visibility restricted to logged-on-users" do
|
||
before do
|
||
group.update!(members_visibility_level: Group.visibility_levels[:logged_on_users])
|
||
end
|
||
|
||
it "should return the right response" do
|
||
get "/topics/groups/#{group.name}.json"
|
||
|
||
expect(response.status).to eq(403)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "for a normal user" do
|
||
before { sign_in(Fabricate(:user)) }
|
||
|
||
describe "restricted group" do
|
||
before { group.update!(visibility_level: Group.visibility_levels[:staff]) }
|
||
|
||
it "should return the right response" do
|
||
get "/topics/groups/#{group.name}.json"
|
||
|
||
expect(response.status).to eq(403)
|
||
end
|
||
end
|
||
|
||
describe "group restricted to logged-on-users" do
|
||
before { group.update!(visibility_level: Group.visibility_levels[:logged_on_users]) }
|
||
|
||
it "should return the right response" do
|
||
get "/topics/groups/#{group.name}.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "for a group user" do
|
||
before { sign_in(user) }
|
||
|
||
it "should be able to view the topics started by group users" do
|
||
get "/topics/groups/#{group.name}.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
topics = response.parsed_body["topic_list"]["topics"]
|
||
|
||
expect(topics.map { |topic| topic["id"] }).to contain_exactly(topic.id, topic2.id)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "RSS feeds" do
|
||
it "renders latest RSS" do
|
||
get "/latest.rss"
|
||
expect(response.status).to eq(200)
|
||
expect(response.media_type).to eq("application/rss+xml")
|
||
expect(response.headers["X-Robots-Tag"]).to eq("noindex")
|
||
end
|
||
|
||
it "renders latest RSS with query params" do
|
||
get "/latest.rss?status=closed"
|
||
expect(response.status).to eq(200)
|
||
expect(response.media_type).to eq("application/rss+xml")
|
||
expect(response.body).to_not include("<item>")
|
||
end
|
||
|
||
it "renders links correctly with subfolder" do
|
||
set_subfolder "/forum"
|
||
_post = Fabricate(:post, topic: topic, user: user)
|
||
get "/latest.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
|
||
|
||
it "renders top RSS" do
|
||
get "/top.rss"
|
||
expect(response.status).to eq(200)
|
||
expect(response.media_type).to eq("application/rss+xml")
|
||
end
|
||
|
||
it "errors for invalid periods on top RSS" do
|
||
get "/top.rss?period=decadely"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
|
||
TopTopic.periods.each do |period|
|
||
it "renders #{period} top RSS" do
|
||
get "/top.rss?period=#{period}"
|
||
expect(response.status).to eq(200)
|
||
expect(response.media_type).to eq("application/rss+xml")
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "Top" do
|
||
it "renders top" do
|
||
get "/top"
|
||
expect(response.status).to eq(200)
|
||
end
|
||
|
||
it "renders top with a period" do
|
||
get "/top?period=weekly"
|
||
expect(response.status).to eq(200)
|
||
end
|
||
|
||
it "errors for invalid periods on top" do
|
||
get "/top?period=decadely"
|
||
expect(response.status).to eq(400)
|
||
end
|
||
end
|
||
|
||
describe "category" do
|
||
context "when in a category" do
|
||
let(:category) { Fabricate(:category_with_definition) }
|
||
let(:group) { Fabricate(:group) }
|
||
let(:private_category) { Fabricate(:private_category, group: group) }
|
||
|
||
context "without access to see the category" do
|
||
it "responds with a 404 error" do
|
||
get "/c/#{private_category.slug}/l/latest"
|
||
expect(response.status).to eq(404)
|
||
end
|
||
end
|
||
|
||
context "with access to see the category" do
|
||
it "succeeds" do
|
||
get "/c/#{category.slug}/#{category.id}/l/latest"
|
||
expect(response.status).to eq(200)
|
||
end
|
||
end
|
||
|
||
context "with encoded slug in the category" do
|
||
let(:category) { Fabricate(:category, slug: "தமிழ்") }
|
||
|
||
before { SiteSetting.slug_generation_method = "encoded" }
|
||
|
||
it "succeeds" do
|
||
get "/c/#{category.slug}/#{category.id}/l/latest"
|
||
expect(response.status).to eq(200)
|
||
end
|
||
end
|
||
|
||
context "with a link that has a parent slug, slug and id in its path" do
|
||
let(:child_category) { Fabricate(:category_with_definition, parent_category: category) }
|
||
|
||
context "with valid slug" do
|
||
it "succeeds" do
|
||
get "/c/#{category.slug}/#{child_category.slug}/#{child_category.id}/l/latest"
|
||
expect(response.status).to eq(200)
|
||
end
|
||
end
|
||
|
||
context "with invalid slug" do
|
||
it "redirects" do
|
||
get "/c/random_slug/another_random_slug/#{child_category.id}/l/latest"
|
||
expect(response).to redirect_to("#{child_category.url}/l/latest")
|
||
end
|
||
end
|
||
end
|
||
|
||
context "when another category exists with a number at the beginning of its name" do
|
||
# One category has another category's id at the beginning of its name
|
||
let!(:other_category) do
|
||
# Our validations don't allow this to happen now, but did historically
|
||
Fabricate(
|
||
:category_with_definition,
|
||
name: "#{category.id} name",
|
||
slug: "will-be-changed",
|
||
).tap { |category| category.update_column(:slug, "#{category.id}-name") }
|
||
end
|
||
|
||
it "uses the correct category" do
|
||
get "/c/#{other_category.slug}/#{other_category.id}/l/latest.json"
|
||
expect(response.status).to eq(200)
|
||
body = response.parsed_body
|
||
expect(body["topic_list"]["topics"].first["category_id"]).to eq(other_category.id)
|
||
end
|
||
end
|
||
|
||
context "with a child category" do
|
||
let(:sub_category) { Fabricate(:category_with_definition, parent_category_id: category.id) }
|
||
|
||
context "when parent and child are requested" do
|
||
it "succeeds" do
|
||
get "/c/#{category.slug}/#{sub_category.slug}/#{sub_category.id}/l/latest"
|
||
expect(response.status).to eq(200)
|
||
end
|
||
end
|
||
|
||
context "when child is requested with the wrong parent" do
|
||
it "responds with a 404 error" do
|
||
get "/c/not-the-right-slug/#{sub_category.slug}/l/latest"
|
||
expect(response.status).to eq(404)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "feed" do
|
||
it "renders RSS" do
|
||
get "/c/#{category.slug}/#{category.id}.rss"
|
||
expect(response.status).to eq(200)
|
||
expect(response.media_type).to eq("application/rss+xml")
|
||
end
|
||
|
||
it "renders RSS in subfolder correctly" do
|
||
set_subfolder "/forum"
|
||
get "/c/#{category.slug}/#{category.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/c/#{category.slug}")
|
||
end
|
||
end
|
||
|
||
describe "category default views" do
|
||
it "has a top default view" do
|
||
category.update!(default_view: "top", default_top_period: "monthly")
|
||
get "/c/#{category.slug}/#{category.id}.json"
|
||
expect(response.status).to eq(200)
|
||
json = response.parsed_body
|
||
expect(json["topic_list"]["for_period"]).to eq("monthly")
|
||
end
|
||
|
||
it "has a default view of nil" do
|
||
category.update!(default_view: nil)
|
||
get "/c/#{category.slug}/#{category.id}.json"
|
||
expect(response.status).to eq(200)
|
||
json = response.parsed_body
|
||
expect(json["topic_list"]["for_period"]).to be_blank
|
||
end
|
||
|
||
it "has a default view of ''" do
|
||
category.update!(default_view: "")
|
||
get "/c/#{category.slug}/#{category.id}.json"
|
||
expect(response.status).to eq(200)
|
||
json = response.parsed_body
|
||
expect(json["topic_list"]["for_period"]).to be_blank
|
||
end
|
||
|
||
it "has a default view of latest" do
|
||
category.update!(default_view: "latest")
|
||
get "/c/#{category.slug}/#{category.id}.json"
|
||
expect(response.status).to eq(200)
|
||
json = response.parsed_body
|
||
expect(json["topic_list"]["for_period"]).to be_blank
|
||
end
|
||
end
|
||
|
||
describe "renders canonical tag" do
|
||
it "for category default view" do
|
||
get "/c/#{category.slug}/#{category.id}"
|
||
expect(response.status).to eq(200)
|
||
expect(css_select("link[rel=canonical]").length).to eq(1)
|
||
end
|
||
|
||
it "for category latest view" do
|
||
get "/c/#{category.slug}/#{category.id}/l/latest"
|
||
expect(response.status).to eq(200)
|
||
expect(css_select("link[rel=canonical]").length).to eq(1)
|
||
end
|
||
end
|
||
|
||
context "for category default view" do
|
||
let!(:amazing_category) { Fabricate(:category_with_definition, name: "Amazing Category") }
|
||
|
||
it "renders correct title" do
|
||
get "/c/#{amazing_category.slug}/#{amazing_category.id}"
|
||
|
||
expect(response.body).to have_tag "title", text: "Amazing Category - Discourse"
|
||
end
|
||
end
|
||
|
||
context "for category latest view" do
|
||
let!(:amazing_category) { Fabricate(:category_with_definition, name: "Amazing Category") }
|
||
|
||
it "renders correct title" do
|
||
SiteSetting.short_site_description = "Best community"
|
||
get "/c/#{amazing_category.slug}/#{amazing_category.id}/l/latest"
|
||
|
||
expect(response.body).to have_tag "title", text: "Amazing Category - Discourse"
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "topics_by" do
|
||
fab!(:topic2) { Fabricate(:topic, user: user) }
|
||
fab!(:user2) { Fabricate(:user) }
|
||
|
||
before do
|
||
user.user_stat.update!(post_count: 1)
|
||
sign_in(user2)
|
||
end
|
||
|
||
it "should respond with a list" do
|
||
get "/topics/created-by/#{user.username}.json"
|
||
expect(response.status).to eq(200)
|
||
json = response.parsed_body
|
||
expect(json["topic_list"]["topics"].size).to eq(2)
|
||
end
|
||
|
||
it "should work with period in username" do
|
||
user.update!(username: "myname.test")
|
||
get "/topics/created-by/#{user.username}", xhr: true
|
||
expect(response.status).to eq(200)
|
||
json = response.parsed_body
|
||
expect(json["topic_list"]["topics"].size).to eq(2)
|
||
end
|
||
|
||
context "with unicode usernames" do
|
||
before { SiteSetting.unicode_usernames = true }
|
||
|
||
it "should return the more_topics_url in the encoded form" do
|
||
stub_const(TopicQuery, "DEFAULT_PER_PAGE_COUNT", 1) do
|
||
user.update!(username: "快快快")
|
||
|
||
get "/topics/created-by/#{UrlHelper.encode(user.username)}.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
json = response.parsed_body
|
||
|
||
expect(json["topic_list"]["more_topics_url"]).to eq(
|
||
"/topics/created-by/%E5%BF%AB%E5%BF%AB%E5%BF%AB?page=1",
|
||
)
|
||
end
|
||
end
|
||
end
|
||
|
||
context "when `hide_profile` is true" do
|
||
before { user.user_option.update_columns(hide_profile: true) }
|
||
|
||
it "returns 404" do
|
||
get "/topics/created-by/#{user.username}.json"
|
||
expect(response.status).to eq(404)
|
||
end
|
||
|
||
it "should respond with a list when `allow_users_to_hide_profile` is false" do
|
||
SiteSetting.allow_users_to_hide_profile = false
|
||
get "/topics/created-by/#{user.username}.json"
|
||
expect(response.status).to eq(200)
|
||
json = response.parsed_body
|
||
expect(json["topic_list"]["topics"].size).to eq(2)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "private_messages" do
|
||
it "returns 403 error when the user can't see private message" do
|
||
sign_in(Fabricate(:user))
|
||
get "/topics/private-messages/#{user.username}.json"
|
||
expect(response).to be_forbidden
|
||
end
|
||
|
||
it "succeeds when the user can see private messages" do
|
||
pm = Fabricate(:private_message_topic, user: Fabricate(:user))
|
||
pm.topic_allowed_users.create!(user: user)
|
||
sign_in(user)
|
||
get "/topics/private-messages/#{user.username}.json"
|
||
expect(response.status).to eq(200)
|
||
json = response.parsed_body
|
||
expect(json["topic_list"]["topics"].size).to eq(1)
|
||
end
|
||
|
||
it "sorts private messages by activity" do
|
||
topic_ids = []
|
||
|
||
[1.year.ago, 1.week.ago, 1.month.ago].each do |date|
|
||
pm =
|
||
Fabricate(
|
||
:private_message_topic,
|
||
user: Fabricate(:user),
|
||
created_at: date,
|
||
bumped_at: date,
|
||
)
|
||
pm.topic_allowed_users.create!(user: user)
|
||
topic_ids << pm.id
|
||
end
|
||
|
||
sign_in(user)
|
||
|
||
get "/topics/private-messages/#{user.username}.json", params: { order: "activity" }
|
||
|
||
expect(response.status).to eq(200)
|
||
json = response.parsed_body
|
||
expect(json["topic_list"]["topics"].pluck("id")).to eq(
|
||
[topic_ids[1], topic_ids[2], topic_ids[0]],
|
||
)
|
||
end
|
||
end
|
||
|
||
describe "private_messages_sent" do
|
||
before do
|
||
pm = Fabricate(:private_message_topic, user: user)
|
||
Fabricate(:post, user: user, topic: pm, post_number: 1)
|
||
end
|
||
|
||
it "returns 403 error when the user can't see private message" do
|
||
sign_in(Fabricate(:user))
|
||
get "/topics/private-messages-sent/#{user.username}.json"
|
||
expect(response).to be_forbidden
|
||
end
|
||
|
||
it "succeeds when the user can see private messages" do
|
||
sign_in(user)
|
||
get "/topics/private-messages-sent/#{user.username}.json"
|
||
expect(response.status).to eq(200)
|
||
json = response.parsed_body
|
||
expect(json["topic_list"]["topics"].size).to eq(1)
|
||
end
|
||
end
|
||
|
||
describe "#private_messages_unread" do
|
||
fab!(:pm_user) { Fabricate(:user) }
|
||
|
||
fab!(:pm) do
|
||
Fabricate(:private_message_topic).tap do |t|
|
||
t.allowed_users << pm_user
|
||
create_post(user: pm_user, topic_id: t.id)
|
||
end
|
||
end
|
||
|
||
it "returns 404 when the user can't see private message" do
|
||
sign_in(Fabricate(:user))
|
||
get "/topics/private-messages-unread/#{pm_user.username}.json"
|
||
expect(response.status).to eq(404)
|
||
end
|
||
|
||
it "succeeds when the user can see private messages" do
|
||
TopicUser.find_by(topic: pm, user: pm_user).update!(
|
||
notification_level: TopicUser.notification_levels[:tracking],
|
||
last_read_post_number: 0,
|
||
)
|
||
|
||
sign_in(pm_user)
|
||
get "/topics/private-messages-unread/#{pm_user.username}.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
json = response.parsed_body
|
||
expect(json["topic_list"]["topics"].size).to eq(1)
|
||
expect(json["topic_list"]["topics"][0]["id"]).to eq(pm.id)
|
||
end
|
||
end
|
||
|
||
describe "#private_messages_warnings" do
|
||
fab!(:target_user) { Fabricate(:user) }
|
||
fab!(:admin)
|
||
fab!(:moderator1) { Fabricate(:moderator) }
|
||
fab!(:moderator2) { Fabricate(:moderator) }
|
||
|
||
let(:create_args) do
|
||
{
|
||
title: "you need a warning buddy!",
|
||
raw: "you did something bad and I'm telling you about it!",
|
||
is_warning: true,
|
||
target_usernames: target_user.username,
|
||
archetype: Archetype.private_message,
|
||
}
|
||
end
|
||
|
||
let(:warning_post) do
|
||
creator = PostCreator.new(moderator1, create_args)
|
||
creator.create
|
||
end
|
||
let(:warning_topic) { warning_post.topic }
|
||
|
||
before { warning_topic }
|
||
|
||
it "returns 403 error for unrelated users" do
|
||
sign_in(Fabricate(:user))
|
||
get "/topics/private-messages-warnings/#{target_user.username}.json"
|
||
expect(response.status).to eq(403)
|
||
end
|
||
|
||
it "shows the warning to moderators and admins" do
|
||
[moderator1, moderator2, admin].each do |viewer|
|
||
sign_in(viewer)
|
||
get "/topics/private-messages-warnings/#{target_user.username}.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
json = response.parsed_body
|
||
expect(json["topic_list"]["topics"].size).to eq(1)
|
||
expect(json["topic_list"]["topics"][0]["id"]).to eq(warning_topic.id)
|
||
end
|
||
end
|
||
|
||
it "does not show the warning as applying to the authoring moderator" do
|
||
sign_in(admin)
|
||
get "/topics/private-messages-warnings/#{moderator1.username}.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
json = response.parsed_body
|
||
expect(json["topic_list"]["topics"].size).to eq(0)
|
||
end
|
||
end
|
||
|
||
describe "read" do
|
||
it "raises an error when not logged in" do
|
||
get "/read"
|
||
expect(response.status).to eq(404)
|
||
end
|
||
|
||
context "when logged in" do
|
||
it "succeeds" do
|
||
sign_in(user)
|
||
get "/read"
|
||
expect(response.status).to eq(200)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "best_periods_for" do
|
||
it "works" do
|
||
expect(ListController.best_periods_for(nil)).to eq([:all])
|
||
expect(ListController.best_periods_for(5.years.ago)).to eq([:all])
|
||
expect(ListController.best_periods_for(2.years.ago)).to eq(%i[yearly all])
|
||
expect(ListController.best_periods_for(6.months.ago)).to eq(%i[quarterly yearly all])
|
||
expect(ListController.best_periods_for(2.months.ago)).to eq(%i[monthly quarterly yearly all])
|
||
expect(ListController.best_periods_for(2.weeks.ago)).to eq(
|
||
%i[weekly monthly quarterly yearly all],
|
||
)
|
||
expect(ListController.best_periods_for(2.days.ago)).to eq(
|
||
%i[daily weekly monthly quarterly yearly all],
|
||
)
|
||
end
|
||
|
||
it "supports default period" do
|
||
expect(ListController.best_periods_for(nil, :yearly)).to eq(%i[yearly all])
|
||
expect(ListController.best_periods_for(nil, :quarterly)).to eq(%i[quarterly all])
|
||
expect(ListController.best_periods_for(nil, :monthly)).to eq(%i[monthly all])
|
||
expect(ListController.best_periods_for(nil, :weekly)).to eq(%i[weekly all])
|
||
expect(ListController.best_periods_for(nil, :daily)).to eq(%i[daily all])
|
||
end
|
||
end
|
||
|
||
describe "user_topics_feed" do
|
||
it "returns 404 if `hide_profile` user option is checked" do
|
||
user.user_option.update_columns(hide_profile: true)
|
||
get "/u/#{user.username}/activity/topics.rss"
|
||
expect(response.status).to eq(404)
|
||
end
|
||
end
|
||
|
||
describe "set_category" do
|
||
let(:category) { Fabricate(:category_with_definition) }
|
||
let(:subcategory) { Fabricate(:category_with_definition, parent_category_id: category.id) }
|
||
let(:subsubcategory) do
|
||
Fabricate(:category_with_definition, parent_category_id: subcategory.id)
|
||
end
|
||
|
||
before { SiteSetting.max_category_nesting = 3 }
|
||
|
||
it "redirects to URL with the updated slug" do
|
||
get "/c/hello/world/bye/#{subsubcategory.id}"
|
||
expect(response.status).to eq(301)
|
||
expect(response).to redirect_to(
|
||
"/c/#{category.slug}/#{subcategory.slug}/#{subsubcategory.slug}/#{subsubcategory.id}",
|
||
)
|
||
|
||
get "/c/#{category.slug}/#{subcategory.slug}/#{subsubcategory.slug}/#{subsubcategory.id}"
|
||
expect(response.status).to eq(200)
|
||
end
|
||
|
||
it "redirects to URL with correct case slug" do
|
||
category.update!(slug: "hello")
|
||
|
||
get "/c/Hello/#{category.id}"
|
||
expect(response).to redirect_to("/c/hello/#{category.id}")
|
||
|
||
get "/c/hello/#{category.id}"
|
||
expect(response.status).to eq(200)
|
||
end
|
||
|
||
context "with encoded slugs" do
|
||
it "does not create a redirect loop" do
|
||
category = Fabricate(:category)
|
||
category.update_columns(slug: CGI.escape("systèmes"))
|
||
|
||
get "/c/syst%C3%A8mes/#{category.id}"
|
||
expect(response.status).to eq(200)
|
||
end
|
||
end
|
||
|
||
context "with lowercase encoded slugs" do
|
||
it "does not create a redirect loop" do
|
||
category = Fabricate(:category)
|
||
category.update_columns(slug: CGI.escape("systèmes").downcase)
|
||
|
||
get "/c/syst%C3%A8mes/#{category.id}"
|
||
expect(response.status).to eq(200)
|
||
end
|
||
end
|
||
|
||
context "with subfolder" do
|
||
it "main category redirects to URL containing the updated slug" do
|
||
set_subfolder "/forum"
|
||
get "/c/#{category.slug}"
|
||
|
||
expect(response.status).to eq(301)
|
||
expect(response).to redirect_to("/forum/c/#{category.slug}/#{category.id}")
|
||
end
|
||
|
||
it "sub-sub-category redirects to URL containing the updated slug" do
|
||
set_subfolder "/forum"
|
||
get "/c/hello/world/bye/#{subsubcategory.id}"
|
||
|
||
expect(response.status).to eq(301)
|
||
expect(response).to redirect_to(
|
||
"/forum/c/#{category.slug}/#{subcategory.slug}/#{subsubcategory.slug}/#{subsubcategory.id}",
|
||
)
|
||
end
|
||
end
|
||
|
||
context "when redirect raises an unsafe redirect error" do
|
||
let(:fake_logger) { FakeLogger.new }
|
||
|
||
before do
|
||
ListController
|
||
.any_instance
|
||
.stubs(:redirect_to)
|
||
.raises(ActionController::Redirecting::UnsafeRedirectError)
|
||
Rails.logger.broadcast_to(fake_logger)
|
||
end
|
||
|
||
after { Rails.logger.stop_broadcasting_to(fake_logger) }
|
||
|
||
it "renders a 404" do
|
||
get "/c/hello/world/bye/#{subsubcategory.id}"
|
||
expect(response).to have_http_status :not_found
|
||
end
|
||
|
||
it "doesn’t log an error" do
|
||
get "/c/hello/world/bye/#{subsubcategory.id}"
|
||
expect(fake_logger.fatals).to be_empty
|
||
end
|
||
end
|
||
|
||
context "when provided slug is gibberish" do
|
||
it "redirects to the proper category" do
|
||
get "/c/summit'%22()&%25%3Czzz%3E%3CScRiPt%20%3EqlJ2(9585)%3C%2FScRiPt%3E/#{category.id}"
|
||
expect(response).to have_http_status :moved_permanently
|
||
expect(response).to redirect_to("/c/#{category.slug}/#{category.id}")
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "shared drafts" do
|
||
fab!(:category1) { Fabricate(:category) }
|
||
fab!(:category2) { Fabricate(:category) }
|
||
|
||
fab!(:topic1) { Fabricate(:topic, category: category1) }
|
||
fab!(:topic2) { Fabricate(:topic, category: category2) }
|
||
|
||
fab!(:shared_draft_topic) { Fabricate(:topic, category: category1) }
|
||
fab!(:shared_draft) { Fabricate(:shared_draft, topic: shared_draft_topic, category: category2) }
|
||
|
||
it "are not displayed if they are disabled" do
|
||
SiteSetting.shared_drafts_category = ""
|
||
sign_in(admin)
|
||
|
||
get "/c/#{category1.slug}/#{category1.id}.json"
|
||
expect(response.parsed_body["topic_list"]["shared_drafts"]).to eq(nil)
|
||
expect(response.parsed_body["topic_list"]["topics"].map { |t| t["id"] }).to contain_exactly(
|
||
topic1.id,
|
||
shared_draft_topic.id,
|
||
)
|
||
end
|
||
|
||
it "are displayed in both shared drafts category and target category" do
|
||
SiteSetting.shared_drafts_category = category1.id
|
||
sign_in(admin)
|
||
|
||
get "/c/#{category1.slug}/#{category1.id}.json"
|
||
expect(response.parsed_body["topic_list"]["shared_drafts"]).to be_nil
|
||
expect(response.parsed_body["topic_list"]["topics"].map { |t| t["id"] }).to contain_exactly(
|
||
topic1.id,
|
||
shared_draft_topic.id,
|
||
)
|
||
|
||
get "/c/#{category2.slug}/#{category2.id}.json"
|
||
expect(
|
||
response.parsed_body["topic_list"]["shared_drafts"].map { |t| t["id"] },
|
||
).to contain_exactly(shared_draft_topic.id)
|
||
expect(response.parsed_body["topic_list"]["topics"].map { |t| t["id"] }).to contain_exactly(
|
||
topic2.id,
|
||
)
|
||
end
|
||
end
|
||
|
||
describe "body class" do
|
||
it "pre-renders the correct body class for categories" do
|
||
c = Fabricate(:category, slug: "myparentslug")
|
||
sub_c = Fabricate(:category, parent_category: c, slug: "mychildslug")
|
||
|
||
get "/c/#{c.slug}/#{sub_c.slug}/#{sub_c.id}"
|
||
|
||
expect(response.body).to have_tag "body", with: { class: "category-myparentslug-mychildslug" }
|
||
end
|
||
end
|
||
|
||
describe "#filter" do
|
||
fab!(:category) { Fabricate(:category, slug: "category-slug") }
|
||
fab!(:tag) { Fabricate(:tag, name: "tag1") }
|
||
fab!(:group)
|
||
fab!(:private_category) { Fabricate(:private_category, group:, slug: "private-category-slug") }
|
||
fab!(:private_message_topic)
|
||
fab!(:topic_in_private_category) { Fabricate(:topic, category: private_category) }
|
||
|
||
it "should not return topics that the user is not allowed to view" do
|
||
sign_in(user)
|
||
|
||
get "/filter.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(
|
||
response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] },
|
||
).to contain_exactly(topic.id)
|
||
end
|
||
|
||
it "should not return topics that an anon user is not allowed to view" do
|
||
get "/filter.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(
|
||
response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] },
|
||
).to contain_exactly(topic.id)
|
||
end
|
||
|
||
it "returns category definition topics if `show_category_definitions_in_topic_lists` site setting is enabled" do
|
||
category_topic = Fabricate(:topic, category: category)
|
||
category.update!(topic: category_topic)
|
||
|
||
SiteSetting.show_category_definitions_in_topic_lists = true
|
||
|
||
sign_in(user)
|
||
|
||
get "/filter.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(
|
||
response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] },
|
||
).to contain_exactly(topic.id, category_topic.id)
|
||
end
|
||
|
||
it "does not return category definition topics if `show_category_definitions_in_topic_lists` site setting is disabled" do
|
||
category_topic = Fabricate(:topic, category: category)
|
||
category.update!(topic: category_topic)
|
||
|
||
SiteSetting.show_category_definitions_in_topic_lists = false
|
||
|
||
sign_in(user)
|
||
|
||
get "/filter.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(
|
||
response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] },
|
||
).to contain_exactly(topic.id)
|
||
end
|
||
|
||
it "should accept the `page` query parameter" do
|
||
topic_with_tag = Fabricate(:topic, tags: [tag])
|
||
topic2_with_tag = Fabricate(:topic, tags: [tag])
|
||
|
||
stub_const(TopicQuery, "DEFAULT_PER_PAGE_COUNT", 1) do
|
||
sign_in(user)
|
||
|
||
get "/filter.json", params: { q: "tags:tag1" }
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
parsed = response.parsed_body
|
||
|
||
expect(parsed["topic_list"]["topics"].length).to eq(1)
|
||
expect(parsed["topic_list"]["topics"].first["id"]).to eq(topic2_with_tag.id)
|
||
|
||
get "/filter.json", params: { q: "tags:tag1", page: 1 }
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
parsed = response.parsed_body
|
||
|
||
expect(parsed["topic_list"]["topics"].length).to eq(1)
|
||
expect(parsed["topic_list"]["topics"].first["id"]).to eq(topic_with_tag.id)
|
||
end
|
||
end
|
||
|
||
it "should filter with tag_group option" do
|
||
topic_with_tag = Fabricate(:topic, tags: [tag])
|
||
topic2_with_tag = Fabricate(:topic, tags: [tag])
|
||
tag_group = Fabricate(:tag_group, tags: [tag])
|
||
|
||
sign_in(user)
|
||
|
||
get "/filter.json", params: { q: "tag_group:#{tag_group.name}" }
|
||
|
||
parsed = response.parsed_body
|
||
expect(response.status).to eq(200)
|
||
expect(parsed["topic_list"]["topics"].length).to eq(2)
|
||
expect(parsed["topic_list"]["topics"].map { |topic| topic["id"] }).to contain_exactly(
|
||
topic_with_tag.id,
|
||
topic2_with_tag.id,
|
||
)
|
||
end
|
||
|
||
describe "when filtering with the `created-by:<username>` filter" do
|
||
fab!(:topic2) { Fabricate(:topic, user: admin) }
|
||
|
||
before do
|
||
topic.update!(user: user)
|
||
user.update!(username: "username")
|
||
admin.update!(username: "username2")
|
||
end
|
||
|
||
it "returns only topics created by the user when `q` query param is `created-by:username`" do
|
||
sign_in(user)
|
||
|
||
get "/filter.json", params: { q: "created-by:username" }
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(
|
||
response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] },
|
||
).to contain_exactly(topic.id)
|
||
end
|
||
|
||
it "returns only topics created by either user when `q` query param is `created-by:username,username2`" do
|
||
sign_in(user)
|
||
|
||
get "/filter.json", params: { q: "created-by:username,username2" }
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(
|
||
response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] },
|
||
).to contain_exactly(topic.id, topic2.id)
|
||
end
|
||
end
|
||
|
||
describe "when filtering with the `category:<category_slug>` filter" do
|
||
fab!(:topic_in_category) { Fabricate(:topic, category:) }
|
||
|
||
it "does not return any topics when `q` query param is `category:private-category-slug` and user is not allowed to see category" do
|
||
sign_in(user)
|
||
|
||
get "/filter.json", params: { q: "category:private-category-slug" }
|
||
|
||
expect(response.status).to eq(200)
|
||
expect(response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] }).to eq([])
|
||
end
|
||
|
||
it "returns only topics in the category when `q` query param is `category:private-category-slug` and user can see category" do
|
||
group.add(user)
|
||
|
||
sign_in(user)
|
||
|
||
get "/filter.json", params: { q: "category:private-category-slug" }
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(
|
||
response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] },
|
||
).to contain_exactly(topic_in_private_category.id)
|
||
end
|
||
end
|
||
|
||
describe "when filtering with the `in:<topic_notification_level>` filter" do
|
||
fab!(:user_muted_topic) do
|
||
Fabricate(:topic).tap do |topic|
|
||
TopicUser.change(
|
||
user.id,
|
||
topic.id,
|
||
notification_level: TopicUser.notification_levels[:muted],
|
||
)
|
||
end
|
||
end
|
||
|
||
fab!(:user_tracking_topic) do
|
||
Fabricate(:topic).tap do |topic|
|
||
TopicUser.change(
|
||
user.id,
|
||
topic.id,
|
||
notification_level: TopicUser.notification_levels[:tracking],
|
||
)
|
||
end
|
||
end
|
||
|
||
it "does not return topics that are muted by the user when `q` query param does not include `in:muted`" do
|
||
sign_in(user)
|
||
|
||
get "/filter.json", params: { q: "in:tracking" }
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] }).to eq(
|
||
[user_tracking_topic.id],
|
||
)
|
||
end
|
||
|
||
it "only return topics that are muted by the user when `q` query param is `in:muted`" do
|
||
sign_in(user)
|
||
|
||
get "/filter.json", params: { q: "in:muted" }
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] }).to eq(
|
||
[user_muted_topic.id],
|
||
)
|
||
end
|
||
end
|
||
|
||
describe "when ordering using the `order:` filter" do
|
||
fab!(:topic2) { Fabricate(:topic, views: 2) }
|
||
fab!(:topic3) { Fabricate(:topic, views: 3) }
|
||
fab!(:topic4) { Fabricate(:topic, views: 1) }
|
||
|
||
it "return topics ordered by topic bumped at date in descending order when `q` query param is not present" do
|
||
sign_in(user)
|
||
|
||
get "/filter.json"
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] }).to eq(
|
||
[topic4.id, topic3.id, topic2.id, topic.id],
|
||
)
|
||
end
|
||
|
||
it "return topics ordered by views when `q` query param is `order:views`" do
|
||
sign_in(user)
|
||
|
||
get "/filter.json", params: { q: "order:views" }
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] }).to eq(
|
||
[topic3.id, topic2.id, topic4.id, topic.id],
|
||
)
|
||
end
|
||
end
|
||
|
||
describe "when filtering by status" do
|
||
fab!(:group)
|
||
fab!(:private_category) { Fabricate(:private_category, group: group) }
|
||
fab!(:topic_in_private_category) { Fabricate(:topic, category: private_category) }
|
||
|
||
it "does not return topics that are unlisted when `q` query param is `status:unlisted` for a user that cannot view unlisted topics" do
|
||
Topic.update_all(deleted_at: true)
|
||
topic.update!(visible: false)
|
||
|
||
sign_in(user)
|
||
|
||
get "/filter.json", params: { q: "status:unlisted" }
|
||
|
||
expect(response.status).to eq(200)
|
||
expect(response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] }).to eq([])
|
||
end
|
||
|
||
it "returns topics that are unlisted when `q` query param is `status:unlisted` for a user that can view unlisted topics" do
|
||
Topic.update_all(visible: true)
|
||
topic.update!(visible: false)
|
||
|
||
sign_in(admin)
|
||
|
||
get "/filter.json", params: { q: "status:unlisted" }
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(
|
||
response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] },
|
||
).to contain_exactly(topic.id)
|
||
end
|
||
|
||
it "ignores the `status` filter for a user that cannot view deleted topics when `q` query param is `status:deleted`" do
|
||
Topic.update_all(deleted_at: nil)
|
||
topic.update!(deleted_at: Time.zone.now)
|
||
|
||
sign_in(user)
|
||
|
||
get "/filter.json", params: { q: "status:deleted" }
|
||
|
||
expect(response.status).to eq(200)
|
||
expect(response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] }).to eq([])
|
||
end
|
||
|
||
it "returns topics that are deleted when `q` query param is `status:deleted` for a user that can view deleted topics" do
|
||
Topic.update_all(deleted_at: nil)
|
||
topic.update!(deleted_at: Time.zone.now)
|
||
|
||
sign_in(admin)
|
||
|
||
get "/filter.json", params: { q: "status:deleted" }
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(
|
||
response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] },
|
||
).to contain_exactly(topic.id)
|
||
end
|
||
|
||
it "does not return topics from read restricted categories when `q` query param is `status:public`" do
|
||
group.add(user)
|
||
|
||
sign_in(user)
|
||
|
||
get "/filter.json", params: { q: "status:public" }
|
||
|
||
expect(response.status).to eq(200)
|
||
|
||
expect(
|
||
response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] },
|
||
).to contain_exactly(topic.id)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "#new" do
|
||
def extract_topic_ids(response)
|
||
response.parsed_body["topic_list"]["topics"].map { |topics| topics["id"] }
|
||
end
|
||
|
||
context "when the user is part of the `experimental_new_new_view_groups` site setting group" do
|
||
fab!(:category)
|
||
fab!(:tag)
|
||
|
||
fab!(:new_reply) { Fabricate(:new_reply_topic, current_user: user) }
|
||
fab!(:new_topic) { Fabricate(:post).topic }
|
||
fab!(:old_topic) { Fabricate(:read_topic, current_user: user) }
|
||
|
||
fab!(:new_reply_in_category) do
|
||
Fabricate(:new_reply_topic, category: category, current_user: user)
|
||
end
|
||
fab!(:new_topic_in_category) do
|
||
Fabricate(:post, topic: Fabricate(:topic, category: category)).topic
|
||
end
|
||
fab!(:old_topic_in_category) do
|
||
Fabricate(:read_topic, category: category, current_user: user)
|
||
end
|
||
|
||
fab!(:new_reply_with_tag) { Fabricate(:new_reply_topic, tags: [tag], current_user: user) }
|
||
fab!(:new_topic_with_tag) { Fabricate(:post, topic: Fabricate(:topic, tags: [tag])).topic }
|
||
fab!(:old_topic_with_tag) { Fabricate(:read_topic, tags: [tag], current_user: user) }
|
||
|
||
before do
|
||
TopicUser.update_last_read(user, topic.id, 1, 1, 1)
|
||
|
||
SiteSetting.experimental_new_new_view_groups = group.name
|
||
group.add(user)
|
||
|
||
sign_in(user)
|
||
end
|
||
|
||
it "returns new topics and topics with new replies" do
|
||
get "/new.json"
|
||
|
||
ids = extract_topic_ids(response)
|
||
expect(ids).to contain_exactly(
|
||
new_reply.id,
|
||
new_topic.id,
|
||
new_reply_in_category.id,
|
||
new_topic_in_category.id,
|
||
new_reply_with_tag.id,
|
||
new_topic_with_tag.id,
|
||
)
|
||
end
|
||
|
||
context "when the subset param is set to topics" do
|
||
it "returns only new topics" do
|
||
get "/new.json", params: { subset: "topics" }
|
||
|
||
ids = extract_topic_ids(response)
|
||
expect(ids).to contain_exactly(
|
||
new_topic.id,
|
||
new_topic_in_category.id,
|
||
new_topic_with_tag.id,
|
||
)
|
||
end
|
||
end
|
||
|
||
context "when the subset param is set to replies" do
|
||
it "returns only topics with new replies" do
|
||
get "/new.json", params: { subset: "replies" }
|
||
|
||
ids = extract_topic_ids(response)
|
||
expect(ids).to contain_exactly(
|
||
new_reply.id,
|
||
new_reply_in_category.id,
|
||
new_reply_with_tag.id,
|
||
)
|
||
end
|
||
end
|
||
|
||
context "when filtering the list to a specific category" do
|
||
it "returns new topics in that category" do
|
||
get "/c/#{category.slug}/#{category.id}/l/new.json"
|
||
|
||
ids = extract_topic_ids(response)
|
||
expect(ids).to contain_exactly(new_topic_in_category.id, new_reply_in_category.id)
|
||
end
|
||
|
||
it "respects the subset param" do
|
||
get "/c/#{category.slug}/#{category.id}/l/new.json", params: { subset: "topics" }
|
||
|
||
ids = extract_topic_ids(response)
|
||
expect(ids).to contain_exactly(new_topic_in_category.id)
|
||
|
||
get "/c/#{category.slug}/#{category.id}/l/new.json", params: { subset: "replies" }
|
||
|
||
ids = extract_topic_ids(response)
|
||
expect(ids).to contain_exactly(new_reply_in_category.id)
|
||
end
|
||
end
|
||
|
||
context "when filtering the list to topics with a specific tag" do
|
||
it "returns new topics with the specified tag" do
|
||
get "/tag/#{tag.name}/l/new.json"
|
||
|
||
ids = extract_topic_ids(response)
|
||
expect(ids).to contain_exactly(new_topic_with_tag.id, new_reply_with_tag.id)
|
||
end
|
||
|
||
it "respects the subset param" do
|
||
get "/tag/#{tag.name}/l/new.json", params: { subset: "topics" }
|
||
|
||
ids = extract_topic_ids(response)
|
||
expect(ids).to contain_exactly(new_topic_with_tag.id)
|
||
|
||
get "/tag/#{tag.name}/l/new.json", params: { subset: "replies" }
|
||
|
||
ids = extract_topic_ids(response)
|
||
expect(ids).to contain_exactly(new_reply_with_tag.id)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|