discourse/spec/models/topic_timer_spec.rb
Martin Brennan 5268568d23
FEATURE: Remove user topic timers and migrate to bookmarks with reminders (#10474)
This PR removes the user reminder topic timers, because that system has been supplanted and improved by bookmark reminders. The option is removed from the UI and all existing user reminder topic timers are migrated to bookmark reminders.

Migration does this:

* Get all topic_timers with status_type 5 (reminders)
* Gets all bookmarks where the user ID and topic ID match
* Loops through the found topic timers
  * If there is no bookmark for the OP of the topic, then we just create a bookmark with a reminder
  * If there is a bookmark for the OP of the topic and it does **not** have a reminder set, then just 
update it with the topic timer reminder
  * If there is a bookmark for the OP of the topic with a reminder then just discard the topic timer
* Cancels all outstanding user reminder topic timers
* **Trashes (not deletes) all user reminder topic timers**

Notes:

* For now I have left the user reminder topic timer job class in place; this is so the jobs can be cancelled in the migration. It and the specs will be deleted in the next PR.
* At a later date I will write a migration to delete all trashed user topic timers. They are not deleted here in case there are data issues and they need to be recovered.
* A future PR will change the UI of the topic timer modal to make it look more like the bookmark modal.
2020-09-14 11:11:55 +10:00

262 lines
7.8 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
RSpec.describe TopicTimer, type: :model do
fab!(:topic_timer) { Fabricate(:topic_timer) }
fab!(:topic) { Fabricate(:topic) }
fab!(:admin) { Fabricate(:admin) }
before { freeze_time }
context "validations" do
describe '#status_type' 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
end
describe '#execute_at' do
describe 'when #execute_at is greater than #created_at' do
it 'should be valid' do
topic_timer = Fabricate.build(:topic_timer,
execute_at: Time.zone.now + 1.hour,
user: Fabricate(:user),
topic: Fabricate(:topic)
)
expect(topic_timer).to be_valid
end
end
describe 'when #execute_at is smaller than #created_at' do
it 'should not be valid' do
topic_timer = Fabricate.build(:topic_timer,
execute_at: Time.zone.now - 1.hour,
created_at: Time.zone.now,
user: Fabricate(:user),
topic: Fabricate(:topic)
)
expect(topic_timer).to_not be_valid
end
end
end
describe '#category_id' do
describe 'when #status_type is publish_to_category' do
describe 'when #category_id is not present' do
it 'should not be valid' do
topic_timer = Fabricate.build(:topic_timer,
status_type: TopicTimer.types[:publish_to_category]
)
expect(topic_timer).to_not be_valid
expect(topic_timer.errors).to include(:category_id)
end
end
describe 'when #category_id is present' do
it 'should be valid' do
topic_timer = Fabricate.build(:topic_timer,
status_type: TopicTimer.types[:publish_to_category],
category_id: Fabricate(:category).id,
user: Fabricate(:user),
topic: Fabricate(:topic)
)
expect(topic_timer).to be_valid
end
end
end
end
end
context 'callbacks' do
describe 'when #execute_at and #user_id are not changed' do
it 'should not schedule another to update topic' do
Jobs.expects(:enqueue_at).never
Jobs.expects(:cancel_scheduled_job).never
topic_timer.update!(topic: Fabricate(:topic))
end
end
describe 'when #execute_at value is changed' do
it 'reschedules the job' do
Jobs.expects(:cancel_scheduled_job).with(
:toggle_topic_closed, topic_timer_id: topic_timer.id
)
expect_enqueued_with(job: :toggle_topic_closed, args: { topic_timer_id: topic_timer.id, state: true }, at: 3.days.from_now) do
topic_timer.update!(execute_at: 3.days.from_now, created_at: Time.zone.now)
end
end
describe 'when execute_at is smaller than the current time' do
it 'should enqueue the job immediately' do
expect_enqueued_with(job: :toggle_topic_closed, args: { topic_timer_id: topic_timer.id, state: true }, at: Time.zone.now) do
topic_timer.update!(
execute_at: Time.zone.now - 1.hour,
created_at: Time.zone.now - 2.hour
)
end
end
end
end
describe 'when user is changed' do
it 'should update the job' do
Jobs.expects(:cancel_scheduled_job).with(
:toggle_topic_closed, topic_timer_id: topic_timer.id
)
expect_enqueued_with(job: :toggle_topic_closed, args: { topic_timer_id: topic_timer.id, state: true }, at: topic_timer.execute_at) do
topic_timer.update!(user: admin)
end
end
end
describe 'when a open topic status update is created for an open topic' do
fab!(:topic) { Fabricate(:topic, closed: false) }
fab!(:topic_timer) do
Fabricate(:topic_timer,
status_type: described_class.types[:open],
topic: topic
)
end
before do
Jobs.run_immediately!
end
it 'should close the topic' do
topic_timer
expect(topic.reload.closed).to eq(true)
end
describe 'when topic has been deleted' do
it 'should not queue the job' do
topic.trash!
topic_timer
expect(Jobs::ToggleTopicClosed.jobs).to eq([])
end
end
end
describe 'when a close topic status update is created for a closed topic' do
fab!(:topic) { Fabricate(:topic, closed: true) }
fab!(:topic_timer) do
Fabricate(:topic_timer,
status_type: described_class.types[:close],
topic: topic
)
end
before do
Jobs.run_immediately!
end
it 'should open the topic' do
topic_timer
expect(topic.reload.closed).to eq(false)
end
describe 'when topic has been deleted' do
it 'should not queue the job' do
topic.trash!
topic_timer
expect(Jobs::ToggleTopicClosed.jobs).to eq([])
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
it 'should enqueue jobs that have been missed' do
close_topic_timer = Fabricate(:topic_timer,
execute_at: Time.zone.now - 1.hour,
created_at: Time.zone.now - 2.hour
)
open_topic_timer = Fabricate(:topic_timer,
status_type: described_class.types[:open],
execute_at: Time.zone.now - 1.hour,
created_at: Time.zone.now - 2.hour,
topic: Fabricate(:topic, closed: true)
)
Fabricate(:topic_timer, execute_at: Time.zone.now + 1.hour)
trashed_close_topic_timer = Fabricate(:topic_timer,
execute_at: Time.zone.now - 1.hour,
created_at: Time.zone.now - 2.hour
)
trashed_close_topic_timer.topic.trash!
trashed_open_topic_timer = Fabricate(:topic_timer,
execute_at: Time.zone.now - 1.hour,
created_at: Time.zone.now - 2.hour,
status_type: described_class.types[:open]
)
trashed_open_topic_timer.topic.trash!
# creating topic timers already enqueues jobs
# let's delete them to test ensure_consistency!
Sidekiq::Worker.clear_all
expect { described_class.ensure_consistency! }
.to change { Jobs::ToggleTopicClosed.jobs.count }.by(4)
expect(job_enqueued?(job: :toggle_topic_closed, args: {
topic_timer_id: close_topic_timer.id,
state: true
})).to eq(true)
expect(job_enqueued?(job: :toggle_topic_closed, args: {
topic_timer_id: open_topic_timer.id,
state: false
})).to eq(true)
expect(job_enqueued?(job: :toggle_topic_closed, args: {
topic_timer_id: trashed_close_topic_timer.id,
state: true
})).to eq(true)
expect(job_enqueued?(job: :toggle_topic_closed, args: {
topic_timer_id: trashed_open_topic_timer.id,
state: false
})).to eq(true)
end
end
end