Commit 5ce9ead2 by wajeeha-khalid

rebased with release to hotpatch

parent 339fd755
...@@ -5,6 +5,10 @@ require_relative 'content' ...@@ -5,6 +5,10 @@ require_relative 'content'
class CommentThread < Content class CommentThread < Content
include Mongoid::Timestamps include Mongoid::Timestamps
include Mongoid::Attributes::Dynamic
include ActiveModel::MassAssignmentSecurity
include Tire::Model::Search
include Tire::Model::Callbacks
extend Enumerize extend Enumerize
voteable self, :up => +1, :down => -1 voteable self, :up => +1, :down => -1
...@@ -28,8 +32,6 @@ class CommentThread < Content ...@@ -28,8 +32,6 @@ class CommentThread < Content
index({author_id: 1, course_id: 1}) index({author_id: 1, course_id: 1})
include Tire::Model::Search
include Tire::Model::Callbacks
index_name Content::ES_INDEX_NAME index_name Content::ES_INDEX_NAME
...@@ -49,11 +51,11 @@ class CommentThread < Content ...@@ -49,11 +51,11 @@ class CommentThread < Content
indexes :author_id, type: :string, as: 'author_id', index: :not_analyzed, included_in_all: false indexes :author_id, type: :string, as: 'author_id', index: :not_analyzed, included_in_all: false
indexes :group_id, type: :integer, as: 'group_id', index: :not_analyzed, included_in_all: false indexes :group_id, type: :integer, as: 'group_id', index: :not_analyzed, included_in_all: false
indexes :id, :index => :not_analyzed indexes :id, :index => :not_analyzed
indexes :thread_id, :analyzer => :keyword, :as => "_id" indexes :thread_id, :analyzer => :keyword, :as => '_id'
end end
belongs_to :author, class_name: "User", inverse_of: :comment_threads, index: true#, autosave: true belongs_to :author, class_name: 'User', inverse_of: :comment_threads, index: true
has_many :comments, dependent: :destroy#, autosave: true# Use destroy to envoke callback on the top-level comments TODO async has_many :comments, dependent: :destroy # Use destroy to invoke callback on the top-level comments
has_many :activities, autosave: true has_many :activities, autosave: true
attr_accessible :title, :body, :course_id, :commentable_id, :anonymous, :anonymous_to_peers, :closed, :thread_type attr_accessible :title, :body, :course_id, :commentable_id, :anonymous, :anonymous_to_peers, :closed, :thread_type
...@@ -69,24 +71,12 @@ class CommentThread < Content ...@@ -69,24 +71,12 @@ class CommentThread < Content
before_create :set_last_activity_at before_create :set_last_activity_at
before_update :set_last_activity_at, :unless => lambda { closed_changed? } before_update :set_last_activity_at, :unless => lambda { closed_changed? }
after_update :clear_endorsements after_update :clear_endorsements
before_destroy :destroy_subscriptions before_destroy :destroy_subscriptions
scope :active_since, ->(from_time) { where(:last_activity_at => {:$gte => from_time}) } scope :active_since, ->(from_time) { where(:last_activity_at => {:$gte => from_time}) }
scope :standalone_context, ->() { where(:context => :standalone) } scope :standalone_context, ->() { where(:context => :standalone) }
scope :course_context, ->() { where(:context => :course) } scope :course_context, ->() { where(:context => :course) }
def self.new_dumb_thread(options={})
c = self.new
c.title = options[:title] || "title"
c.body = options[:body] || "body"
c.commentable_id = options[:commentable_id] || "commentable_id"
c.course_id = options[:course_id] || "course_id"
c.author = options[:author] || User.first
c.save!
c
end
def activity_since(from_time=nil) def activity_since(from_time=nil)
if from_time if from_time
activities.where(:created_at => {:$gte => from_time}) activities.where(:created_at => {:$gte => from_time})
...@@ -95,13 +85,21 @@ class CommentThread < Content ...@@ -95,13 +85,21 @@ class CommentThread < Content
end end
end end
def activity_today; activity_since(Date.today.to_time); end def activity_today
activity_since(Date.today.to_time)
end
def activity_this_week; activity_since(Date.today.to_time - 1.weeks); end def activity_this_week
activity_since(Date.today.to_time - 1.weeks)
end
def activity_this_month; activity_since(Date.today.to_time - 1.months); end def activity_this_month
activity_since(Date.today.to_time - 1.months)
end
def activity_overall; activity_since(nil); end def activity_overall
activity_since(nil)
end
def root_comments def root_comments
Comment.roots.where(comment_thread_id: self.id) Comment.roots.where(comment_thread_id: self.id)
...@@ -124,16 +122,17 @@ class CommentThread < Content ...@@ -124,16 +122,17 @@ class CommentThread < Content
end end
def to_hash(params={}) def to_hash(params={})
as_document.slice(*%w[thread_type title body course_id anonymous anonymous_to_peers commentable_id created_at updated_at at_position_list closed context]) as_document.slice(*%w[thread_type title body course_id anonymous anonymous_to_peers commentable_id created_at updated_at at_position_list closed context last_activity_at])
.merge("id" => _id, "user_id" => author_id, .merge('id' => _id,
"username" => author_username, 'user_id' => author_id,
"votes" => votes.slice(*%w[count up_count down_count point]), 'username' => author_username,
"abuse_flaggers" => abuse_flaggers, 'votes' => votes.slice(*%w[count up_count down_count point]),
"tags" => [], 'abuse_flaggers' => abuse_flaggers,
"type" => "thread", 'tags' => [],
"group_id" => group_id, 'type' => 'thread',
"pinned" => pinned?, 'group_id' => group_id,
"comments_count" => comment_count) 'pinned' => pinned?,
'comments_count' => comment_count)
end end
...@@ -142,7 +141,7 @@ class CommentThread < Content ...@@ -142,7 +141,7 @@ class CommentThread < Content
self.id self.id
end end
private private
def set_last_activity_at def set_last_activity_at
self.last_activity_at = Time.now.utc unless last_activity_at_changed? self.last_activity_at = Time.now.utc unless last_activity_at_changed?
...@@ -154,8 +153,8 @@ private ...@@ -154,8 +153,8 @@ private
# the last activity time on the thread. Therefore the callbacks would be mutually recursive and we end up with a # the last activity time on the thread. Therefore the callbacks would be mutually recursive and we end up with a
# 'SystemStackError'. The 'set' method skips callbacks and therefore bypasses this issue. # 'SystemStackError'. The 'set' method skips callbacks and therefore bypasses this issue.
self.comments.each do |comment| self.comments.each do |comment|
comment.set :endorsed, false comment.set(endorsed: false)
comment.set :endorsement, nil comment.set(endorsement: nil)
end end
end end
end end
...@@ -163,5 +162,4 @@ private ...@@ -163,5 +162,4 @@ private
def destroy_subscriptions def destroy_subscriptions
subscriptions.delete_all subscriptions.delete_all
end end
end end
ENV["SINATRA_ENV"] = "test" ENV["SINATRA_ENV"] = "test"
require 'simplecov' require 'simplecov'
SimpleCov.start SimpleCov.start
if ENV['CI']=='true'
require 'codecov'
SimpleCov.formatter = SimpleCov::Formatter::Codecov
end
require File.join(File.dirname(__FILE__), '..', 'app') require File.join(File.dirname(__FILE__), '..', 'app')
require 'sinatra'
require 'rack/test' require 'rack/test'
require 'sinatra'
require 'yajl' require 'yajl'
require 'database_cleaner'
require 'support/database_cleaner'
require 'support/elasticsearch'
require 'support/factory_girl'
# setup test environment # setup test environment
set :environment, :test set :environment, :test
...@@ -15,6 +23,9 @@ set :run, false ...@@ -15,6 +23,9 @@ set :run, false
set :raise_errors, true set :raise_errors, true
set :logging, false set :logging, false
Mongoid.logger.level = Logger::WARN
Mongo::Logger.logger.level = ENV["ENABLE_MONGO_DEBUGGING"] ? Logger::DEBUG : Logger::WARN
Delayed::Worker.delay_jobs = false Delayed::Worker.delay_jobs = false
def app def app
...@@ -28,36 +39,12 @@ def set_api_key_header ...@@ -28,36 +39,12 @@ def set_api_key_header
current_session.header "X-Edx-Api-Key", TEST_API_KEY current_session.header "X-Edx-Api-Key", TEST_API_KEY
end end
def delete_es_index
Tire.index Content::ES_INDEX_NAME do delete end
end
def create_es_index
new_index = Tire.index Content::ES_INDEX_NAME
new_index.create
[CommentThread, Comment].each do |klass|
klass.put_search_index_mapping
end
end
def refresh_es_index
# we are using the same index for two types, which is against the
# grain of Tire's design. This is why this method works for both
# comment_threads and comments.
CommentThread.tire.index.refresh
end
RSpec.configure do |config| RSpec.configure do |config|
config.include Rack::Test::Methods config.include Rack::Test::Methods
config.treat_symbols_as_metadata_keys_with_true_values = true config.treat_symbols_as_metadata_keys_with_true_values = true
config.filter_run focus: true config.filter_run focus: true
config.run_all_when_everything_filtered = true config.run_all_when_everything_filtered = true
config.before(:each) do
Mongoid::IdentityMap.clear
DatabaseCleaner.clean
delete_es_index
create_es_index
end
end end
Mongoid.configure do |config| Mongoid.configure do |config|
...@@ -72,16 +59,24 @@ def create_test_user(id) ...@@ -72,16 +59,24 @@ def create_test_user(id)
User.create!(external_id: id.to_s, username: "user#{id}") User.create!(external_id: id.to_s, username: "user#{id}")
end end
def init_without_subscriptions # Add the given body of text to the list of blocked texts/hashes.
def block_post_body(body='blocked post')
body = body.strip.downcase.gsub(/[^a-z ]/, '').gsub(/\s+/, ' ')
blocked_hash = Digest::MD5.hexdigest(body)
Content.mongo_client[:blocked_hash].insert_one(hash: blocked_hash)
[Comment, CommentThread, User, Notification, Subscription, Activity, Delayed::Backend::Mongoid::Job].each(&:delete_all).each(&:remove_indexes).each(&:create_indexes) # reload the global holding the blocked hashes
Content.mongo_session[:blocked_hash].drop CommentService.blocked_hashes = Content.mongo_client[:blocked_hash].find(nil, projection: {hash: 1}).map do |d|
delete_es_index d['hash']
create_es_index end
blocked_hash
end
def init_without_subscriptions
commentable = Commentable.new("question_1") commentable = Commentable.new("question_1")
users = (1..10).map{|id| create_test_user(id)} users = (1..10).map { |id| create_test_user(id) }
user = users.first user = users.first
thread = CommentThread.new(title: "I can't solve this problem", body: "can anyone help me?", course_id: "1", commentable_id: commentable.id) thread = CommentThread.new(title: "I can't solve this problem", body: "can anyone help me?", course_id: "1", commentable_id: commentable.id)
...@@ -150,63 +145,15 @@ def init_without_subscriptions ...@@ -150,63 +145,15 @@ def init_without_subscriptions
Comment.all.each do |c| Comment.all.each do |c|
user.vote(c, :up) # make the first user always vote up for convenience user.vote(c, :up) # make the first user always vote up for convenience
users[2,9].each {|user| user.vote(c, [:up, :down].sample)} users[2, 9].each { |user| user.vote(c, [:up, :down].sample) }
end end
CommentThread.all.each do |c| CommentThread.all.each do |c|
user.vote(c, :up) # make the first user always vote up for convenience user.vote(c, :up) # make the first user always vote up for convenience
users[2,9].each {|user| user.vote(c, [:up, :down].sample)} users[2, 9].each { |user| user.vote(c, [:up, :down].sample) }
end end
Content.mongo_session[:blocked_hash].insert(hash: Digest::MD5.hexdigest("blocked post")) block_post_body
# reload the global holding the blocked hashes
CommentService.blocked_hashes = Content.mongo_session[:blocked_hash].find.select(hash: 1).each.map {|d| d["hash"]}
end
def init_with_subscriptions
[Comment, CommentThread, User, Notification, Subscription, Activity, Delayed::Backend::Mongoid::Job].each(&:delete_all).each(&:remove_indexes).each(&:create_indexes)
delete_es_index
create_es_index
user1 = create_test_user(1)
user2 = create_test_user(2)
user2.subscribe(user1)
commentable = Commentable.new("question_1")
user1.subscribe(commentable)
user2.subscribe(commentable)
thread = CommentThread.new(title: "I can't solve this problem", body: "can anyone help me?", course_id: "1", commentable_id: commentable.id)
thread.author = user1
user1.subscribe(thread)
user2.subscribe(thread)
thread.save!
thread = thread.reload
comment = thread.comments.new(body: "this problem is so easy", course_id: "1")
comment.author = user2
comment.save!
comment1 = comment.children.new(body: "not for me!", course_id: "1")
comment1.author = user1
comment1.comment_thread = thread
comment1.save!
comment2 = comment1.children.new(body: "not for me neither!", course_id: "1")
comment2.author = user2
comment2.comment_thread = thread
comment2.save!
thread = CommentThread.new(title: "This problem is wrong", body: "it is unsolvable", course_id: "2", commentable_id: commentable.id)
thread.author = user2
user2.subscribe(thread)
thread.save!
thread = CommentThread.new(title: "I don't know what to say", body: "lol", course_id: "2", commentable_id: "something else")
thread.author = user1
thread.save!
end end
# this method is used to test results produced using the helper function handle_threads_query # this method is used to test results produced using the helper function handle_threads_query
...@@ -215,7 +162,7 @@ def check_thread_result(user, thread, hash, is_json=false) ...@@ -215,7 +162,7 @@ def check_thread_result(user, thread, hash, is_json=false)
expected_keys = %w(id thread_type title body course_id commentable_id created_at updated_at context) expected_keys = %w(id thread_type title body course_id commentable_id created_at updated_at context)
expected_keys += %w(anonymous anonymous_to_peers at_position_list closed user_id) expected_keys += %w(anonymous anonymous_to_peers at_position_list closed user_id)
expected_keys += %w(username votes abuse_flaggers tags type group_id pinned) expected_keys += %w(username votes abuse_flaggers tags type group_id pinned)
expected_keys += %w(comments_count unread_comments_count read endorsed) expected_keys += %w(comments_count unread_comments_count read endorsed last_activity_at)
# these keys are checked separately, when desired, using check_thread_response_paging. # these keys are checked separately, when desired, using check_thread_response_paging.
actual_keys = hash.keys - [ actual_keys = hash.keys - [
"children", "endorsed_responses", "non_endorsed_responses", "resp_skip", "children", "endorsed_responses", "non_endorsed_responses", "resp_skip",
...@@ -250,9 +197,11 @@ def check_thread_result(user, thread, hash, is_json=false) ...@@ -250,9 +197,11 @@ def check_thread_result(user, thread, hash, is_json=false)
hash["id"].should == thread._id.to_s hash["id"].should == thread._id.to_s
hash["created_at"].should == thread.created_at.utc.strftime("%Y-%m-%dT%H:%M:%SZ") hash["created_at"].should == thread.created_at.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
hash["updated_at"].should == thread.updated_at.utc.strftime("%Y-%m-%dT%H:%M:%SZ") hash["updated_at"].should == thread.updated_at.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
hash["last_activity_at"].should == thread.last_activity_at.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
else else
hash["created_at"].should == thread.created_at hash["created_at"].should == thread.created_at
hash["updated_at"].should == thread.updated_at hash["updated_at"].should == thread.updated_at
hash["last_activity_at"].should == thread.last_activity_at
end end
if user.nil? if user.nil?
...@@ -265,7 +214,7 @@ def check_thread_result(user, thread, hash, is_json=false) ...@@ -265,7 +214,7 @@ def check_thread_result(user, thread, hash, is_json=false)
read_date = read_states.first.last_read_times[thread.id.to_s] read_date = read_states.first.last_read_times[thread.id.to_s]
if read_date if read_date
thread.comments.each do |c| thread.comments.each do |c|
if c.author != user and c.updated_at < read_date if c.updated_at < read_date
expected_unread_cnt -= 1 expected_unread_cnt -= 1
end end
end end
...@@ -282,6 +231,12 @@ def check_thread_result_json(user, thread, json_response) ...@@ -282,6 +231,12 @@ def check_thread_result_json(user, thread, json_response)
check_thread_result(user, thread, json_response, true) check_thread_result(user, thread, json_response, true)
end end
def check_unread_thread_result_json(thread, json_response)
# when thread is unread we do not check if thread matches the user read states data
# and explicitly asserts `read` to false; hence pass user=nil
check_thread_result(nil, thread, json_response, true)
end
def check_thread_response_paging(thread, hash, resp_skip=0, resp_limit=nil, is_json=false, recursive=false) def check_thread_response_paging(thread, hash, resp_skip=0, resp_limit=nil, is_json=false, recursive=false)
case thread.thread_type case thread.thread_type
when "discussion" when "discussion"
...@@ -298,8 +253,9 @@ def check_comment(comment, hash, is_json, recursive=false) ...@@ -298,8 +253,9 @@ def check_comment(comment, hash, is_json, recursive=false)
hash["username"].should == comment.author_username hash["username"].should == comment.author_username
hash["endorsed"].should == comment.endorsed hash["endorsed"].should == comment.endorsed
hash["endorsement"].should == comment.endorsement hash["endorsement"].should == comment.endorsement
if recursive
children = Comment.where({"parent_id" => comment.id}).sort({"sk" => 1}).to_a children = Comment.where({"parent_id" => comment.id}).sort({"sk" => 1}).to_a
hash["child_count"].should == children.length
if recursive
hash["children"].length.should == children.length hash["children"].length.should == children.length
hash["children"].each_with_index do |child_hash, i| hash["children"].each_with_index do |child_hash, i|
check_comment(children[i], child_hash, is_json) check_comment(children[i], child_hash, is_json)
...@@ -377,6 +333,7 @@ def make_comment(author, parent, text) ...@@ -377,6 +333,7 @@ def make_comment(author, parent, text)
else else
coll = parent.children coll = parent.children
thread = parent.comment_thread thread = parent.comment_thread
parent.set(child_count: coll.length + 1)
end end
comment = coll.new(body: text, course_id: parent.course_id) comment = coll.new(body: text, course_id: parent.course_id)
comment.author = author comment.author = author
...@@ -425,5 +382,19 @@ def setup_10_threads ...@@ -425,5 +382,19 @@ def setup_10_threads
@comments["t#{i} c#{j}"] = comment @comments["t#{i} c#{j}"] = comment
end end
end end
@default_order = 10.times.map {|i| "t#{i}"}.reverse @default_order = 10.times.map { |i| "t#{i}" }.reverse
end
# Creates a CommentThread with a Comment, and nested child Comment.
# The author of the thread is subscribed to the thread.
def create_comment_thread_and_comments
# Create a new comment thread, and subscribe the author to the thread
thread = create(:comment_thread, :subscribe_author)
# Create a comment along with a nested child comment
comment = create(:comment, comment_thread: thread)
create(:comment, parent: comment)
comment.set(child_count: 1)
thread
end end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment