feat(lifecycle): add Guards::Base e 3 guards simples (ReservationActive, OptOutLabel, MaxPerReservation)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Rodribm10 2026-04-15 01:42:10 -03:00
parent f6aa39921a
commit 823008a1cd
8 changed files with 159 additions and 0 deletions

View File

@ -0,0 +1,4 @@
# frozen_string_literal: true
module Captain::Lifecycle::Guards
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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