# encoding: utf-8 require 'rails_helper' require_dependency 'post_creator' describe Category do let(:user) { Fabricate(:user) } it { is_expected.to validate_presence_of :user_id } it { is_expected.to validate_presence_of :name } it 'validates uniqueness of name' do Fabricate(:category) is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_category_id).case_insensitive end it 'validates inclusion of search_priority' do category = Fabricate.build(:category, user: user) expect(category.valid?).to eq(true) category.search_priority = Searchable::PRIORITIES.values.last + 1 expect(category.valid?).to eq(false) expect(category.errors.keys).to contain_exactly(:search_priority) end it 'validates uniqueness in case insensitive way' do Fabricate(:category, name: "Cats") cats = Fabricate.build(:category, name: "cats") expect(cats).to_not be_valid expect(cats.errors[:name]).to be_present end describe "resolve_permissions" do it "can determine read_restricted" do read_restricted, resolved = Category.resolve_permissions(everyone: :full) expect(read_restricted).to be false expect(resolved).to be_blank end end describe "permissions_params" do it "returns the right group names and permission type" do category = Fabricate(:category) group = Fabricate(:group) category_group = Fabricate(:category_group, category: category, group: group) expect(category.permissions_params).to eq("#{group.name}" => category_group.permission_type) end end describe "topic_create_allowed and post_create_allowed" do it "works" do # NOTE we also have the uncategorized category ... hence the increased count _default_category = Fabricate(:category) full_category = Fabricate(:category) can_post_category = Fabricate(:category) can_read_category = Fabricate(:category) user = Fabricate(:user) group = Fabricate(:group) group.add(user) group.save admin = Fabricate(:admin) full_category.set_permissions(group => :full) full_category.save can_post_category.set_permissions(group => :create_post) can_post_category.save can_read_category.set_permissions(group => :readonly) can_read_category.save guardian = Guardian.new(admin) expect(Category.topic_create_allowed(guardian).count).to be(5) expect(Category.post_create_allowed(guardian).count).to be(5) expect(Category.secured(guardian).count).to be(5) guardian = Guardian.new(user) expect(Category.secured(guardian).count).to be(5) expect(Category.post_create_allowed(guardian).count).to be(4) expect(Category.topic_create_allowed(guardian).count).to be(3) # explicitly allowed once, default allowed once expect(Category.scoped_to_permissions(nil, [:readonly]).count).to be(2) # everyone has special semantics, test it as well can_post_category.set_permissions(everyone: :create_post) can_post_category.save expect(Category.post_create_allowed(guardian).count).to be(4) # anonymous has permission to create no topics guardian = Guardian.new(nil) expect(Category.post_create_allowed(guardian).count).to be(0) expect(Category.topic_create_allowed(guardian).count).to be(0) expect(Category.scoped_to_permissions(guardian, [:readonly]).count).to be(3) end end describe "security" do let(:category) { Fabricate(:category) } let(:category_2) { Fabricate(:category) } let(:user) { Fabricate(:user) } let(:group) { Fabricate(:group) } it "secures categories correctly" do expect(category.read_restricted?).to be false category.set_permissions({}) expect(category.read_restricted?).to be true category.set_permissions(everyone: :full) expect(category.read_restricted?).to be false expect(user.secure_categories).to be_empty group.add(user) group.save category.set_permissions(group.id => :full) category.save user.reload expect(user.secure_categories).to eq([category]) end it "lists all secured categories correctly" do uncategorized = Category.find(SiteSetting.uncategorized_category_id) group.add(user) category.set_permissions(group.id => :full) category.save! category_2.set_permissions(group.id => :full) category_2.save! expect(Category.secured).to match_array([uncategorized]) expect(Category.secured(Guardian.new(user))).to match_array([uncategorized, category, category_2]) end end it "strips leading blanks" do expect(Fabricate(:category, name: " music").name).to eq("music") end it "strips trailing blanks" do expect(Fabricate(:category, name: "bugs ").name).to eq("bugs") end it "strips leading and trailing blanks" do expect(Fabricate(:category, name: " blanks ").name).to eq("blanks") end it "sets name_lower" do expect(Fabricate(:category, name: "Not MySQL").name_lower).to eq("not mysql") end it "has custom fields" do category = Fabricate(:category, name: " music") expect(category.custom_fields["a"]).to be_nil category.custom_fields["bob"] = "marley" category.custom_fields["jack"] = "black" category.save category = Category.find(category.id) expect(category.custom_fields).to eq("bob" => "marley", "jack" => "black") end describe "short name" do let!(:category) { Fabricate(:category, name: 'xx') } it "creates the category" do expect(category).to be_present end it 'has one topic' do expect(Topic.where(category_id: category.id).count).to eq(1) end end describe 'non-english characters' do context 'uses ascii slug generator' do before do SiteSetting.slug_generation_method = 'ascii' @category = Fabricate(:category, name: "测试") end after { @category.destroy } it "creates a blank slug" do expect(@category.slug).to be_blank expect(@category.slug_for_url).to eq("#{@category.id}-category") end end context 'uses none slug generator' do before do SiteSetting.slug_generation_method = 'none' @category = Fabricate(:category, name: "测试") end after do SiteSetting.slug_generation_method = 'ascii' @category.destroy end it "creates a blank slug" do expect(@category.slug).to be_blank expect(@category.slug_for_url).to eq("#{@category.id}-category") end end context 'uses encoded slug generator' do before do SiteSetting.slug_generation_method = 'encoded' @category = Fabricate(:category, name: "测试") end after do SiteSetting.slug_generation_method = 'ascii' @category.destroy end it "creates a slug" do expect(@category.slug).to eq("测试") expect(@category.slug_for_url).to eq("测试") end end end describe 'slug would be a number' do let(:category) { Fabricate.build(:category, name: "2") } it 'creates a blank slug' do expect(category.slug).to be_blank expect(category.slug_for_url).to eq("#{category.id}-category") end end describe 'custom slug can be provided' do it 'can be sanitized' do @c = Fabricate(:category, name: "Fun Cats", slug: "fun-cats") @cat = Fabricate(:category, name: "love cats", slug: "love-cats") @c.slug = ' invalid slug' @c.save expect(@c.slug).to eq('invalid-slug') c = Fabricate.build(:category, name: "More Fun Cats", slug: "love-cats") expect(c).not_to be_valid expect(c.errors[:slug]).to be_present @cat.slug = "#{@c.id}-category" expect(@cat).not_to be_valid expect(@cat.errors[:slug]).to be_present @cat.slug = "#{@cat.id}-category" expect(@cat).to be_valid expect(@cat.errors[:slug]).not_to be_present end end describe 'description_text' do it 'correctly generates text description as needed' do c = Category.new expect(c.description_text).to be_nil c.description = "<hello test." expect(c.description_text).to eq("test') expect(category.valid?).to eq(false) expect(category.errors.full_messages.join).not_to match(//) end context "with a duplicate email in a group" do let(:group) { Fabricate(:group, name: 'testgroup', incoming_email: 'test@example.com') } it "adds an error with an invalid email" do category = Category.new(name: 'test', user: user, email_in: group.incoming_email) expect(category.valid?).to eq(false) end end context "with duplicate email in a category" do let!(:category) { Fabricate(:category, user: user, name: 'cool', email_in: 'test@example.com') } it "adds an error with an invalid email" do category = Category.new(name: 'test', user: user, email_in: "test@example.com") expect(category.valid?).to eq(false) expect(category.errors.full_messages.join).not_to match(//) end end end describe 'require topic/post approval' do let(:category) { Fabricate(:category) } describe '#require_topic_approval?' do before do category.custom_fields[Category::REQUIRE_TOPIC_APPROVAL] = true category.save end it { expect(category.reload.require_topic_approval?).to eq(true) } end describe '#require_reply_approval?' do before do category.custom_fields[Category::REQUIRE_REPLY_APPROVAL] = true category.save end it { expect(category.reload.require_reply_approval?).to eq(true) } end end describe 'auto bump' do after do RateLimiter.disable end it 'should correctly automatically bump topics' do freeze_time 1.second.ago category = Fabricate(:category) category.clear_auto_bump_cache! freeze_time 1.second.from_now post1 = create_post(category: category) freeze_time 1.second.from_now _post2 = create_post(category: category) freeze_time 1.second.from_now _post3 = create_post(category: category) # no limits on post creation or category creation please RateLimiter.enable time = 1.month.from_now freeze_time time expect(category.auto_bump_topic!).to eq(false) expect(Topic.where(bumped_at: time).count).to eq(0) category.num_auto_bump_daily = 2 category.save! expect(category.auto_bump_topic!).to eq(true) expect(Topic.where(bumped_at: time).count).to eq(1) # our extra bump message expect(post1.topic.reload.posts_count).to eq(2) time = time + 13.hours freeze_time time expect(category.auto_bump_topic!).to eq(true) expect(Topic.where(bumped_at: time).count).to eq(1) expect(category.auto_bump_topic!).to eq(false) expect(Topic.where(bumped_at: time).count).to eq(1) time = 1.month.from_now freeze_time time category.auto_bump_limiter.clear! expect(Category.auto_bump_topic!).to eq(true) expect(Topic.where(bumped_at: time).count).to eq(1) category.num_auto_bump_daily = "" category.save! expect(Category.auto_bump_topic!).to eq(false) end end describe "validate permissions compatibility" do let(:admin) { Fabricate(:admin) } let(:group) { Fabricate(:group) } let(:group2) { Fabricate(:group) } let(:parent_category) { Fabricate(:category, name: "parent") } let(:subcategory) { Fabricate(:category, name: "child1", parent_category_id: parent_category.id) } let(:subcategory2) { Fabricate(:category, name: "child2", parent_category_id: parent_category.id) } context "when changing subcategory permissions" do it "it is not valid if permissions are less restrictive" do parent_category.set_permissions(group => :readonly) parent_category.save! subcategory.set_permissions(group => :full, group2 => :readonly) expect(subcategory.valid?).to eq(false) expect(subcategory.errors.full_messages).to contain_exactly(I18n.t("category.errors.permission_conflict", group_names: group2.name)) end it "is valid if permissions are same or more restrictive" do parent_category.set_permissions(group => :full, group2 => :create_post) parent_category.save! subcategory.set_permissions(group => :create_post, group2 => :full) expect(subcategory.valid?).to eq(true) end it "is valid if everyone has access to parent category" do parent_category.set_permissions(everyone: :readonly) parent_category.save! subcategory.set_permissions(group => :create_post, group2 => :create_post) expect(subcategory.valid?).to eq(true) end end context "when changing parent category permissions" do it "it is not valid if subcategory permissions are less restrictive" do subcategory.set_permissions(group => :create_post) subcategory.save! subcategory2.set_permissions(group => :create_post, group2 => :create_post) subcategory2.save! parent_category.set_permissions(group => :readonly) expect(parent_category.valid?).to eq(false) expect(parent_category.errors.full_messages).to contain_exactly(I18n.t("category.errors.permission_conflict", group_names: group2.name)) end it "is valid if subcategory permissions are same or more restrictive" do subcategory.set_permissions(group => :create_post) subcategory.save! subcategory2.set_permissions(group => :create_post, group2 => :create_post) subcategory2.save! parent_category.set_permissions(group => :full, group2 => :create_post) expect(parent_category.valid?).to eq(true) end it "is valid if everyone has access to parent category" do subcategory.set_permissions(group => :create_post) subcategory.save parent_category.set_permissions(everyone: :readonly) expect(parent_category.valid?).to eq(true) end end end end