136 lines
4.8 KiB
Ruby
136 lines
4.8 KiB
Ruby
require "test_helper"
|
|
|
|
class Mailings::OutboundQueueTest < ActiveSupport::TestCase
|
|
include ActionMailer::TestHelper
|
|
|
|
setup do
|
|
Mailings::OutboundQueue.min_interval = 0
|
|
Mailings::OutboundQueue.fail_interval = 0
|
|
Mailings::OutboundQueue.reset!
|
|
Mailings::SmtpGate.min_gap = 0
|
|
Mailings::SmtpGate.backoff_base = 0
|
|
Mailings::SmtpGate.reset!
|
|
end
|
|
|
|
teardown do
|
|
Mailings::OutboundQueue.min_interval = 0
|
|
Mailings::OutboundQueue.fail_interval = 0
|
|
Mailings::OutboundQueue.reset!
|
|
end
|
|
|
|
test "campaign jobs only enqueue, drain sends one email at a time" do
|
|
first = create_campaign_org(name: "Club Uno", email: "uno@example.com")
|
|
second = create_campaign_org(name: "Club Due", email: "due@example.com")
|
|
mailing_a = create_sending_mailing(name: "Campagna A")
|
|
mailing_b = create_sending_mailing(name: "Campagna B")
|
|
recipient_a = enqueue_org(mailing_a, first)
|
|
recipient_b = enqueue_org(mailing_b, second)
|
|
|
|
assert_equal "queued", recipient_a.reload.status
|
|
assert_equal "queued", recipient_b.status
|
|
|
|
assert_emails 1 do
|
|
assert_equal :sent, Mailings::OutboundQueue.drain_one!
|
|
end
|
|
assert_equal "sent", recipient_a.reload.status
|
|
assert_equal "queued", recipient_b.reload.status
|
|
|
|
assert_emails 1 do
|
|
assert_equal :sent, Mailings::OutboundQueue.drain_one!
|
|
end
|
|
assert_equal "sent", recipient_b.reload.status
|
|
assert_equal :empty, Mailings::OutboundQueue.drain_one!
|
|
end
|
|
|
|
test "does not start a second SMTP session while the lock is held" do
|
|
org = create_campaign_org(name: "Club Lock", email: "lock@example.com")
|
|
mailing = create_sending_mailing(name: "Campagna lock")
|
|
enqueue_org(mailing, org)
|
|
|
|
started = Queue.new
|
|
release = Queue.new
|
|
result = nil
|
|
thread = Thread.new do
|
|
Mailings::OutboundQueue.exclusive do
|
|
started << true
|
|
release.pop
|
|
end
|
|
end
|
|
started.pop
|
|
result = Mailings::OutboundQueue.drain_one!
|
|
release << true
|
|
thread.join
|
|
|
|
assert_equal :busy, result
|
|
assert_equal "queued", mailing.mailing_recipients.first.reload.status
|
|
end
|
|
|
|
test "transient SMTP errors stay queued and the next drain tries another recipient" do
|
|
first = create_campaign_org(name: "Club EOF", email: "eof@example.com")
|
|
second = create_campaign_org(name: "Club Ok", email: "ok@example.com")
|
|
mailing = create_sending_mailing(name: "Campagna eof")
|
|
recipient_a = enqueue_org(mailing, first)
|
|
recipient_b = enqueue_org(mailing, second)
|
|
gate = Mailings::SmtpGate
|
|
original = gate.method(:deliver)
|
|
gate.define_singleton_method(:deliver) { raise EOFError, "end of file reached" }
|
|
|
|
begin
|
|
assert_equal :deferred, Mailings::OutboundQueue.drain_one!
|
|
ensure
|
|
gate.define_singleton_method(:deliver, original)
|
|
end
|
|
|
|
recipient_a.reload
|
|
assert_equal "queued", recipient_a.status
|
|
assert_match(/Da ritentare \(1\/8\)/, recipient_a.error_message)
|
|
assert_equal recipient_b.id, Mailings::OutboundQueue.next_queued.id
|
|
|
|
# Il contatore deve avanzare sullo stesso destinatario (prima il regex
|
|
# non matchava "Da ritentare (N/8)" e restava sempre tentativo 1).
|
|
begin
|
|
gate.define_singleton_method(:deliver) { raise EOFError, "end of file reached" }
|
|
assert_equal :deferred, recipient_a.deliver_queued!
|
|
ensure
|
|
gate.define_singleton_method(:deliver, original)
|
|
end
|
|
assert_match(/Da ritentare \(2\/8\)/, recipient_a.reload.error_message)
|
|
end
|
|
|
|
test "next_queued skips recipients whose send window is closed" do
|
|
open_org = create_campaign_org(name: "Club Aperto", email: "aperto@example.com")
|
|
closed_org = create_campaign_org(name: "Club Chiuso", email: "chiuso@example.com")
|
|
open_mailing = create_sending_mailing(name: "Finestra aperta")
|
|
closed_mailing = create_sending_mailing(name: "Finestra chiusa")
|
|
closed_mailing.update!(send_window_enabled: true, send_window_start_minutes: 8 * 60, send_window_end_minutes: 9 * 60)
|
|
open_mailing.update!(send_window_enabled: false)
|
|
|
|
closed_recipient = enqueue_org(closed_mailing, closed_org)
|
|
open_recipient = enqueue_org(open_mailing, open_org)
|
|
closed_recipient.update_columns(updated_at: 1.hour.ago)
|
|
|
|
travel_to Time.zone.local(2026, 9, 10, 20, 0, 0) do
|
|
assert_equal open_recipient.id, Mailings::OutboundQueue.next_queued.id
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def create_sending_mailing(name:)
|
|
mailing = create_mailing(name: name, audience: "to_send", interval_seconds: 0)
|
|
mailing.update!(status: "sending", queued_at: Time.current, test_sent_at: Time.current, test_sent_to: "test@example.com")
|
|
mailing
|
|
end
|
|
|
|
def enqueue_org(mailing, org)
|
|
recipient = mailing.mailing_recipients.create!(
|
|
organization: org,
|
|
contact: org.contacts.first,
|
|
email: org.email,
|
|
status: "pending"
|
|
)
|
|
assert Mailings::OutboundQueue.enqueue(recipient.reload)
|
|
recipient
|
|
end
|
|
end
|