mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 11:44:49 +08:00
backend for secure categories mostly done (todo pm groups)
This commit is contained in:
parent
a99efecb39
commit
5cfcdc7ef0
|
@ -13,6 +13,9 @@ class Category < ActiveRecord::Base
|
|||
has_many :category_featured_users
|
||||
has_many :featured_users, through: :category_featured_users, source: :user
|
||||
|
||||
has_many :category_groups
|
||||
has_many :groups, through: :category_groups
|
||||
|
||||
validates :user_id, presence: true
|
||||
validates :name, presence: true, uniqueness: true, length: { in: 1..50 }
|
||||
validate :uncategorized_validator
|
||||
|
@ -28,6 +31,32 @@ class Category < ActiveRecord::Base
|
|||
|
||||
delegate :post_template, to: 'self.class'
|
||||
|
||||
# Internal: Update category stats: # of topics in past year, month, week for
|
||||
# all categories.
|
||||
def self.update_stats
|
||||
topics = Topic
|
||||
.select("COUNT(*)")
|
||||
.where("topics.category_id = categories.id")
|
||||
.where("categories.topic_id <> topics.id")
|
||||
.visible
|
||||
|
||||
topic_count = topics.to_sql
|
||||
topics_year = topics.created_since(1.year.ago).to_sql
|
||||
topics_month = topics.created_since(1.month.ago).to_sql
|
||||
topics_week = topics.created_since(1.week.ago).to_sql
|
||||
|
||||
Category.update_all("topic_count = (#{topic_count}),
|
||||
topics_year = (#{topics_year}),
|
||||
topics_month = (#{topics_month}),
|
||||
topics_week = (#{topics_week})")
|
||||
end
|
||||
|
||||
# Internal: Generate the text of post prompting to enter category
|
||||
# description.
|
||||
def self.post_template
|
||||
I18n.t("category.post_template", replace_paragraph: I18n.t("category.replace_paragraph"))
|
||||
end
|
||||
|
||||
def create_category_definition
|
||||
create_topic!(title: I18n.t("category.topic_prefix", category: name), user: user, pinned_at: Time.now)
|
||||
update_column(:topic_id, topic.id)
|
||||
|
@ -66,29 +95,23 @@ class Category < ActiveRecord::Base
|
|||
errors.add(:slug, I18n.t(:is_reserved)) if slug == SiteSetting.uncategorized_name
|
||||
end
|
||||
|
||||
# Internal: Update category stats: # of topics in past year, month, week for
|
||||
# all categories.
|
||||
def self.update_stats
|
||||
topics = Topic
|
||||
.select("COUNT(*)")
|
||||
.where("topics.category_id = categories.id")
|
||||
.where("categories.topic_id <> topics.id")
|
||||
.visible
|
||||
|
||||
topic_count = topics.to_sql
|
||||
topics_year = topics.created_since(1.year.ago).to_sql
|
||||
topics_month = topics.created_since(1.month.ago).to_sql
|
||||
topics_week = topics.created_since(1.week.ago).to_sql
|
||||
|
||||
Category.update_all("topic_count = (#{topic_count}),
|
||||
topics_year = (#{topics_year}),
|
||||
topics_month = (#{topics_month}),
|
||||
topics_week = (#{topics_week})")
|
||||
def secure?
|
||||
self.secure
|
||||
end
|
||||
|
||||
# Internal: Generate the text of post prompting to enter category
|
||||
# description.
|
||||
def self.post_template
|
||||
I18n.t("category.post_template", replace_paragraph: I18n.t("category.replace_paragraph"))
|
||||
def deny(group)
|
||||
if group == :all
|
||||
self.secure = true
|
||||
end
|
||||
end
|
||||
|
||||
def allow(group)
|
||||
if group == :all
|
||||
self.secure = false
|
||||
category_groups.clear
|
||||
else
|
||||
groups.push(group)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
4
app/models/category_group.rb
Normal file
4
app/models/category_group.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
class CategoryGroup < ActiveRecord::Base
|
||||
belongs_to :category
|
||||
belongs_to :group
|
||||
end
|
|
@ -1,5 +1,15 @@
|
|||
class Group < ActiveRecord::Base
|
||||
has_many :category_groups
|
||||
has_many :group_users
|
||||
|
||||
has_many :categories, through: :category_groups
|
||||
has_many :users, through: :group_users
|
||||
|
||||
def self.builtin
|
||||
Enum.new(:moderators, :admins, :trust_level_1, :trust_level_2)
|
||||
end
|
||||
|
||||
def add(user)
|
||||
self.users.push(user)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
class GroupUser < ActiveRecord::Base
|
||||
belongs_to :group
|
||||
belongs_to :user
|
||||
end
|
||||
|
|
|
@ -68,7 +68,7 @@ class Topic < ActiveRecord::Base
|
|||
|
||||
scope :listable_topics, lambda { where('topics.archetype <> ?', [Archetype.private_message]) }
|
||||
|
||||
scope :by_newest, order('created_at desc, id desc')
|
||||
scope :by_newest, order('topics.created_at desc, topics.id desc')
|
||||
|
||||
# Helps us limit how many favorites can be made in a day
|
||||
class FavoriteLimiter < RateLimiter
|
||||
|
|
|
@ -26,6 +26,10 @@ class User < ActiveRecord::Base
|
|||
has_one :github_user_info, dependent: :destroy
|
||||
belongs_to :approved_by, class_name: 'User'
|
||||
|
||||
has_many :group_users
|
||||
has_many :groups, through: :group_users
|
||||
has_many :secure_categories, through: :groups, source: :categories
|
||||
|
||||
validates_presence_of :username
|
||||
validates_presence_of :email
|
||||
validates_uniqueness_of :email
|
||||
|
@ -150,10 +154,6 @@ class User < ActiveRecord::Base
|
|||
u.errors[:username].blank?
|
||||
end
|
||||
|
||||
def enqueue_welcome_message(message_type)
|
||||
return unless SiteSetting.send_welcome_message?
|
||||
Jobs.enqueue(:send_system_message, user_id: id, message_type: message_type)
|
||||
end
|
||||
|
||||
def self.suggest_name(email)
|
||||
return "" unless email
|
||||
|
@ -162,6 +162,25 @@ class User < ActiveRecord::Base
|
|||
name.titleize
|
||||
end
|
||||
|
||||
# Find a user by temporary key, nil if not found or key is invalid
|
||||
def self.find_by_temporary_key(key)
|
||||
user_id = $redis.get("temporary_key:#{key}")
|
||||
if user_id.present?
|
||||
where(id: user_id.to_i).first
|
||||
end
|
||||
end
|
||||
|
||||
def self.find_by_username_or_email(username_or_email)
|
||||
lower_user = username_or_email.downcase
|
||||
lower_email = Email.downcase(username_or_email)
|
||||
where("username_lower = :user or lower(username) = :user or email = :email or lower(name) = :user", user: lower_user, email: lower_email)
|
||||
end
|
||||
|
||||
def enqueue_welcome_message(message_type)
|
||||
return unless SiteSetting.send_welcome_message?
|
||||
Jobs.enqueue(:send_system_message, user_id: id, message_type: message_type)
|
||||
end
|
||||
|
||||
def change_username(new_username)
|
||||
current_username, self.username = username, new_username
|
||||
|
||||
|
@ -185,19 +204,6 @@ class User < ActiveRecord::Base
|
|||
key
|
||||
end
|
||||
|
||||
# Find a user by temporary key, nil if not found or key is invalid
|
||||
def self.find_by_temporary_key(key)
|
||||
user_id = $redis.get("temporary_key:#{key}")
|
||||
if user_id.present?
|
||||
where(id: user_id.to_i).first
|
||||
end
|
||||
end
|
||||
|
||||
def self.find_by_username_or_email(username_or_email)
|
||||
lower_user = username_or_email.downcase
|
||||
lower_email = Email.downcase(username_or_email)
|
||||
where("username_lower = :user or lower(username) = :user or email = :email or lower(name) = :user", user: lower_user, email: lower_email)
|
||||
end
|
||||
|
||||
# tricky, we need our bus to be subscribed from the right spot
|
||||
def sync_notification_channel_position
|
||||
|
@ -510,6 +516,11 @@ class User < ActiveRecord::Base
|
|||
.count
|
||||
end
|
||||
|
||||
def secure_category_ids
|
||||
cats = self.moderator? ? Category.select(:id).where(:secure, true) : secure_categories.select('categories.id')
|
||||
cats.map{|c| c.id}
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def cook
|
||||
|
@ -591,4 +602,5 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
|
@ -35,27 +35,28 @@ class UserAction < ActiveRecord::Base
|
|||
EDIT
|
||||
].each_with_index.to_a.flatten]
|
||||
|
||||
|
||||
def self.stats(user_id, guardian)
|
||||
results = UserAction.select("action_type, COUNT(*) count")
|
||||
.joins(:target_topic)
|
||||
.where(user_id: user_id)
|
||||
.group('action_type')
|
||||
|
||||
# We apply similar filters in stream, might consider trying to consolidate somehow
|
||||
unless guardian.can_see_private_messages?(user_id)
|
||||
results = results.where('topics.archetype <> ?', Archetype::private_message)
|
||||
end
|
||||
# Sam: I tried this in AR and it got complex
|
||||
builder = UserAction.sql_builder <<SQL
|
||||
|
||||
unless guardian.user && guardian.user.id == user_id
|
||||
results = results.where("action_type <> ?", BOOKMARK)
|
||||
end
|
||||
SELECT action_type, COUNT(*) count
|
||||
FROM user_actions a
|
||||
JOIN topics t ON t.id = a.target_topic_id
|
||||
LEFT JOIN posts p on p.id = a.target_post_id
|
||||
JOIN posts p2 on p2.topic_id = a.target_topic_id and p2.post_number = 1
|
||||
LEFT JOIN categories c ON c.id = t.category_id
|
||||
/*where*/
|
||||
GROUP BY action_type
|
||||
SQL
|
||||
|
||||
unless guardian.can_see_deleted_posts?
|
||||
results = results.where('topics.deleted_at IS NULL')
|
||||
end
|
||||
|
||||
results = results.to_a
|
||||
builder.where('a.user_id = :user_id', user_id: user_id)
|
||||
|
||||
apply_common_filters(builder, user_id, guardian)
|
||||
|
||||
results = builder.exec.to_a
|
||||
results.sort! { |a,b| ORDER[a.action_type] <=> ORDER[b.action_type] }
|
||||
|
||||
results
|
||||
|
@ -93,23 +94,14 @@ JOIN posts p2 on p2.topic_id = a.target_topic_id and p2.post_number = 1
|
|||
JOIN users u on u.id = a.acting_user_id
|
||||
JOIN users pu on pu.id = COALESCE(p.user_id, t.user_id)
|
||||
JOIN users au on au.id = a.user_id
|
||||
LEFT JOIN categories c on c.id = t.category_id
|
||||
/*where*/
|
||||
/*order_by*/
|
||||
/*offset*/
|
||||
/*limit*/
|
||||
")
|
||||
|
||||
unless guardian.can_see_deleted_posts?
|
||||
builder.where("p.deleted_at is null and p2.deleted_at is null and t.deleted_at is null")
|
||||
end
|
||||
|
||||
unless guardian.user && guardian.user.id == user_id
|
||||
builder.where("a.action_type not in (#{BOOKMARK})")
|
||||
end
|
||||
|
||||
if !guardian.can_see_private_messages?(user_id) || ignore_private_messages
|
||||
builder.where("t.archetype != :archetype", archetype: Archetype::private_message)
|
||||
end
|
||||
apply_common_filters(builder, user_id, guardian, ignore_private_messages)
|
||||
|
||||
if action_id
|
||||
builder.where("a.id = :id", id: action_id.to_i)
|
||||
|
@ -182,6 +174,34 @@ JOIN users au on au.id = a.user_id
|
|||
end
|
||||
|
||||
protected
|
||||
|
||||
def self.apply_common_filters(builder,user_id,guardian,ignore_private_messages=false)
|
||||
|
||||
|
||||
unless guardian.can_see_deleted_posts?
|
||||
builder.where("p.deleted_at is null and p2.deleted_at is null and t.deleted_at is null")
|
||||
end
|
||||
|
||||
unless guardian.user && guardian.user.id == user_id
|
||||
builder.where("a.action_type not in (#{BOOKMARK})")
|
||||
end
|
||||
|
||||
if !guardian.can_see_private_messages?(user_id) || ignore_private_messages
|
||||
builder.where("t.archetype != :archetype", archetype: Archetype::private_message)
|
||||
end
|
||||
|
||||
unless guardian.is_moderator?
|
||||
allowed = guardian.secure_category_ids
|
||||
if allowed.present?
|
||||
builder.where("( c.secure IS NULL OR
|
||||
c.secure = 'f' OR
|
||||
(c.secure = 't' and c.id in (:cats)) )", cats: guardian.secure_category_ids )
|
||||
else
|
||||
builder.where("(c.secure IS NULL OR c.secure = 'f')")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.require_parameters(data, *params)
|
||||
params.each do |p|
|
||||
raise Discourse::InvalidParameters.new(p) if data[p].nil?
|
||||
|
|
11
db/migrate/20130429000101_add_security_to_categories.rb
Normal file
11
db/migrate/20130429000101_add_security_to_categories.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
class AddSecurityToCategories < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :categories, :secure, :boolean, default: false, null: false
|
||||
|
||||
create_table :category_groups do |t|
|
||||
t.integer :category_id, null: false
|
||||
t.integer :group_id, null: false
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
|
@ -12,7 +12,11 @@ class Guardian
|
|||
end
|
||||
|
||||
def is_admin?
|
||||
!@user.nil? && @user.admin?
|
||||
@user && @user.admin?
|
||||
end
|
||||
|
||||
def is_moderator?
|
||||
@user && @user.moderator?
|
||||
end
|
||||
|
||||
# Can the user see the object?
|
||||
|
@ -329,6 +333,15 @@ class Guardian
|
|||
end
|
||||
|
||||
def can_see_topic?(topic)
|
||||
return false unless topic
|
||||
|
||||
return true if @user && @user.moderator?
|
||||
return false if topic.deleted_at.present?
|
||||
|
||||
if topic.category && topic.category.secure
|
||||
return false unless @user && can_see_category?(topic.category)
|
||||
end
|
||||
|
||||
if topic.private_message?
|
||||
return false if @user.blank?
|
||||
return true if topic.allowed_users.include?(@user)
|
||||
|
@ -337,6 +350,22 @@ class Guardian
|
|||
true
|
||||
end
|
||||
|
||||
def can_see_post?(post)
|
||||
return false unless post
|
||||
|
||||
return true if @user && @user.moderator?
|
||||
return false if post.deleted_at.present?
|
||||
|
||||
can_see_topic?(post.topic)
|
||||
end
|
||||
|
||||
def can_see_category?(category)
|
||||
return true unless category.secure
|
||||
return false unless @user
|
||||
|
||||
@user.secure_category_ids.include?(category.id)
|
||||
end
|
||||
|
||||
def can_vote?(post, opts={})
|
||||
post_can_act?(post,:vote, opts)
|
||||
end
|
||||
|
@ -371,4 +400,7 @@ class Guardian
|
|||
return true
|
||||
end
|
||||
|
||||
def secure_category_ids
|
||||
@user ? @user.secure_category_ids : []
|
||||
end
|
||||
end
|
||||
|
|
|
@ -207,13 +207,13 @@ class TopicQuery
|
|||
# Start with a list of all topics
|
||||
result = Topic
|
||||
|
||||
if @user_id.present?
|
||||
if @user_id
|
||||
result = result.joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{@user_id})")
|
||||
end
|
||||
|
||||
unless query_opts[:unordered]
|
||||
# If we're logged in, we have to pay attention to our pinned settings
|
||||
if @user_id.present?
|
||||
if @user
|
||||
result = result.order(TopicQuery.order_nocategory_with_pinned_sql)
|
||||
else
|
||||
result = result.order(TopicQuery.order_nocategory_basic_bumped)
|
||||
|
@ -227,6 +227,16 @@ class TopicQuery
|
|||
result = result.visible if @user.blank? or @user.regular?
|
||||
result = result.where('topics.id <> ?', query_opts[:except_topic_id]) if query_opts[:except_topic_id].present?
|
||||
result = result.offset(query_opts[:page].to_i * page_size) if query_opts[:page].present?
|
||||
|
||||
unless @user && @user.moderator?
|
||||
category_ids = @user.secure_category_ids if @user
|
||||
if category_ids.present?
|
||||
result = result.where('categories.secure IS NULL OR categories.secure = ? OR categories.id IN (?)', false, category_ids)
|
||||
else
|
||||
result = result.where('categories.secure IS NULL OR categories.secure = ?', false)
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
|
|
|
@ -144,28 +144,14 @@ describe Guardian do
|
|||
|
||||
let(:topic) { Fabricate(:topic, user: coding_horror)}
|
||||
|
||||
it 'returns false when the post is nil' do
|
||||
Guardian.new(user).can_see_post_actors?(nil, PostActionType.types[:like]).should be_false
|
||||
end
|
||||
|
||||
it 'returns true for likes' do
|
||||
Guardian.new(user).can_see_post_actors?(topic, PostActionType.types[:like]).should be_true
|
||||
end
|
||||
|
||||
it 'returns false for bookmarks' do
|
||||
Guardian.new(user).can_see_post_actors?(topic, PostActionType.types[:bookmark]).should be_false
|
||||
end
|
||||
|
||||
it 'returns false for off-topic flags' do
|
||||
Guardian.new(user).can_see_post_actors?(topic, PostActionType.types[:off_topic]).should be_false
|
||||
end
|
||||
|
||||
it 'returns false for spam flags' do
|
||||
Guardian.new(user).can_see_post_actors?(topic, PostActionType.types[:spam]).should be_false
|
||||
end
|
||||
|
||||
it 'returns true for public votes' do
|
||||
Guardian.new(user).can_see_post_actors?(topic, PostActionType.types[:vote]).should be_true
|
||||
it 'displays visibility correctly' do
|
||||
guardian = Guardian.new(user)
|
||||
guardian.can_see_post_actors?(nil, PostActionType.types[:like]).should be_false
|
||||
guardian.can_see_post_actors?(topic, PostActionType.types[:like]).should be_true
|
||||
guardian.can_see_post_actors?(topic, PostActionType.types[:bookmark]).should be_false
|
||||
guardian.can_see_post_actors?(topic, PostActionType.types[:off_topic]).should be_false
|
||||
guardian.can_see_post_actors?(topic, PostActionType.types[:spam]).should be_false
|
||||
guardian.can_see_post_actors?(topic, PostActionType.types[:vote]).should be_true
|
||||
end
|
||||
|
||||
it 'returns false for private votes' do
|
||||
|
@ -176,34 +162,15 @@ describe Guardian do
|
|||
end
|
||||
|
||||
describe 'can_impersonate?' do
|
||||
it 'returns false when the target is nil' do
|
||||
it 'allows impersonation correctly' do
|
||||
Guardian.new(admin).can_impersonate?(nil).should be_false
|
||||
end
|
||||
|
||||
it 'returns false when the user is nil' do
|
||||
Guardian.new.can_impersonate?(user).should be_false
|
||||
end
|
||||
|
||||
it "doesn't allow a non-admin to impersonate someone" do
|
||||
Guardian.new(coding_horror).can_impersonate?(user).should be_false
|
||||
end
|
||||
|
||||
it "doesn't allow an admin to impersonate themselves" do
|
||||
Guardian.new(admin).can_impersonate?(admin).should be_false
|
||||
end
|
||||
|
||||
it "doesn't allow an admin to impersonate another admin" do
|
||||
Guardian.new(admin).can_impersonate?(another_admin).should be_false
|
||||
end
|
||||
|
||||
it "allows an admin to impersonate a regular user" do
|
||||
Guardian.new(admin).can_impersonate?(user).should be_true
|
||||
end
|
||||
|
||||
it "allows an admin to impersonate a moderator" do
|
||||
Guardian.new(admin).can_impersonate?(moderator).should be_true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'can_invite_to?' do
|
||||
|
@ -211,16 +178,11 @@ describe Guardian do
|
|||
let(:user) { topic.user }
|
||||
let(:moderator) { Fabricate(:moderator) }
|
||||
|
||||
it 'returns false with a nil user' do
|
||||
it 'handles invitation correctly' do
|
||||
Guardian.new(nil).can_invite_to?(topic).should be_false
|
||||
end
|
||||
|
||||
it 'returns false with a nil object' do
|
||||
Guardian.new(moderator).can_invite_to?(nil).should be_false
|
||||
end
|
||||
|
||||
it 'returns true for a moderator to invite' do
|
||||
Guardian.new(moderator).can_invite_to?(topic).should be_true
|
||||
Guardian.new(user).can_invite_to?(topic).should be_false
|
||||
end
|
||||
|
||||
it 'returns false when the site requires approving users' do
|
||||
|
@ -228,10 +190,6 @@ describe Guardian do
|
|||
Guardian.new(moderator).can_invite_to?(topic).should be_false
|
||||
end
|
||||
|
||||
it 'returns false for a regular user to invite' do
|
||||
Guardian.new(user).can_invite_to?(topic).should be_false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'can_see?' do
|
||||
|
@ -244,6 +202,40 @@ describe Guardian do
|
|||
it 'allows non logged in users to view topics' do
|
||||
Guardian.new.can_see?(topic).should be_true
|
||||
end
|
||||
|
||||
it 'correctly handles groups' do
|
||||
group = Fabricate(:group)
|
||||
category = Fabricate(:category, secure: true)
|
||||
category.allow(group)
|
||||
|
||||
topic = Fabricate(:topic, category: category)
|
||||
|
||||
Guardian.new(user).can_see?(topic).should be_false
|
||||
group.add(user)
|
||||
group.save
|
||||
|
||||
Guardian.new(user).can_see?(topic).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'a Post' do
|
||||
it 'correctly handles post visibility' do
|
||||
Guardian.new(user).can_see?(post).should be_true
|
||||
|
||||
post.destroy
|
||||
post.reload
|
||||
Guardian.new(user).can_see?(post).should be_false
|
||||
Guardian.new(admin).can_see?(post).should be_true
|
||||
|
||||
post.recover
|
||||
post.reload
|
||||
topic.destroy
|
||||
topic.reload
|
||||
Guardian.new(user).can_see?(post).should be_false
|
||||
Guardian.new(admin).can_see?(post).should be_true
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -3,13 +3,38 @@ require 'topic_view'
|
|||
|
||||
describe TopicQuery do
|
||||
|
||||
let!(:user) { Fabricate(:coding_horror) }
|
||||
let(:user) { Fabricate(:coding_horror) }
|
||||
let(:creator) { Fabricate(:user) }
|
||||
let(:topic_query) { TopicQuery.new(user) }
|
||||
|
||||
let(:moderator) { Fabricate(:moderator) }
|
||||
let(:admin) { Fabricate(:moderator) }
|
||||
|
||||
|
||||
context 'secure category' do
|
||||
it "filters categories out correctly" do
|
||||
category = Fabricate(:category)
|
||||
category.deny(:all)
|
||||
group = Fabricate(:group)
|
||||
category.allow(group)
|
||||
category.save
|
||||
|
||||
topic = Fabricate(:topic, category: category)
|
||||
|
||||
TopicQuery.new(nil).list_latest.topics.count.should == 0
|
||||
TopicQuery.new(user).list_latest.topics.count.should == 0
|
||||
|
||||
# mods can see every group
|
||||
TopicQuery.new(moderator).list_latest.topics.count.should == 2
|
||||
|
||||
group.add(user)
|
||||
group.save
|
||||
|
||||
TopicQuery.new(user).list_latest.topics.count.should == 2
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'a bunch of topics' do
|
||||
let!(:regular_topic) { Fabricate(:topic, title: 'this is a regular topic', user: creator, bumped_at: 15.minutes.ago) }
|
||||
let!(:pinned_topic) { Fabricate(:topic, title: 'this is a pinned topic', user: creator, pinned_at: 10.minutes.ago, bumped_at: 10.minutes.ago) }
|
||||
|
@ -90,17 +115,11 @@ describe TopicQuery do
|
|||
|
||||
context 'with no data' do
|
||||
|
||||
it "has no read topics" do
|
||||
topic_query.list_unread.topics.should be_blank
|
||||
end
|
||||
|
||||
it "has no unread topics" do
|
||||
topic_query.list_unread.topics.should be_blank
|
||||
end
|
||||
|
||||
it "has an unread count of 0" do
|
||||
topic_query.unread_count.should == 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'with read data' do
|
||||
|
@ -115,9 +134,6 @@ describe TopicQuery do
|
|||
context 'list_unread' do
|
||||
it 'contains no topics' do
|
||||
topic_query.list_unread.topics.should == []
|
||||
end
|
||||
|
||||
it "returns 0 as the unread count" do
|
||||
topic_query.unread_count.should == 0
|
||||
end
|
||||
end
|
||||
|
|
3
spec/fabricators/group_fabricator.rb
Normal file
3
spec/fabricators/group_fabricator.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
Fabricator(:group) do
|
||||
name 'my group'
|
||||
end
|
|
@ -18,6 +18,34 @@ describe Category do
|
|||
it { should have_many :category_featured_topics }
|
||||
it { should have_many :featured_topics }
|
||||
|
||||
describe "security" do
|
||||
it "secures categories correctly" do
|
||||
category = Fabricate(:category)
|
||||
|
||||
category.secure?.should be_false
|
||||
|
||||
category.deny(:all)
|
||||
category.secure?.should be_true
|
||||
|
||||
category.allow(:all)
|
||||
category.secure?.should be_false
|
||||
|
||||
user = Fabricate(:user)
|
||||
user.secure_categories.to_a.should == []
|
||||
|
||||
group = Fabricate(:group)
|
||||
group.add(user)
|
||||
group.save
|
||||
|
||||
category.allow(group)
|
||||
category.save
|
||||
|
||||
user.reload
|
||||
user.secure_categories.to_a.should == [category]
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe "uncategorized name" do
|
||||
let(:category) { Fabricate.build(:category, name: SiteSetting.uncategorized_name) }
|
||||
|
||||
|
@ -71,47 +99,27 @@ describe Category do
|
|||
@topic = @category.topic
|
||||
end
|
||||
|
||||
it 'creates a slug' do
|
||||
it 'is created correctly' do
|
||||
@category.slug.should == 'amazing-category'
|
||||
end
|
||||
|
||||
it "has a hotness of 5.0 by default" do
|
||||
@category.hotness.should == 5.0
|
||||
end
|
||||
|
||||
it 'has a default description' do
|
||||
@category.description.should be_blank
|
||||
end
|
||||
|
||||
it 'has one topic' do
|
||||
Topic.where(category_id: @category).count.should == 1
|
||||
end
|
||||
|
||||
it 'creates a topic post' do
|
||||
@topic.should be_present
|
||||
end
|
||||
|
||||
it 'points back to itself' do
|
||||
@topic.category.should == @category
|
||||
end
|
||||
|
||||
it 'is a visible topic' do
|
||||
@topic.should be_visible
|
||||
end
|
||||
|
||||
it 'is pinned' do
|
||||
@topic.pinned_at.should be_present
|
||||
end
|
||||
|
||||
it 'is an undeletable topic' do
|
||||
Guardian.new(@category.user).can_delete?(@topic).should be_false
|
||||
end
|
||||
|
||||
it 'should have one post' do
|
||||
@topic.posts.count.should == 1
|
||||
end
|
||||
|
||||
it 'should have a topic url' do
|
||||
@category.topic_url.should be_present
|
||||
end
|
||||
|
||||
|
@ -129,17 +137,12 @@ describe Category do
|
|||
@category.reload
|
||||
end
|
||||
|
||||
it 'still has 0 forum topics' do
|
||||
it 'does not cause changes' do
|
||||
@category.topic_count.should == 0
|
||||
end
|
||||
|
||||
it "didn't change the category" do
|
||||
@topic.category.should == @category
|
||||
end
|
||||
|
||||
it "didn't change the category's forum topic" do
|
||||
@category.topic.should == @topic
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -151,11 +154,8 @@ describe Category do
|
|||
@category.destroy
|
||||
end
|
||||
|
||||
it 'deletes the category' do
|
||||
it 'is deleted correctly' do
|
||||
Category.exists?(id: @category_id).should be_false
|
||||
end
|
||||
|
||||
it 'deletes the forum topic' do
|
||||
Topic.exists?(id: @topic_id).should be_false
|
||||
end
|
||||
end
|
||||
|
@ -172,21 +172,13 @@ describe Category do
|
|||
@category.reload
|
||||
end
|
||||
|
||||
it 'updates topics_week' do
|
||||
it 'updates topic stats' do
|
||||
@category.topics_week.should == 1
|
||||
end
|
||||
|
||||
it 'updates topics_month' do
|
||||
@category.topics_month.should == 1
|
||||
end
|
||||
|
||||
it 'updates topics_year' do
|
||||
@category.topics_year.should == 1
|
||||
end
|
||||
|
||||
it 'updates topic_count' do
|
||||
@category.topic_count.should == 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'with deleted topics' do
|
||||
|
@ -197,21 +189,13 @@ describe Category do
|
|||
@category.reload
|
||||
end
|
||||
|
||||
it 'does not count deleted topics for topics_week' do
|
||||
it 'does not count deleted topics' do
|
||||
@category.topics_week.should == 0
|
||||
end
|
||||
|
||||
it 'does not count deleted topics for topics_month' do
|
||||
@category.topic_count.should == 0
|
||||
@category.topics_month.should == 0
|
||||
end
|
||||
|
||||
it 'does not count deleted topics for topics_year' do
|
||||
@category.topics_year.should == 0
|
||||
end
|
||||
|
||||
it 'does not count deleted topics for topic_count' do
|
||||
@category.topic_count.should == 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
4
spec/models/group_spec.rb
Normal file
4
spec/models/group_spec.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Group do
|
||||
end
|
|
@ -37,41 +37,52 @@ describe UserAction do
|
|||
log_test_action(action_type: UserAction::BOOKMARK)
|
||||
end
|
||||
|
||||
describe 'stats' do
|
||||
|
||||
let :mystats do
|
||||
UserAction.stats(user.id, Guardian.new(user))
|
||||
end
|
||||
|
||||
it 'include correct events' do
|
||||
mystats.map{|r| r["action_type"].to_i}.should include(UserAction::NEW_TOPIC)
|
||||
UserAction.stats(user.id,Guardian.new).map{|r| r["action_type"].to_i}.should_not include(UserAction::NEW_PRIVATE_MESSAGE)
|
||||
UserAction.stats(user.id,Guardian.new).map{|r| r["action_type"].to_i}.should_not include(UserAction::GOT_PRIVATE_MESSAGE)
|
||||
mystats.map{|r| r["action_type"].to_i}.should include(UserAction::NEW_PRIVATE_MESSAGE)
|
||||
mystats.map{|r| r["action_type"].to_i}.should include(UserAction::GOT_PRIVATE_MESSAGE)
|
||||
end
|
||||
|
||||
it 'should not include new topic when topic is deleted' do
|
||||
public_topic.destroy
|
||||
mystats.map{|r| r["action_type"].to_i}.should_not include(UserAction::NEW_TOPIC)
|
||||
end
|
||||
|
||||
def stats_for_user(viewer=nil)
|
||||
UserAction.stats(user.id, Guardian.new(viewer)).map{|r| r["action_type"].to_i}.sort
|
||||
end
|
||||
|
||||
describe 'stream' do
|
||||
def stream_count(viewer=nil)
|
||||
UserAction.stream(user_id: user.id, guardian: Guardian.new(viewer)).count
|
||||
end
|
||||
|
||||
it 'should have 1 item for non owners' do
|
||||
UserAction.stream(user_id: user.id, guardian: Guardian.new).count.should == 1
|
||||
end
|
||||
it 'includes the events correctly' do
|
||||
|
||||
it 'should include no items for non owner when topic deleted' do
|
||||
public_topic.destroy
|
||||
UserAction.stream(user_id: user.id, guardian: Guardian.new).count.should == 0
|
||||
end
|
||||
mystats = stats_for_user(user)
|
||||
expecting = [UserAction::NEW_TOPIC, UserAction::NEW_PRIVATE_MESSAGE, UserAction::GOT_PRIVATE_MESSAGE, UserAction::BOOKMARK].sort
|
||||
mystats.should == expecting
|
||||
stream_count(user).should == 4
|
||||
|
||||
it 'should have bookmarks and pms for owners' do
|
||||
UserAction.stream(user_id: user.id, guardian: user.guardian).count.should == 4
|
||||
end
|
||||
other_stats = stats_for_user
|
||||
expecting = [UserAction::NEW_TOPIC]
|
||||
stream_count.should == 1
|
||||
|
||||
other_stats.should == expecting
|
||||
|
||||
public_topic.destroy
|
||||
stats_for_user.should == []
|
||||
stream_count.should == 0
|
||||
|
||||
# groups
|
||||
|
||||
category = Fabricate(:category, secure: true)
|
||||
|
||||
public_topic.recover
|
||||
public_topic.category = category
|
||||
public_topic.save
|
||||
|
||||
stats_for_user.should == []
|
||||
stream_count.should == 0
|
||||
|
||||
group = Fabricate(:group)
|
||||
u = Fabricate(:coding_horror)
|
||||
group.add(u)
|
||||
group.save
|
||||
|
||||
category.allow(group)
|
||||
category.save
|
||||
|
||||
stats_for_user(u).should == [UserAction::NEW_TOPIC]
|
||||
stream_count(u).should == 1
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -146,8 +157,6 @@ describe UserAction do
|
|||
end
|
||||
it 'should exist' do
|
||||
@action.should_not be_nil
|
||||
end
|
||||
it 'shoule have the correct date' do
|
||||
@action.created_at.should be_within(1).of(@post.topic.created_at)
|
||||
end
|
||||
end
|
||||
|
@ -164,16 +173,11 @@ describe UserAction do
|
|||
@response = Fabricate(:post, reply_to_post_number: 1, topic: @post.topic, user: @other_user, raw: "perhaps @#{@mentioned.username} knows how this works?")
|
||||
end
|
||||
|
||||
it 'should log a post action for the poster' do
|
||||
it 'should log user actions correctly' do
|
||||
@response.user.user_actions.where(action_type: UserAction::POST).first.should_not be_nil
|
||||
end
|
||||
|
||||
it 'should log a post action for the original poster' do
|
||||
@post.user.user_actions.where(action_type: UserAction::RESPONSE).first.should_not be_nil
|
||||
end
|
||||
|
||||
it 'should log a mention for the mentioned' do
|
||||
@mentioned.user_actions.where(action_type: UserAction::MENTION).first.should_not be_nil
|
||||
@post.user.user_actions.joins(:target_post).where('posts.post_number = 2').count.should == 1
|
||||
end
|
||||
|
||||
it 'should not log a double notification for a post edit' do
|
||||
|
@ -182,12 +186,7 @@ describe UserAction do
|
|||
@response.user.user_actions.where(action_type: UserAction::POST).count.should == 1
|
||||
end
|
||||
|
||||
it 'should not log topic reply and reply for a single post' do
|
||||
@post.user.user_actions.joins(:target_post).where('posts.post_number = 2').count.should == 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'when user bookmarks' do
|
||||
|
@ -198,19 +197,12 @@ describe UserAction do
|
|||
@action = @user.user_actions.where(action_type: UserAction::BOOKMARK).first
|
||||
end
|
||||
|
||||
it 'should create a bookmark action' do
|
||||
it 'should create a bookmark action correctly' do
|
||||
@action.action_type.should == UserAction::BOOKMARK
|
||||
end
|
||||
it 'should point to the correct post' do
|
||||
@action.target_post_id.should == @post.id
|
||||
end
|
||||
it 'should have the right acting_user' do
|
||||
@action.acting_user_id.should == @user.id
|
||||
end
|
||||
it 'should target the correct user' do
|
||||
@action.user_id.should == @user.id
|
||||
end
|
||||
it 'should nuke the action when unbookmarked' do
|
||||
|
||||
PostAction.remove_act(@user, @post, PostActionType.types[:bookmark])
|
||||
@user.user_actions.where(action_type: UserAction::BOOKMARK).first.should be_nil
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue
Block a user