From e0b8586662d8fe52bde649892bef6702e942e180 Mon Sep 17 00:00:00 2001 From: Hermann Mayer Date: Mon, 8 Jul 2024 05:39:16 +0200 Subject: [PATCH] Dropped support for Ruby <2.7. (#8) Signed-off-by: Hermann Mayer --- .github/workflows/documentation.yml | 4 +- .github/workflows/release.yml | 6 +- .github/workflows/test.yml | 6 +- .gitignore | 1 + .rubocop.yml | 2 +- Appraisals | 12 + CHANGELOG.md | 3 +- Dockerfile | 6 +- Makefile | 2 +- alarmable.gemspec | 2 +- config/docker/.bashrc | 4 +- gemfiles/rails_6.1.gemfile | 23 ++ gemfiles/rails_7.1.gemfile | 23 ++ spec/alarmable_spec.rb | 382 +++++++++++++++++++++++++++ spec/concern_spec.rb | 387 ---------------------------- spec/spec_helper.rb | 3 +- 16 files changed, 462 insertions(+), 404 deletions(-) create mode 100644 gemfiles/rails_6.1.gemfile create mode 100644 gemfiles/rails_7.1.gemfile delete mode 100644 spec/concern_spec.rb diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index a6d8ed8..8894b15 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -17,9 +17,9 @@ jobs: - name: Install the correct Ruby version uses: ruby/setup-ruby@v1 with: - ruby-version: 2.5 + ruby-version: 2.7 bundler-cache: true - rubygems: '3.3.26' + rubygems: '3.4.22' - name: Prepare the virtual environment uses: hausgold/actions/ci@master diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7cf61fb..363bb30 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,12 +20,12 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install Ruby 2.5 + - name: Install Ruby 2.7 uses: ruby/setup-ruby@v1 with: - ruby-version: 2.5 + ruby-version: 2.7 bundler-cache: true - rubygems: '3.3.26' + rubygems: '3.4.22' - name: Prepare the virtual environment uses: hausgold/actions/ci@master diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ec15349..bfa13e7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,8 +18,8 @@ jobs: strategy: fail-fast: false matrix: - ruby: ['2.5', '2.7'] - rails: ['5.2'] + ruby: ['2.7'] + rails: ['5.2', '6.1', '7.1'] env: BUNDLE_GEMFILE: 'gemfiles/rails_${{ matrix.rails }}.gemfile' steps: @@ -30,7 +30,7 @@ jobs: with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - rubygems: '3.3.26' + rubygems: '3.4.22' - name: Prepare the virtual environment uses: hausgold/actions/ci@master diff --git a/.gitignore b/.gitignore index 0dd81e4..d194b86 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ /pkg/ /spec/reports/ /tmp/ +/log/ /vendor/ /gemfiles/vendor/ *.gemfile.lock diff --git a/.rubocop.yml b/.rubocop.yml index cd81854..512dd6f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -12,7 +12,7 @@ AllCops: NewCops: enable SuggestExtensions: false DisplayCopNames: true - TargetRubyVersion: 2.5 + TargetRubyVersion: 2.7 TargetRailsVersion: 5.2 Exclude: - bin/**/* diff --git a/Appraisals b/Appraisals index 27bf4d4..15aa0af 100644 --- a/Appraisals +++ b/Appraisals @@ -5,3 +5,15 @@ appraise 'rails-5.2' do gem 'activerecord', '~> 5.2.0' gem 'activesupport', '~> 5.2.0' end + +appraise 'rails-6.1' do + gem 'activejob', '~> 6.1.0' + gem 'activerecord', '~> 6.1.0' + gem 'activesupport', '~> 6.1.0' +end + +appraise 'rails-7.1' do + gem 'activejob', '~> 7.1.0' + gem 'activerecord', '~> 7.1.0' + gem 'activesupport', '~> 7.1.0' +end diff --git a/CHANGELOG.md b/CHANGELOG.md index 6319497..e72a339 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ ### next -* Moved the development dependencies from the gemspec to the Gemfile (#6) * Upgraded to PostgreSQL 15.2 and Redis 7.0 (#5) +* Moved the development dependencies from the gemspec to the Gemfile (#6) +* Dropped support for Ruby <2.7 (#8) ### 1.1.0 diff --git a/Dockerfile b/Dockerfile index def923e..13ccce4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM hausgold/ruby:2.5 +FROM hausgold/ruby:2.7 MAINTAINER Hermann Mayer # Update system gem -RUN gem update --system '3.3.26' +RUN gem update --system '3.4.22' # Install system packages and the latest bundler RUN apt-get update -yqqq && \ @@ -11,7 +11,7 @@ RUN apt-get update -yqqq && \ ca-certificates \ bash-completion inotify-tools && \ echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen && /usr/sbin/locale-gen && \ - gem install bundler -v '~> 2.3.0' --no-document --no-prerelease + gem install bundler -v '~> 2.4.22' --no-document --no-prerelease # Add new web user RUN mkdir /app && \ diff --git a/Makefile b/Makefile index 5f94152..fe106b9 100644 --- a/Makefile +++ b/Makefile @@ -120,7 +120,7 @@ test-style: \ test-style-ruby: # Run the static code analyzer (rubocop) @$(call run-shell,$(BUNDLE) exec $(RUBOCOP) -a \ - || ($(TEST) $$($(RUBY_VERSION)) != '2.5' && true)) + || ($(TEST) $$($(RUBY_VERSION)) != '2.7' && true)) clean: # Clean the dependencies diff --git a/alarmable.gemspec b/alarmable.gemspec index 508cb11..5babc31 100644 --- a/alarmable.gemspec +++ b/alarmable.gemspec @@ -34,7 +34,7 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.required_ruby_version = '>= 2.5' + spec.required_ruby_version = '>= 2.7' spec.add_dependency 'activejob', '>= 5.2' spec.add_dependency 'activejob-cancel', '~> 0.3' diff --git a/config/docker/.bashrc b/config/docker/.bashrc index 6884dc6..4942fcf 100644 --- a/config/docker/.bashrc +++ b/config/docker/.bashrc @@ -16,7 +16,9 @@ sudo sed -i 's/autostart=.*/autostart=false/g' /etc/supervisor/conf.d/* sudo supervisord >/dev/null 2>&1 & # Wait for supervisord -while ! supervisorctl status >/dev/null 2>&1; do sleep 1; done +while ! (sudo supervisorctl status | grep avahi) >/dev/null 2>&1; do + sleep 1 +done # Boot the mDNS stack echo '# Start the mDNS stack' diff --git a/gemfiles/rails_6.1.gemfile b/gemfiles/rails_6.1.gemfile new file mode 100644 index 0000000..6641ba6 --- /dev/null +++ b/gemfiles/rails_6.1.gemfile @@ -0,0 +1,23 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "appraisal", "~> 2.4" +gem "bundler", "~> 2.3" +gem "countless", "~> 1.1" +gem "guard-rspec", "~> 4.7" +gem "pg", "~> 1.4" +gem "railties", ">= 5.2" +gem "rake", "~> 13.0" +gem "rspec", "~> 3.12" +gem "rubocop", "~> 1.28" +gem "rubocop-rails", "~> 2.14" +gem "rubocop-rspec", "~> 2.10" +gem "simplecov", ">= 0.22" +gem "yard", ">= 0.9.28" +gem "yard-activesupport-concern", ">= 0.0.1" +gem "activejob", "~> 6.1.0" +gem "activerecord", "~> 6.1.0" +gem "activesupport", "~> 6.1.0" + +gemspec path: "../" diff --git a/gemfiles/rails_7.1.gemfile b/gemfiles/rails_7.1.gemfile new file mode 100644 index 0000000..8e65bd9 --- /dev/null +++ b/gemfiles/rails_7.1.gemfile @@ -0,0 +1,23 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "appraisal", "~> 2.4" +gem "bundler", "~> 2.3" +gem "countless", "~> 1.1" +gem "guard-rspec", "~> 4.7" +gem "pg", "~> 1.4" +gem "railties", ">= 5.2" +gem "rake", "~> 13.0" +gem "rspec", "~> 3.12" +gem "rubocop", "~> 1.28" +gem "rubocop-rails", "~> 2.14" +gem "rubocop-rspec", "~> 2.10" +gem "simplecov", ">= 0.22" +gem "yard", ">= 0.9.28" +gem "yard-activesupport-concern", ">= 0.0.1" +gem "activejob", "~> 7.1.0" +gem "activerecord", "~> 7.1.0" +gem "activesupport", "~> 7.1.0" + +gemspec path: "../" diff --git a/spec/alarmable_spec.rb b/spec/alarmable_spec.rb index 2b5e02f..29cca3a 100644 --- a/spec/alarmable_spec.rb +++ b/spec/alarmable_spec.rb @@ -2,8 +2,390 @@ require 'spec_helper' +class TestAlarmJob < ActiveJob::Base; end + +class TestAlarmable < ActiveRecord::Base + include Alarmable + self.alarm_job = TestAlarmJob + self.alarm_base_date_property = :start_at +end + +class TestAlarmableJobMissing < ActiveRecord::Base + include Alarmable + self.alarm_base_date_property = :start_at +end + +class TestAlarmableJobInvalid < ActiveRecord::Base + include Alarmable + self.alarm_job = :unknown +end + +class TestAlarmableBaseDateMissing < ActiveRecord::Base + include Alarmable + self.alarm_job = TestAlarmJob +end + +class TestAlarmableBaseDateInvalid < ActiveRecord::Base + include Alarmable + self.alarm_job = TestAlarmJob + self.alarm_base_date_property = false +end + +tables = %i[test_alarmables + test_alarmable_job_missings + test_alarmable_job_invalids + test_alarmable_base_date_missings + test_alarmable_base_date_invalids] + RSpec.describe Alarmable do + include ActiveJob::TestHelper + + def create_tables(*tables) + tables.each do |table| + ActiveRecord::Base.connection.create_table table do |t| + t.jsonb :alarms + t.jsonb :alarm_jobs + t.datetime :start_at + end + end + end + + def drop_tables(*tables) + tables.each do |table| + ActiveRecord::Base.connection.drop_table table + end + end + + # rubocop:disable RSpec/BeforeAfterAll because we are aware + before(:all) { create_tables(*tables) } + + before { enqueued_jobs.clear } + + after(:all) { drop_tables(*tables) } + + let(:alarmable) { TestAlarmable.new(start_at: 1.day.from_now) } + let(:test_job) { TestAlarmJob.perform_later } + let(:email_alarm) { { channel: 'email', before_minutes: 15 } } + let(:alarm_id) { '858dc938829b7a40b31e228f9e7a914d' } + let(:alarms_attributes) { { alarms: [email_alarm] } } + let(:alarm_jobs_attributes) do + { alarm_jobs: { alarm_id => test_job.job_id } } + end + + let(:uuid_regex) do + /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/ + end + it 'has a version number' do expect(Alarmable::VERSION).not_to be_nil end + + describe '#alarm_id' do + it 'generates a new alarm id (md5)' do + expect(alarmable.alarm_id('email', 15).length).to be 32 + end + + it 'generates the same id for the same inputs' do + expect(alarmable.alarm_id('email', 15)).to eql(alarm_id) + end + + it 'generates another id for different inputs' do + expect(alarmable.alarm_id('email', 16)).not_to eql(alarm_id) + end + end + + describe '#alarm_job' do + it 'delivers the alarm job on the instance' do + expect(alarmable.alarm_job).to eq(TestAlarmable.alarm_job) + end + end + + describe '#reschedule_alarm_job' do + context 'without notification base date' do + before do + alarmable.save + alarmable.update(start_at: nil) + end + + it 'schedules no new job' do + expect { alarmable.reschedule_alarm_job(email_alarm) }.not_to \ + (change { enqueued_jobs.count }).from(0) + end + + it 'cancels a older matching job' do + alarmable.update(alarm_jobs_attributes) + expect { alarmable.reschedule_alarm_job(email_alarm) }.to \ + (change { enqueued_jobs.count }).from(1).to(0) + end + end + + context 'with notification date already passed' do + let(:alarmable) do + opts = alarms_attributes.merge(start_at: 1.day.ago) + TestAlarmable.new(opts) + end + + it 'cancels nothing' do + test_job + expect { alarmable.reschedule_alarm_job(email_alarm) }.not_to \ + (change { enqueued_jobs }) + end + + it 'schedules no new jobs' do + alarmable.reschedule_alarm_job(email_alarm) + expect(enqueued_jobs).to be_empty + end + end + + context 'with notification date not yet passed' do + before { alarmable.save } + + it 'cancels nothing when no matching job was found' do + expect(TestAlarmJob).not_to receive(:cancel) + alarmable.reschedule_alarm_job(email_alarm) + end + + it 'cancels not non-matching jobs' do + job_id = test_job.job_id + alarmable.update_columns(alarm_jobs: { '404' => job_id }) + expect { alarmable.reschedule_alarm_job(email_alarm) }.to \ + change { enqueued_jobs.count }.from(1).to(2) + end + + it 'cancels matching jobs' do + job_id = test_job.job_id + alarmable.update_columns(alarm_jobs: { alarm_id => job_id }) + expect { alarmable.reschedule_alarm_job(email_alarm) }.to \ + (change { enqueued_jobs }) + end + + it 'passes back the partial alarm_job hash' do + result = alarmable.reschedule_alarm_job(email_alarm) + expect(result['858dc938829b7a40b31e228f9e7a914d']).to \ + match uuid_regex + end + end + + describe 'scheduling of a new alarm job' do + it 'schedules a new alarm job' do + expect { alarmable.reschedule_alarm_job(email_alarm) }.to \ + change { enqueued_jobs.count }.from(0).to(1) + end + + it 'with the persisted entity id' do + alarmable.save + alarmable.reschedule_alarm_job(email_alarm) + expect(enqueued_jobs.last[:args].first).not_to be_nil + end + + it 'with the entity id set as argument' do + alarmable.reschedule_alarm_job(email_alarm) + expect(enqueued_jobs.last[:args].first).to \ + eql(alarmable.id) + end + + it 'with the alarm set as argument' do + alarmable.reschedule_alarm_job(email_alarm) + expect(enqueued_jobs.last[:args].last).to \ + include(email_alarm.stringify_keys) + end + + it 'with correct schedule date' do + alarmable.reschedule_alarm_job(email_alarm) + expect(enqueued_jobs.last[:at]).to \ + eql((alarmable.start_at - 15.minutes).to_f) + end + end + end + + describe '#reschedule_alarm_jobs' do + before { alarmable.save } + + it 'reschedules every alarm' do + allow(alarmable).to receive(:reschedule_alarm_job).and_return({}) + expect(alarmable).to receive(:reschedule_alarm_job) + alarmable.alarms = [email_alarm] + alarmable.reschedule_alarm_jobs + end + + it 'cancels nothing on a clean reference pool' do + expect(TestAlarmJob).not_to receive(:cancel) + alarmable.alarms = [email_alarm] + alarmable.alarm_jobs = {} + alarmable.reschedule_alarm_jobs + end + + it 'cancels alarms which are not configured anymore' do + expect(TestAlarmJob).to receive(:cancel).with('404-job-id') + alarmable.alarms = [email_alarm] + alarmable.alarm_jobs = { '404' => '404-job-id' } + alarmable.reschedule_alarm_jobs + end + + # rubocop:disable RSpec/ExampleLength because we need 6 lines here :( + it 'cancels none updated jobs' do + allow(alarmable).to receive(:reschedule_alarm_job) \ + .and_return({ alarm_id => 'something-new' }) + expect(TestAlarmJob).not_to receive(:cancel).with('404-job-id') + alarmable.alarms = [email_alarm] + alarmable.alarm_jobs = { alarm_id => '404-job-id' } + alarmable.reschedule_alarm_jobs + end + # rubocop:enable RSpec/ExampleLength + + it 'updates the alarm_jobs property (persistence)' do + alarmable.alarms = [email_alarm] + alarmable.save + expect { alarmable.reschedule_alarm_jobs }.to \ + (change { alarmable.reload.alarm_jobs }) + end + end + + describe '#alarms_update_callback' do + before { alarmable.save } + + it 'only performs rescheduling when alarms was changed' do + expect(alarmable).to receive(:reschedule_alarm_jobs) + alarmable.alarms = [email_alarm] + alarmable.alarms_update_callback + end + + it 'performs no rescheduling when alarms is untouched' do + expect(alarmable).not_to receive(:reschedule_alarm_jobs) + alarmable.alarms_update_callback + end + end + + describe '#alarms_destroy_callback' do + it 'cancels all notification jobs from the reference pool' do + expect(TestAlarmJob).to receive(:cancel).with('cancel-now') + alarmable.alarm_jobs = { alarm_id => 'cancel-now' } + alarmable.alarms_destroy_callback + end + end + + describe 'hooks' do + describe '#alarm_defaults (after_initialize)' do + it 'sets an empty hash on the alarms property' do + expect(alarmable.alarms).to eq([]) + end + + it 'sets an empty hash on the alarm_jobs property' do + expect(alarmable.alarm_jobs).to eq({}) + end + end + + describe '#validate_alarm_settings (after_initialize)' do + describe 'alarm_job' do + it 'raise when not set' do + expect { TestAlarmableJobMissing.new }.to \ + raise_error(RuntimeError, /alarm_job/) + end + + # rubocop:disable RSpec/RepeatedExample because it looks like the same + # but the background is different + it 'raise not when set' do + expect { TestAlarmable.new }.not_to raise_error + end + # rubocop:enable RSpec/RepeatedExample + + it 'raise when not a class' do + expect { TestAlarmableJobInvalid.new }.to \ + raise_error(RuntimeError, /alarm_job/) + end + + # rubocop:disable RSpec/RepeatedExample because it looks like the same + # but the background is different + it 'raise not when a class' do + expect { TestAlarmable.new }.not_to raise_error + end + # rubocop:enable RSpec/RepeatedExample + end + + describe 'alarm_base_date_property' do + it 'raise when not set' do + expect { TestAlarmableBaseDateMissing.new }.to \ + raise_error(RuntimeError, /alarm_base_date_property/) + end + + # rubocop:disable RSpec/RepeatedExample because it looks like the same + # but the background is different + it 'raise not when set' do + expect { TestAlarmable.new }.not_to raise_error + end + # rubocop:enable RSpec/RepeatedExample + + it 'raise when not an useable property' do + expect { TestAlarmableBaseDateInvalid.new }.to \ + raise_error(RuntimeError, /alarm_base_date_property/) + end + + # rubocop:disable RSpec/RepeatedExample because it looks like the same + # but the background is different + it 'raise not when an useable property' do + expect { TestAlarmable.new }.not_to raise_error + end + # rubocop:enable RSpec/RepeatedExample + end + end + + describe '#reschedule_alarm_jobs (after_create)' do + describe 'no alarms change' do + it 'change not the alarm_jobs' do + expect { alarmable.save }.not_to \ + (change { alarmable.alarm_jobs }) + end + + it 'creates no new jobs' do + alarmable.save + expect(enqueued_jobs).to be_empty + end + end + + it 'schedules a new job' do + alarmable.alarms = [email_alarm] + expect { alarmable.save }.to \ + change { enqueued_jobs.count }.from(0).to(1) + end + + it 'changes the alarm_jobs property' do + alarmable.alarms = [email_alarm] + expect { alarmable.save }.to \ + (change { alarmable.alarm_jobs }) + end + + it 'schedules no job when base date is nil' do + alarmable.alarms = [email_alarm] + alarmable.start_at = nil + expect { alarmable.save }.not_to \ + (change { enqueued_jobs.count }).from(0) + end + end + + describe '#alarms_update_callback (before_update)' do + before { alarmable.save } + + it 'schedules a new job with the correct id' do + alarmable.update(alarms_attributes) + expect(enqueued_jobs.last[:args].first).to be >= 1 + end + + it 'schedules no job when base date is nil' do + opts = alarms_attributes.merge(start_at: nil) + expect { alarmable.update(opts) }.not_to \ + (change { enqueued_jobs.count }).from(0) + end + end + + describe '#alarms_destroy_callback (before_destroy)' do + before { alarmable.save } + + it 'cancels all notification jobs on destroy' do + alarmable.update(alarms_attributes) + expect { alarmable.destroy }.to \ + change { enqueued_jobs.count }.from(1).to(0) + end + end + end + # rubocop:enable RSpec/BeforeAfterAll end diff --git a/spec/concern_spec.rb b/spec/concern_spec.rb deleted file mode 100644 index 4177696..0000000 --- a/spec/concern_spec.rb +++ /dev/null @@ -1,387 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -class TestAlarmJob < ActiveJob::Base; end - -class TestAlarmable < ActiveRecord::Base - include Alarmable - self.alarm_job = TestAlarmJob - self.alarm_base_date_property = :start_at -end - -class TestAlarmableJobMissing < ActiveRecord::Base - include Alarmable - self.alarm_base_date_property = :start_at -end - -class TestAlarmableJobInvalid < ActiveRecord::Base - include Alarmable - self.alarm_job = :unknown -end - -class TestAlarmableBaseDateMissing < ActiveRecord::Base - include Alarmable - self.alarm_job = TestAlarmJob -end - -class TestAlarmableBaseDateInvalid < ActiveRecord::Base - include Alarmable - self.alarm_job = TestAlarmJob - self.alarm_base_date_property = false -end - -tables = %i[test_alarmables - test_alarmable_job_missings - test_alarmable_job_invalids - test_alarmable_base_date_missings - test_alarmable_base_date_invalids] - -RSpec.describe Alarmable do - include ActiveJob::TestHelper - - def create_tables(*tables) - tables.each do |table| - ActiveRecord::Base.connection.create_table table do |t| - t.jsonb :alarms - t.jsonb :alarm_jobs - t.datetime :start_at - end - end - end - - def drop_tables(*tables) - tables.each do |table| - ActiveRecord::Base.connection.drop_table table - end - end - - # rubocop:disable RSpec/BeforeAfterAll because we are aware - before(:all) { create_tables(*tables) } - - before { enqueued_jobs.clear } - - after(:all) { drop_tables(*tables) } - - let(:alarmable) { TestAlarmable.new(start_at: 1.day.from_now) } - let(:test_job) { TestAlarmJob.perform_later } - let(:email_alarm) { { channel: 'email', before_minutes: 15 } } - let(:alarm_id) { '858dc938829b7a40b31e228f9e7a914d' } - let(:alarms_attributes) { { alarms: [email_alarm] } } - let(:alarm_jobs_attributes) do - { alarm_jobs: { alarm_id => test_job.job_id } } - end - - let(:uuid_regex) do - /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/ - end - - describe '#alarm_id' do - it 'generates a new alarm id (md5)' do - expect(alarmable.alarm_id('email', 15).length).to be 32 - end - - it 'generates the same id for the same inputs' do - expect(alarmable.alarm_id('email', 15)).to be_eql(alarm_id) - end - - it 'generates another id for different inputs' do - expect(alarmable.alarm_id('email', 16)).not_to be_eql(alarm_id) - end - end - - describe '#alarm_job' do - it 'delivers the alarm job on the instance' do - expect(alarmable.alarm_job).to eq(TestAlarmable.alarm_job) - end - end - - describe '#reschedule_alarm_job' do - context 'without notification base date' do - before do - alarmable.save - alarmable.update(start_at: nil) - end - - it 'schedules no new job' do - expect { alarmable.reschedule_alarm_job(email_alarm) }.not_to \ - (change { enqueued_jobs.count }).from(0) - end - - it 'cancels a older matching job' do - alarmable.update(alarm_jobs_attributes) - expect { alarmable.reschedule_alarm_job(email_alarm) }.to \ - (change { enqueued_jobs.count }).from(1).to(0) - end - end - - context 'with notification date already passed' do - let(:alarmable) do - opts = alarms_attributes.merge(start_at: 1.day.ago) - TestAlarmable.new(opts) - end - - it 'cancels nothing' do - test_job - expect { alarmable.reschedule_alarm_job(email_alarm) }.not_to \ - (change { enqueued_jobs }) - end - - it 'schedules no new jobs' do - alarmable.reschedule_alarm_job(email_alarm) - expect(enqueued_jobs).to be_empty - end - end - - context 'with notification date not yet passed' do - before { alarmable.save } - - it 'cancels nothing when no matching job was found' do - expect(TestAlarmJob).not_to receive(:cancel) - alarmable.reschedule_alarm_job(email_alarm) - end - - it 'cancels not non-matching jobs' do - job_id = test_job.job_id - alarmable.update_columns(alarm_jobs: { '404' => job_id }) - expect { alarmable.reschedule_alarm_job(email_alarm) }.to \ - change { enqueued_jobs.count }.from(1).to(2) - end - - it 'cancels matching jobs' do - job_id = test_job.job_id - alarmable.update_columns(alarm_jobs: { alarm_id => job_id }) - expect { alarmable.reschedule_alarm_job(email_alarm) }.to \ - (change { enqueued_jobs }) - end - - it 'passes back the partial alarm_job hash' do - result = alarmable.reschedule_alarm_job(email_alarm) - expect(result['858dc938829b7a40b31e228f9e7a914d']).to \ - match uuid_regex - end - end - - describe 'scheduling of a new alarm job' do - it 'schedules a new alarm job' do - expect { alarmable.reschedule_alarm_job(email_alarm) }.to \ - change { enqueued_jobs.count }.from(0).to(1) - end - - it 'with the persisted entity id' do - alarmable.save - alarmable.reschedule_alarm_job(email_alarm) - expect(enqueued_jobs.last[:args].first).not_to be_nil - end - - it 'with the entity id set as argument' do - alarmable.reschedule_alarm_job(email_alarm) - expect(enqueued_jobs.last[:args].first).to \ - be_eql(alarmable.id) - end - - it 'with the alarm set as argument' do - alarmable.reschedule_alarm_job(email_alarm) - expect(enqueued_jobs.last[:args].last).to \ - include(email_alarm.stringify_keys) - end - - it 'with correct schedule date' do - alarmable.reschedule_alarm_job(email_alarm) - expect(enqueued_jobs.last[:at]).to \ - be_eql((alarmable.start_at - 15.minutes).to_f) - end - end - end - - describe '#reschedule_alarm_jobs' do - before { alarmable.save } - - it 'reschedules every alarm' do - allow(alarmable).to receive(:reschedule_alarm_job).and_return({}) - expect(alarmable).to receive(:reschedule_alarm_job) - alarmable.alarms = [email_alarm] - alarmable.reschedule_alarm_jobs - end - - it 'cancels nothing on a clean reference pool' do - expect(TestAlarmJob).not_to receive(:cancel) - alarmable.alarms = [email_alarm] - alarmable.alarm_jobs = {} - alarmable.reschedule_alarm_jobs - end - - it 'cancels alarms which are not configured anymore' do - expect(TestAlarmJob).to receive(:cancel).with('404-job-id') - alarmable.alarms = [email_alarm] - alarmable.alarm_jobs = { '404' => '404-job-id' } - alarmable.reschedule_alarm_jobs - end - - # rubocop:disable RSpec/ExampleLength because we need 6 lines here :( - it 'cancels none updated jobs' do - allow(alarmable).to receive(:reschedule_alarm_job) \ - .and_return({ alarm_id => 'something-new' }) - expect(TestAlarmJob).not_to receive(:cancel).with('404-job-id') - alarmable.alarms = [email_alarm] - alarmable.alarm_jobs = { alarm_id => '404-job-id' } - alarmable.reschedule_alarm_jobs - end - # rubocop:enable RSpec/ExampleLength - - it 'updates the alarm_jobs property (persistence)' do - alarmable.alarms = [email_alarm] - alarmable.save - expect { alarmable.reschedule_alarm_jobs }.to \ - (change { alarmable.reload.alarm_jobs }) - end - end - - describe '#alarms_update_callback' do - before { alarmable.save } - - it 'only performs rescheduling when alarms was changed' do - expect(alarmable).to receive(:reschedule_alarm_jobs) - alarmable.alarms = [email_alarm] - alarmable.alarms_update_callback - end - - it 'performs no rescheduling when alarms is untouched' do - expect(alarmable).not_to receive(:reschedule_alarm_jobs) - alarmable.alarms_update_callback - end - end - - describe '#alarms_destroy_callback' do - it 'cancels all notification jobs from the reference pool' do - expect(TestAlarmJob).to receive(:cancel).with('cancel-now') - alarmable.alarm_jobs = { alarm_id => 'cancel-now' } - alarmable.alarms_destroy_callback - end - end - - describe 'hooks' do - describe '#alarm_defaults (after_initialize)' do - it 'sets an empty hash on the alarms property' do - expect(alarmable.alarms).to eq([]) - end - - it 'sets an empty hash on the alarm_jobs property' do - expect(alarmable.alarm_jobs).to eq({}) - end - end - - describe '#validate_alarm_settings (after_initialize)' do - describe 'alarm_job' do - it 'raise when not set' do - expect { TestAlarmableJobMissing.new }.to \ - raise_error(RuntimeError, /alarm_job/) - end - - # rubocop:disable RSpec/RepeatedExample because it looks like the same - # but the background is different - it 'raise not when set' do - expect { TestAlarmable.new }.not_to raise_error - end - # rubocop:enable RSpec/RepeatedExample - - it 'raise when not a class' do - expect { TestAlarmableJobInvalid.new }.to \ - raise_error(RuntimeError, /alarm_job/) - end - - # rubocop:disable RSpec/RepeatedExample because it looks like the same - # but the background is different - it 'raise not when a class' do - expect { TestAlarmable.new }.not_to raise_error - end - # rubocop:enable RSpec/RepeatedExample - end - - describe 'alarm_base_date_property' do - it 'raise when not set' do - expect { TestAlarmableBaseDateMissing.new }.to \ - raise_error(RuntimeError, /alarm_base_date_property/) - end - - # rubocop:disable RSpec/RepeatedExample because it looks like the same - # but the background is different - it 'raise not when set' do - expect { TestAlarmable.new }.not_to raise_error - end - # rubocop:enable RSpec/RepeatedExample - - it 'raise when not an useable property' do - expect { TestAlarmableBaseDateInvalid.new }.to \ - raise_error(RuntimeError, /alarm_base_date_property/) - end - - # rubocop:disable RSpec/RepeatedExample because it looks like the same - # but the background is different - it 'raise not when an useable property' do - expect { TestAlarmable.new }.not_to raise_error - end - # rubocop:enable RSpec/RepeatedExample - end - end - - describe '#reschedule_alarm_jobs (after_create)' do - describe 'no alarms change' do - it 'change not the alarm_jobs' do - expect { alarmable.save }.not_to \ - (change { alarmable.alarm_jobs }) - end - - it 'creates no new jobs' do - alarmable.save - expect(enqueued_jobs).to be_empty - end - end - - it 'schedules a new job' do - alarmable.alarms = [email_alarm] - expect { alarmable.save }.to \ - change { enqueued_jobs.count }.from(0).to(1) - end - - it 'changes the alarm_jobs property' do - alarmable.alarms = [email_alarm] - expect { alarmable.save }.to \ - (change { alarmable.alarm_jobs }) - end - - it 'schedules no job when base date is nil' do - alarmable.alarms = [email_alarm] - alarmable.start_at = nil - expect { alarmable.save }.not_to \ - (change { enqueued_jobs.count }).from(0) - end - end - - describe '#alarms_update_callback (before_update)' do - before { alarmable.save } - - it 'schedules a new job with the correct id' do - alarmable.update(alarms_attributes) - expect(enqueued_jobs.last[:args].first).to be >= 1 - end - - it 'schedules no job when base date is nil' do - opts = alarms_attributes.merge(start_at: nil) - expect { alarmable.update(opts) }.not_to \ - (change { enqueued_jobs.count }).from(0) - end - end - - describe '#alarms_destroy_callback (before_destroy)' do - before { alarmable.save } - - it 'cancels all notification jobs on destroy' do - alarmable.update(alarms_attributes) - expect { alarmable.destroy }.to \ - change { enqueued_jobs.count }.from(1).to(0) - end - end - end - # rubocop:enable RSpec/BeforeAfterAll -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9b79581..390e246 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -32,7 +32,8 @@ # Configure Active Record db_config = Pathname.new(__dir__).join('config', 'database.yml') -ActiveRecord::Base.configurations = YAML.load_file(db_config) +load_method = YAML.respond_to?(:unsafe_load) ? :unsafe_load : :load +ActiveRecord::Base.configurations = YAML.send(load_method, db_config.read) ActiveRecord::Base.establish_connection(env) # Configure Active Job