diff --git a/enterprise/app/services/captain/lifecycle/guards.rb b/enterprise/app/services/captain/lifecycle/guards.rb new file mode 100644 index 000000000..1cbe1a6cd --- /dev/null +++ b/enterprise/app/services/captain/lifecycle/guards.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +module Captain::Lifecycle::Guards +end diff --git a/enterprise/app/services/captain/lifecycle/guards/base.rb b/enterprise/app/services/captain/lifecycle/guards/base.rb new file mode 100644 index 000000000..9ece3af21 --- /dev/null +++ b/enterprise/app/services/captain/lifecycle/guards/base.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class Captain::Lifecycle::Guards::Base + def initialize(delivery) + @delivery = delivery + @reservation = delivery.captain_reservation + @account = delivery.account + end + + def check + raise NotImplementedError + end + + protected + + def pass + { action: :pass } + end + + def skip(reason) + { action: :skip, reason: reason } + end + + def reschedule(new_fire_at) + { action: :reschedule, fire_at: new_fire_at } + end +end diff --git a/enterprise/app/services/captain/lifecycle/guards/max_per_reservation.rb b/enterprise/app/services/captain/lifecycle/guards/max_per_reservation.rb new file mode 100644 index 000000000..7d6713e22 --- /dev/null +++ b/enterprise/app/services/captain/lifecycle/guards/max_per_reservation.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class Captain::Lifecycle::Guards::MaxPerReservation < Captain::Lifecycle::Guards::Base + CAP = 5 + + def check + count = Captain::Lifecycle::Delivery.count_sent_for_reservation(@reservation.id) + return skip('max_reached') if count >= CAP + + pass + end +end diff --git a/enterprise/app/services/captain/lifecycle/guards/opt_out_label.rb b/enterprise/app/services/captain/lifecycle/guards/opt_out_label.rb new file mode 100644 index 000000000..264a541a1 --- /dev/null +++ b/enterprise/app/services/captain/lifecycle/guards/opt_out_label.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class Captain::Lifecycle::Guards::OptOutLabel < Captain::Lifecycle::Guards::Base + def check + config = Captain::Lifecycle::Config.for_account(@account) + label = config.opt_out_label + return pass if label.blank? + + contact = @reservation.contact + return pass if contact.blank? + + return skip('opt_out_label') if contact.label_list.include?(label.title) + + pass + end +end diff --git a/enterprise/app/services/captain/lifecycle/guards/reservation_active.rb b/enterprise/app/services/captain/lifecycle/guards/reservation_active.rb new file mode 100644 index 000000000..b36b36890 --- /dev/null +++ b/enterprise/app/services/captain/lifecycle/guards/reservation_active.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class Captain::Lifecycle::Guards::ReservationActive < Captain::Lifecycle::Guards::Base + BLOCKING_STATUSES = %w[cancelled no_show].freeze + + def check + return skip('reservation_cancelled') if BLOCKING_STATUSES.include?(@reservation.status.to_s) + + pass + end +end diff --git a/spec/enterprise/services/captain/lifecycle/guards/max_per_reservation_spec.rb b/spec/enterprise/services/captain/lifecycle/guards/max_per_reservation_spec.rb new file mode 100644 index 000000000..df5567281 --- /dev/null +++ b/spec/enterprise/services/captain/lifecycle/guards/max_per_reservation_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Captain::Lifecycle::Guards::MaxPerReservation do + subject(:guard) { described_class.new(delivery) } + + let(:reservation) { create(:captain_reservation) } + let(:delivery) { create(:captain_lifecycle_delivery, captain_reservation: reservation) } + + it 'passes with 0 sent deliveries' do + expect(guard.check).to eq(action: :pass) + end + + it 'passes with 4 sent deliveries' do + 4.times do + create(:captain_lifecycle_delivery, + captain_reservation: reservation, status: 'sent', origin: 'scheduled_lifecycle') + end + expect(guard.check).to eq(action: :pass) + end + + it 'skips at 5 sent deliveries' do + 5.times do + create(:captain_lifecycle_delivery, + captain_reservation: reservation, status: 'sent', origin: 'scheduled_lifecycle') + end + expect(guard.check).to eq(action: :skip, reason: 'max_reached') + end + + it 'ignores concierge_reply origin' do + 10.times do + create(:captain_lifecycle_delivery, + captain_reservation: reservation, status: 'sent', origin: 'concierge_reply') + end + expect(guard.check).to eq(action: :pass) + end +end diff --git a/spec/enterprise/services/captain/lifecycle/guards/opt_out_label_spec.rb b/spec/enterprise/services/captain/lifecycle/guards/opt_out_label_spec.rb new file mode 100644 index 000000000..bf8feba28 --- /dev/null +++ b/spec/enterprise/services/captain/lifecycle/guards/opt_out_label_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Captain::Lifecycle::Guards::OptOutLabel do + subject(:guard) { described_class.new(delivery) } + + let(:account) { create(:account) } + let(:label) { create(:label, account: account, title: 'nao_mandar_auto') } + let(:contact) { create(:contact, account: account) } + let(:reservation) { create(:captain_reservation, account: account, contact: contact) } + let(:delivery) { create(:captain_lifecycle_delivery, account: account, captain_reservation: reservation) } + + context 'when no config' do + it 'passes' do + expect(guard.check).to eq(action: :pass) + end + end + + context 'with config and opt_out_label configured' do + before { create(:captain_lifecycle_config, account: account, opt_out_label: label) } + + it 'passes when contact has no opt_out label' do + expect(guard.check).to eq(action: :pass) + end + + it 'skips when contact has the configured label' do + contact.update!(label_list: [label.title]) + expect(guard.check).to eq(action: :skip, reason: 'opt_out_label') + end + end +end diff --git a/spec/enterprise/services/captain/lifecycle/guards/reservation_active_spec.rb b/spec/enterprise/services/captain/lifecycle/guards/reservation_active_spec.rb new file mode 100644 index 000000000..d2e69993b --- /dev/null +++ b/spec/enterprise/services/captain/lifecycle/guards/reservation_active_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Captain::Lifecycle::Guards::ReservationActive do + subject(:guard) { described_class.new(delivery) } + + let(:reservation) { create(:captain_reservation, status: 'confirmed') } + let(:delivery) { create(:captain_lifecycle_delivery, captain_reservation: reservation) } + + it 'returns pass for active reservation' do + expect(guard.check).to eq(action: :pass) + end + + it 'returns skip for cancelled reservation' do + reservation.update!(status: 'cancelled') + expect(guard.check).to eq(action: :skip, reason: 'reservation_cancelled') + end +end