From 78214001417f79477337c427f95ea499db5c3c86 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Tue, 16 May 2017 14:49:42 -0400 Subject: [PATCH] FEATURE: staff can set a timer to remind them about a topic --- .../controllers/edit-topic-timer.js.es6 | 7 ++- app/jobs/regular/topic_reminder.rb | 26 +++++++++ app/models/notification.rb | 3 +- app/models/topic.rb | 19 +++---- app/models/topic_timer.rb | 31 ++++++++++- app/serializers/topic_view_serializer.rb | 2 +- app/services/topic_status_updater.rb | 2 +- config/locales/client.en.yml | 4 ++ ...5203721_add_public_type_to_topic_timers.rb | 27 ++++++++++ lib/post_creator.rb | 2 +- spec/components/topic_creator_spec.rb | 2 +- .../integration/managing_topic_status_spec.rb | 2 +- spec/integration/topic_auto_close_spec.rb | 10 ++-- spec/jobs/delete_topic_spec.rb | 10 ++-- spec/jobs/publish_topic_to_category_spec.rb | 8 +-- spec/jobs/toggle_topic_closed_spec.rb | 8 +-- spec/jobs/topic_reminder_spec.rb | 54 +++++++++++++++++++ spec/models/category_spec.rb | 2 +- spec/models/topic_spec.rb | 26 ++++++++- spec/models/topic_timer_spec.rb | 52 +++++++++++++++++- 20 files changed, 255 insertions(+), 42 deletions(-) create mode 100644 app/jobs/regular/topic_reminder.rb create mode 100644 db/migrate/20170515203721_add_public_type_to_topic_timers.rb create mode 100644 spec/jobs/topic_reminder_spec.rb diff --git a/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 b/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 index a5a3ab2b9b8..34f1b9cf66d 100644 --- a/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 @@ -7,6 +7,7 @@ export const CLOSE_STATUS_TYPE = 'close'; const OPEN_STATUS_TYPE = 'open'; const PUBLISH_TO_CATEGORY_STATUS_TYPE = 'publish_to_category'; const DELETE_STATUS_TYPE = 'delete'; +const REMINDER_TYPE = 'reminder'; export default Ember.Controller.extend(ModalFunctionality, { loading: false, @@ -17,8 +18,9 @@ export default Ember.Controller.extend(ModalFunctionality, { autoClose: Ember.computed.equal('selection', CLOSE_STATUS_TYPE), autoDelete: Ember.computed.equal('selection', DELETE_STATUS_TYPE), publishToCategory: Ember.computed.equal('selection', PUBLISH_TO_CATEGORY_STATUS_TYPE), + reminder: Ember.computed.equal('selection', REMINDER_TYPE), - showTimeOnly: Ember.computed.or('autoOpen', 'autoDelete'), + showTimeOnly: Ember.computed.or('autoOpen', 'autoDelete', 'reminder'), @computed("model.closed") timerTypes(closed) { @@ -26,7 +28,8 @@ export default Ember.Controller.extend(ModalFunctionality, { { id: CLOSE_STATUS_TYPE, name: I18n.t(closed ? 'topic.temp_open.title' : 'topic.auto_close.title'), }, { id: OPEN_STATUS_TYPE, name: I18n.t(closed ? 'topic.auto_reopen.title' : 'topic.temp_close.title') }, { id: PUBLISH_TO_CATEGORY_STATUS_TYPE, name: I18n.t('topic.publish_to_category.title') }, - { id: DELETE_STATUS_TYPE, name: I18n.t('topic.auto_delete.title') } + { id: DELETE_STATUS_TYPE, name: I18n.t('topic.auto_delete.title') }, + { id: REMINDER_TYPE, name: I18n.t('topic.reminder.title') } ]; }, diff --git a/app/jobs/regular/topic_reminder.rb b/app/jobs/regular/topic_reminder.rb new file mode 100644 index 00000000000..d2d1460a48e --- /dev/null +++ b/app/jobs/regular/topic_reminder.rb @@ -0,0 +1,26 @@ +module Jobs + class TopicReminder < Jobs::Base + + def execute(args) + topic_timer = TopicTimer.find_by(id: args[:topic_timer_id]) + + topic = topic_timer&.topic + user = topic_timer&.user + + if topic_timer.blank? || topic.blank? || user.blank? || + topic_timer.execute_at > Time.zone.now + return + end + + user.notifications.create!( + notification_type: Notification.types[:topic_reminder], + topic_id: topic.id, + post_number: 1, + data: { topic_title: topic.title, display_username: user.username }.to_json + ) + + topic_timer.trash!(Discourse.system_user) + end + + end +end diff --git a/app/models/notification.rb b/app/models/notification.rb index a09451e4ee0..b64a7d06912 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -52,7 +52,8 @@ class Notification < ActiveRecord::Base custom: 14, group_mentioned: 15, group_message_summary: 16, - watching_first_post: 17 + watching_first_post: 17, + topic_reminder: 18 ) end diff --git a/app/models/topic.rb b/app/models/topic.rb index 9dafc340b2c..e0d6ff67e07 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -215,7 +215,7 @@ class Topic < ActiveRecord::Base if !@ignore_category_auto_close && self.category && self.category.auto_close_hours && - !topic_timer&.execute_at + !public_topic_timer&.execute_at self.set_or_create_timer( TopicTimer.types[:close], @@ -953,12 +953,8 @@ SQL Topic.where("pinned_until < now()").update_all(pinned_at: nil, pinned_globally: false, pinned_until: nil) end - def topic_timer - @topic_timer ||= topic_timers.first - end - - def topic_status_update - topic_timer # will be used to filter timers unrelated to topic status + def public_topic_timer + topic_timers.where(deleted_at: nil, public_type: true).first end # Valid arguments for the time: @@ -974,10 +970,11 @@ SQL # * based_on_last_post: True if time should be based on timestamp of the last post. # * category_id: Category that the update will apply to. def set_or_create_timer(status_type, time, by_user: nil, timezone_offset: 0, based_on_last_post: false, category_id: SiteSetting.uncategorized_category_id) - topic_timer = TopicTimer.find_or_initialize_by( - status_type: status_type, - topic: self - ) + topic_timer = if TopicTimer.public_types[status_type] + TopicTimer.find_or_initialize_by( status_type: status_type, topic: self ) + else + TopicTimer.find_or_initialize_by( status_type: status_type, topic: self, user: by_user ) + end if time.blank? topic_timer.trash!(trashed_by: by_user || Discourse.system_user) diff --git a/app/models/topic_timer.rb b/app/models/topic_timer.rb index 5ab04bcc52b..ea0c474ce4e 100644 --- a/app/models/topic_timer.rb +++ b/app/models/topic_timer.rb @@ -9,13 +9,15 @@ class TopicTimer < ActiveRecord::Base validates :topic_id, presence: true validates :execute_at, presence: true validates :status_type, presence: true - validates :status_type, uniqueness: { scope: [:topic_id, :deleted_at] } + validates :status_type, uniqueness: { scope: [:topic_id, :deleted_at] }, if: :public_type? + validates :status_type, uniqueness: { scope: [:topic_id, :deleted_at, :user_id] }, if: :private_type? validates :category_id, presence: true, if: :publishing_to_category? validate :ensure_update_will_happen before_save do self.created_at ||= Time.zone.now if execute_at + self.public_type = self.public_type? if (execute_at_changed? && !execute_at_was.nil?) || user_id_changed? self.send("cancel_auto_#{self.class.types[status_type]}_job") @@ -36,10 +38,19 @@ class TopicTimer < ActiveRecord::Base close: 1, open: 2, publish_to_category: 3, - delete: 4 + delete: 4, + reminder: 5 ) end + def self.public_types + @_public_types ||= types.except(:reminder) + end + + def self.private_types + @_private_types ||= types.only(:reminder) + end + def self.ensure_consistency! TopicTimer.where("topic_timers.execute_at < ?", Time.zone.now) .find_each do |topic_timer| @@ -59,6 +70,14 @@ class TopicTimer < ActiveRecord::Base end end + def public_type? + !!self.class.public_types[self.status_type] + end + + def private_type? + !!self.class.private_types[self.status_type] + end + private def ensure_update_will_happen @@ -82,6 +101,10 @@ class TopicTimer < ActiveRecord::Base Jobs.cancel_scheduled_job(:delete_topic, topic_timer_id: id) end + def cancel_auto_reminder_job + Jobs.cancel_scheduled_job(:topic_reminder, topic_timer_id: id) + end + def schedule_auto_open_job(time) return unless topic topic.update_status('closed', true, user) if !topic.closed @@ -113,6 +136,10 @@ class TopicTimer < ActiveRecord::Base def schedule_auto_delete_job(time) Jobs.enqueue_at(time, :delete_topic, topic_timer_id: id) end + + def schedule_auto_reminder_job(time) + Jobs.enqueue_at(time, :topic_reminder, topic_timer_id: id) + end end # == Schema Information diff --git a/app/serializers/topic_view_serializer.rb b/app/serializers/topic_view_serializer.rb index 6dc9cc74164..2ba330cbcdf 100644 --- a/app/serializers/topic_view_serializer.rb +++ b/app/serializers/topic_view_serializer.rb @@ -251,7 +251,7 @@ class TopicViewSerializer < ApplicationSerializer def topic_timer TopicTimerSerializer.new( - object.topic.topic_timer, root: false + object.topic.public_topic_timer, root: false ) end diff --git a/app/services/topic_status_updater.rb b/app/services/topic_status_updater.rb index 2f0c0dd89f6..a2fc8f4dd63 100644 --- a/app/services/topic_status_updater.rb +++ b/app/services/topic_status_updater.rb @@ -2,7 +2,7 @@ TopicStatusUpdater = Struct.new(:topic, :user) do def update!(status, enabled, opts={}) status = Status.new(status, enabled) - @topic_status_update = topic.topic_status_update + @topic_status_update = topic.public_topic_timer updated = nil Topic.transaction do diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 6228db50610..15abfec97da 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1240,6 +1240,7 @@ en: moved_post: "

{{username}} moved {{description}}

" linked: "

{{username}} {{description}}

" granted_badge: "

Earned '{{description}}'

" + topic_reminder: "

{{username}} {{description}}

" watching_first_post: "

New Topic {{description}}

" @@ -1527,6 +1528,8 @@ en: based_on_last_post: "Don't close until the last post in the topic is at least this old." auto_delete: title: "Auto-Delete Topic" + reminder: + title: "Remind Me" status_update_notice: auto_open: "This topic will automatically open %{timeLeft}." @@ -1534,6 +1537,7 @@ en: auto_publish_to_category: "This topic will be published to #%{categoryName} %{timeLeft}." auto_close_based_on_last_post: "This topic will close %{duration} after the last reply." auto_delete: "This topic will be automatically deleted %{timeLeft}." + auto_reminder: "You will be reminded about this topic %{timeLeft}." auto_close_title: 'Auto-Close Settings' auto_close_immediate: one: "The last post in the topic is already 1 hour old, so the topic will be closed immediately." diff --git a/db/migrate/20170515203721_add_public_type_to_topic_timers.rb b/db/migrate/20170515203721_add_public_type_to_topic_timers.rb new file mode 100644 index 00000000000..96665ce8ebf --- /dev/null +++ b/db/migrate/20170515203721_add_public_type_to_topic_timers.rb @@ -0,0 +1,27 @@ +class AddPublicTypeToTopicTimers < ActiveRecord::Migration + def up + add_column :topic_timers, :public_type, :boolean, default: true + + execute("drop index idx_topic_id_status_type_deleted_at") rescue nil + + # Only one public timer per topic (close, open, delete): + execute <<~SQL + CREATE UNIQUE INDEX idx_topic_id_public_type_deleted_at + ON topic_timers (topic_id) + WHERE public_type = TRUE + AND deleted_at IS NULL + SQL + end + + def down + execute "DROP INDEX idx_topic_id_public_type_deleted_at" + + execute <<~SQL + CREATE UNIQUE INDEX idx_topic_id_status_type_deleted_at + ON topic_timers (topic_id, status_type) + WHERE deleted_at IS NULL + SQL + + remove_column :topic_timers, :public_type + end +end diff --git a/lib/post_creator.rb b/lib/post_creator.rb index 40a821ac6a1..00f6719dafe 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -390,7 +390,7 @@ class PostCreator end def update_topic_auto_close - topic_timer = @topic.topic_timer + topic_timer = @topic.public_topic_timer if topic_timer && topic_timer.based_on_last_post && diff --git a/spec/components/topic_creator_spec.rb b/spec/components/topic_creator_spec.rb index d47cc8dc112..1e97a81b791 100644 --- a/spec/components/topic_creator_spec.rb +++ b/spec/components/topic_creator_spec.rb @@ -39,7 +39,7 @@ describe TopicCreator do it "ignores auto_close_time without raising an error" do topic = TopicCreator.create(user, Guardian.new(user), valid_attrs.merge(auto_close_time: '24')) expect(topic).to be_valid - expect(topic.topic_status_update).to eq(nil) + expect(topic.public_topic_timer).to eq(nil) end it "category name is case insensitive" do diff --git a/spec/integration/managing_topic_status_spec.rb b/spec/integration/managing_topic_status_spec.rb index 5977150cd3e..0673c279597 100644 --- a/spec/integration/managing_topic_status_spec.rb +++ b/spec/integration/managing_topic_status_spec.rb @@ -67,7 +67,7 @@ RSpec.describe "Managing a topic's status update", type: :request do status_type: TopicTimer.types[1] expect(response).to be_success - expect(topic.reload.topic_status_update).to eq(nil) + expect(topic.reload.public_topic_timer).to eq(nil) json = JSON.parse(response.body) diff --git a/spec/integration/topic_auto_close_spec.rb b/spec/integration/topic_auto_close_spec.rb index c2d8737bb14..75d2e07c2cd 100644 --- a/spec/integration/topic_auto_close_spec.rb +++ b/spec/integration/topic_auto_close_spec.rb @@ -16,7 +16,7 @@ describe Topic do let(:category) { nil } it 'should not schedule the topic to auto-close' do - expect(topic.topic_status_update).to eq(nil) + expect(topic.public_topic_timer).to eq(nil) expect(job_klass.jobs).to eq([]) end end @@ -25,7 +25,7 @@ describe Topic do let(:category) { Fabricate(:category, auto_close_hours: nil) } it 'should not schedule the topic to auto-close' do - expect(topic.topic_status_update).to eq(nil) + expect(topic.public_topic_timer).to eq(nil) expect(job_klass.jobs).to eq([]) end end @@ -49,11 +49,11 @@ describe Topic do topic_status_update = TopicTimer.last expect(topic_status_update.topic).to eq(topic) - expect(topic.topic_status_update.execute_at).to be_within_one_second_of(2.hours.from_now) + expect(topic.public_topic_timer.execute_at).to be_within_one_second_of(2.hours.from_now) args = job_klass.jobs.last['args'].first - expect(args["topic_timer_id"]).to eq(topic.topic_status_update.id) + expect(args["topic_timer_id"]).to eq(topic.public_topic_timer.id) expect(args["state"]).to eq(true) end @@ -79,7 +79,7 @@ describe Topic do context 'topic is closed manually' do it 'should remove the schedule to auto-close the topic' do Timecop.freeze do - topic_timer_id = staff_topic.topic_timer.id + topic_timer_id = staff_topic.public_topic_timer.id staff_topic.update_status('closed', true, admin) diff --git a/spec/jobs/delete_topic_spec.rb b/spec/jobs/delete_topic_spec.rb index 404f7f52095..11a49d7a29c 100644 --- a/spec/jobs/delete_topic_spec.rb +++ b/spec/jobs/delete_topic_spec.rb @@ -19,10 +19,10 @@ describe Jobs::DeleteTopic do first_post Timecop.freeze(2.hours.from_now) do - described_class.new.execute(topic_timer_id: topic.topic_timer.id) + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id) expect(topic.reload).to be_trashed expect(first_post.reload).to be_trashed - expect(topic.reload.topic_timer).to eq(nil) + expect(topic.reload.public_topic_timer).to eq(nil) end end @@ -31,7 +31,7 @@ describe Jobs::DeleteTopic do topic.trash! Timecop.freeze(2.hours.from_now) do Topic.any_instance.expects(:trash!).never - described_class.new.execute(topic_timer_id: topic.topic_timer.id) + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id) end end @@ -41,7 +41,7 @@ describe Jobs::DeleteTopic do ) create_post(topic: t) Timecop.freeze(4.hours.from_now) do - described_class.new.execute(topic_timer_id: t.topic_timer.id) + described_class.new.execute(topic_timer_id: t.public_topic_timer.id) expect(t.reload).to_not be_trashed end end @@ -56,7 +56,7 @@ describe Jobs::DeleteTopic do it "shouldn't delete the topic" do create_post(topic: topic) Timecop.freeze(2.hours.from_now) do - described_class.new.execute(topic_timer_id: topic.topic_timer.id) + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id) expect(topic.reload).to_not be_trashed end end diff --git a/spec/jobs/publish_topic_to_category_spec.rb b/spec/jobs/publish_topic_to_category_spec.rb index fca2b8251e7..2cb97a1ccad 100644 --- a/spec/jobs/publish_topic_to_category_spec.rb +++ b/spec/jobs/publish_topic_to_category_spec.rb @@ -29,7 +29,7 @@ RSpec.describe Jobs::PublishTopicToCategory do Timecop.travel(1.hour.ago) { topic } topic.trash! - described_class.new.execute(topic_timer_id: topic.topic_timer.id) + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id) topic.reload expect(topic.category).to eq(category) @@ -41,13 +41,13 @@ RSpec.describe Jobs::PublishTopicToCategory do Timecop.travel(1.hour.ago) { topic.update!(visible: false) } message = MessageBus.track_publish do - described_class.new.execute(topic_timer_id: topic.topic_timer.id) + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id) end.first topic.reload expect(topic.category).to eq(another_category) expect(topic.visible).to eq(true) - expect(topic.topic_timer).to eq(nil) + expect(topic.public_topic_timer).to eq(nil) %w{created_at bumped_at updated_at last_posted_at}.each do |attribute| expect(topic.public_send(attribute)).to be_within(1.second).of(Time.zone.now) @@ -68,7 +68,7 @@ RSpec.describe Jobs::PublishTopicToCategory do it 'should publish the topic to the new category' do message = MessageBus.track_publish do - described_class.new.execute(topic_timer_id: topic.topic_timer.id) + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id) end.last topic.reload diff --git a/spec/jobs/toggle_topic_closed_spec.rb b/spec/jobs/toggle_topic_closed_spec.rb index 8c973da719c..2c4b5c6c8a8 100644 --- a/spec/jobs/toggle_topic_closed_spec.rb +++ b/spec/jobs/toggle_topic_closed_spec.rb @@ -18,7 +18,7 @@ describe Jobs::ToggleTopicClosed do Timecop.travel(1.hour.from_now) do described_class.new.execute( - topic_timer_id: topic.topic_timer.id, + topic_timer_id: topic.public_topic_timer.id, state: true ) @@ -35,7 +35,7 @@ describe Jobs::ToggleTopicClosed do Timecop.travel(1.hour.from_now) do described_class.new.execute( - topic_timer_id: topic.topic_timer.id, + topic_timer_id: topic.public_topic_timer.id, state: false ) @@ -54,7 +54,7 @@ describe Jobs::ToggleTopicClosed do Topic.any_instance.expects(:update_status).never described_class.new.execute( - topic_timer_id: topic.topic_timer.id, + topic_timer_id: topic.public_topic_timer.id, state: true ) end @@ -69,7 +69,7 @@ describe Jobs::ToggleTopicClosed do it 'should not do anything' do described_class.new.execute( - topic_timer_id: topic.topic_timer.id, + topic_timer_id: topic.public_topic_timer.id, state: false ) diff --git a/spec/jobs/topic_reminder_spec.rb b/spec/jobs/topic_reminder_spec.rb new file mode 100644 index 00000000000..da84b680fad --- /dev/null +++ b/spec/jobs/topic_reminder_spec.rb @@ -0,0 +1,54 @@ +require 'rails_helper' + +describe Jobs::TopicReminder do + let(:admin) { Fabricate(:admin) } + let(:topic) { Fabricate(:topic, topic_timers: [ + Fabricate(:topic_timer, user: admin, status_type: TopicTimer.types[:reminder]) + ]) } + + before do + SiteSetting.queue_jobs = true + end + + it "should be able to create a reminder" do + topic_timer = topic.topic_timers.first + Timecop.freeze(1.day.from_now) do + expect { + described_class.new.execute(topic_timer_id: topic_timer.id) + }.to change { Notification.count }.by(1) + expect( admin.notifications.where(notification_type: Notification.types[:topic_reminder]).first&.topic_id ).to eq(topic.id) + expect( TopicTimer.where(id: topic_timer.id).first ).to be_nil + end + end + + it "does nothing if it was trashed before the scheduled time" do + topic_timer = topic.topic_timers.first + topic_timer.trash!(Discourse.system_user) + Timecop.freeze(1.day.from_now) do + expect { + described_class.new.execute(topic_timer_id: topic_timer.id) + }.to_not change { Notification.count } + end + end + + it "does nothing if job runs too early" do + topic_timer = topic.topic_timers.first + topic_timer.update_attribute(:execute_at, 8.hours.from_now) + Timecop.freeze(6.hours.from_now) do + expect { + described_class.new.execute(topic_timer_id: topic_timer.id) + }.to_not change { Notification.count } + end + end + + it "does nothing if topic was deleted" do + topic_timer = topic.topic_timers.first + topic.trash! + Timecop.freeze(1.day.from_now) do + expect { + described_class.new.execute(topic_timer_id: topic_timer.id) + }.to_not change { Notification.count } + end + end + +end \ No newline at end of file diff --git a/spec/models/category_spec.rb b/spec/models/category_spec.rb index d18d30a63e1..87e808c26c5 100644 --- a/spec/models/category_spec.rb +++ b/spec/models/category_spec.rb @@ -343,7 +343,7 @@ describe Category do it "should not set its description topic to auto-close" do category = Fabricate(:category, name: 'Closing Topics', auto_close_hours: 1) - expect(category.topic.topic_status_update).to eq(nil) + expect(category.topic.public_topic_timer).to eq(nil) end describe "creating a new category with the same slug" do diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb index 0fc5c80cd70..15bea596814 100644 --- a/spec/models/topic_spec.rb +++ b/spec/models/topic_spec.rb @@ -1253,7 +1253,7 @@ describe Topic do it 'updates topic status update execute_at if it was already set to close' do Timecop.freeze(now) do closing_topic.set_or_create_timer(TopicTimer.types[:close], 48) - expect(closing_topic.reload.topic_status_update.execute_at).to eq(2.days.from_now) + expect(closing_topic.reload.public_topic_timer.execute_at).to eq(2.days.from_now) end end @@ -1280,6 +1280,30 @@ describe Topic do end end end + + describe "private status type" do + let(:topic) { Fabricate(:topic) } + let(:reminder) { Fabricate(:topic_timer, user: admin, topic: topic, status_type: TopicTimer.types[:reminder]) } + let(:other_admin) { Fabricate(:admin) } + + it "lets two users have their own record" do + reminder + expect { + topic.set_or_create_timer(TopicTimer.types[:reminder], 2, by_user: other_admin) + }.to change { TopicTimer.count }.by(1) + end + + it "can update a user's existing record" do + Timecop.freeze(now) do + reminder + expect { + topic.set_or_create_timer(TopicTimer.types[:reminder], 11, by_user: admin) + }.to_not change { TopicTimer.count } + reminder.reload + expect(reminder.execute_at).to eq(11.hours.from_now) + end + end + end end describe 'for_digest' do diff --git a/spec/models/topic_timer_spec.rb b/spec/models/topic_timer_spec.rb index e1cb3212dc0..8ab6e1809e4 100644 --- a/spec/models/topic_timer_spec.rb +++ b/spec/models/topic_timer_spec.rb @@ -3,6 +3,7 @@ require 'rails_helper' RSpec.describe TopicTimer, type: :model do let(:topic_timer) { Fabricate(:topic_timer) } let(:topic) { Fabricate(:topic) } + let(:admin) { Fabricate(:admin) } before do Jobs::ToggleTopicClosed.jobs.clear @@ -10,13 +11,27 @@ RSpec.describe TopicTimer, type: :model do context "validations" do describe '#status_type' do - it 'should ensure that only one active topic status update exists' do + it 'should ensure that only one active public topic status update exists' do topic_timer.update!(topic: topic) Fabricate(:topic_timer, deleted_at: Time.zone.now, topic: topic) expect { Fabricate(:topic_timer, topic: topic) } .to raise_error(ActiveRecord::RecordInvalid) end + + it 'should ensure that only one active private topic timer exists per user' do + Fabricate(:topic_timer, topic: topic, user: admin, status_type: TopicTimer.types[:reminder]) + + expect { Fabricate(:topic_timer, topic: topic, user: admin, status_type: TopicTimer.types[:reminder]) } + .to raise_error(ActiveRecord::RecordInvalid) + end + + it 'should allow users to have their own private topic timer' do + Fabricate(:topic_timer, topic: topic, user: admin, status_type: TopicTimer.types[:reminder]) + expect { + Fabricate(:topic_timer, topic: topic, user: Fabricate(:admin), status_type: TopicTimer.types[:reminder]) + }.to_not raise_error + end end describe '#execute_at' do @@ -204,6 +219,27 @@ RSpec.describe TopicTimer, type: :model do end end end + + describe 'public_type' do + [:close, :open, :delete].each do |public_type| + it "is true for #{public_type}" do + timer = Fabricate(:topic_timer, status_type: described_class.types[public_type]) + expect(timer.public_type).to eq(true) + end + end + + it "is true for publish_to_category" do + timer = Fabricate(:topic_timer, status_type: described_class.types[:publish_to_category], category: Fabricate(:category)) + expect(timer.public_type).to eq(true) + end + + described_class.private_types.keys.each do |private_type| + it "is false for #{private_type}" do + timer = Fabricate(:topic_timer, status_type: described_class.types[private_type]) + expect(timer.public_type).to be_falsey + end + end + end end describe '.ensure_consistency!' do @@ -244,5 +280,19 @@ RSpec.describe TopicTimer, type: :model do expect(job_args["topic_timer_id"]).to eq(open_topic_timer.id) expect(job_args["state"]).to eq(false) end + + it "should enqueue remind me jobs that have been missed" do + reminder = Fabricate(:topic_timer, + status_type: described_class.types[:reminder], + execute_at: Time.zone.now - 1.hour, + created_at: Time.zone.now - 2.hour + ) + + expect { described_class.ensure_consistency! } + .to change { Jobs::TopicReminder.jobs.count }.by(1) + + job_args = Jobs::TopicReminder.jobs.first["args"].first + expect(job_args["topic_timer_id"]).to eq(reminder.id) + end end end