Skip to content

Commit 3b6aea1

Browse files
authored
Merge pull request #1935 from alphagov/rake-task-resend-submission-batch
Updating rake tasks to handle batches of submissions
2 parents c66f200 + 9fba374 commit 3b6aea1

File tree

4 files changed

+253
-13
lines changed

4 files changed

+253
-13
lines changed

app/jobs/send_submission_batch_job.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ def perform(delivery:)
3232

3333
message_id = AwsSesSubmissionBatchService.new(submissions_query: submissions, form:, date:, mode:).send_batch
3434

35+
delivery.new_attempt!
3536
delivery.update!(
3637
delivery_reference: message_id,
37-
last_attempt_at: Time.zone.now,
3838
)
3939

4040
CurrentJobLoggingAttributes.delivery_reference = delivery.delivery_reference

lib/tasks/submissions.rake

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ namespace :submissions do
1414

1515
deliveries = Delivery.failed.joins(:submissions).where(submissions: { form_id: form_id }).distinct
1616
Rails.logger.info "Found #{deliveries.length} bounced submission deliveries for form with ID #{form_id}"
17-
deliveries.find_each do |delivery|
18-
# This will need to be updated when we support batches
17+
deliveries.immediate.each do |delivery|
1918
submission = delivery.submissions.first
20-
Rails.logger.info "Submission reference: #{submission.reference}, created_at: #{submission.created_at}, last_attempt_at: #{delivery.last_attempt_at}"
19+
Rails.logger.info "Immediate delivery - submission reference: #{submission.reference}, created_at: #{submission.created_at}, last_attempt_at: #{delivery.last_attempt_at}"
20+
end
21+
deliveries.daily.each do |delivery|
22+
Rails.logger.info "Daily batch delivery - delivery_reference: #{delivery.delivery_reference}, created_at: #{delivery.created_at}, last_attempt_at: #{delivery.last_attempt_at}"
2123
end
2224
end
2325

@@ -42,13 +44,17 @@ namespace :submissions do
4244

4345
bounced_deliveries = Delivery.failed.joins(:submissions).where(submissions: { form_id: form_id }).distinct
4446

45-
Rails.logger.info "#{bounced_deliveries.length} submission deliveries to retry for form with ID: #{form_id}"
47+
Rails.logger.info "#{bounced_deliveries.length} deliveries to retry for form with ID: #{form_id}"
4648

4749
bounced_deliveries.each do |delivery|
48-
# This will need to be updated when we support batches
4950
submission = delivery.submissions.first
50-
Rails.logger.info "Retrying submission with reference #{submission.reference} for form with ID: #{form_id}"
51-
SendSubmissionJob.perform_later(submission)
51+
if delivery.immediate?
52+
Rails.logger.info "Retrying submission with reference #{submission.reference} for form with ID: #{form_id}"
53+
SendSubmissionJob.perform_later(submission)
54+
elsif delivery.daily?
55+
Rails.logger.info "Retrying daily batch delivery with delivery_id: #{delivery.id} for date: #{submission.submission_time.to_date} for form with ID: #{form_id}"
56+
SendSubmissionBatchJob.perform_later(delivery:)
57+
end
5258
end
5359
end
5460

@@ -166,6 +172,55 @@ namespace :submissions do
166172
end
167173
end
168174

175+
desc "Re-deliver daily submission batches corresponding to submissions between two timestamps for a specific form"
176+
task :redeliver_daily_batches_by_date, %i[form_id start_timestamp end_timestamp dry_run] => :environment do |_, args|
177+
form_id = args[:form_id]
178+
start_timestamp = args[:start_timestamp]
179+
end_timestamp = args[:end_timestamp]
180+
dry_run_arg = args[:dry_run]
181+
dry_run = dry_run_arg == "true"
182+
183+
usage_message = "usage: rake submissions:redeliver_daily_batches_by_date[<form_id>,<start_timestamp>,<end_timestamp>,<dry_run>]".freeze
184+
185+
if form_id.blank? || start_timestamp.blank? || end_timestamp.blank? || !dry_run_arg.in?(%w[true false])
186+
abort usage_message
187+
end
188+
189+
start_time = Time.zone.parse(start_timestamp)
190+
end_time = Time.zone.parse(end_timestamp)
191+
192+
if start_time.nil? || end_time.nil?
193+
abort "Error: Invalid timestamp format. Use ISO 8601 format (e.g. '2024-01-01T00:00:00Z')"
194+
end
195+
196+
if start_time >= end_time
197+
abort "Error: Start timestamp must be before end timestamp"
198+
end
199+
200+
deliveries_to_redeliver = Delivery.daily
201+
.joins(:submissions)
202+
.where(submissions: { form_id: form_id, created_at: start_time..end_time })
203+
.distinct
204+
205+
Rails.logger.info "Time range: #{start_time} to #{end_time}"
206+
Rails.logger.info "Dry run mode: #{dry_run ? 'enabled' : 'disabled'}"
207+
208+
if deliveries_to_redeliver.any?
209+
Rails.logger.info "Found #{deliveries_to_redeliver.count} submission batches to re-deliver for form ID: #{form_id}"
210+
211+
deliveries_to_redeliver.each do |delivery|
212+
if dry_run
213+
Rails.logger.info "Would re-deliver batch with ID #{delivery.id} and delivery reference #{delivery.delivery_reference}"
214+
else
215+
Rails.logger.info "Re-delivering batch ID #{delivery.id} and delivery reference #{delivery.delivery_reference}"
216+
SendSubmissionBatchJob.perform_later(delivery:)
217+
end
218+
end
219+
else
220+
Rails.logger.info "No batches found matching the criteria"
221+
end
222+
end
223+
169224
namespace :file_answers do
170225
desc "Generate filename for file upload answers that do not have an original filename stored and schedule the submission to be sent"
171226
task :fix_missing_original_filenames, %i[reference] => :environment do |_, args|

spec/jobs/send_submission_batch_job_spec.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@
7878
expect(delivery.reload.last_attempt_at).to be_within(1.second).of(@job_ran_at)
7979
end
8080

81+
context "when the delivery has already been attempted" do
82+
let(:delivery) { create(:delivery, delivery_schedule: "daily", submissions:, delivered_at: Time.zone.now - 2.hours, failed_at: Time.zone.now - 1.hour, failure_reason: "bounced") }
83+
84+
it "updates the resets the delivery details" do
85+
expect(delivery.reload.delivered_at).to be_nil
86+
expect(delivery.reload.failed_at).to be_nil
87+
expect(delivery.reload.failure_reason).to be_nil
88+
end
89+
end
90+
8191
it "attaches a csv with the expected filename" do
8292
expect(mail.attachments).not_to be_empty
8393

spec/lib/tasks/submissions.rake_spec.rb

Lines changed: 180 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464

6565
let!(:bounced_submission) { create :submission, :bounced, form_id: }
6666
let!(:another_bounced_submission) { create :submission, :bounced, form_id: }
67+
let!(:bounced_daily_delivery) { create :delivery, :failed, :daily_scheduled_delivery, submissions: [bounced_submission, another_bounced_submission] }
6768

6869
before do
6970
# create some submissions that won't be matched
@@ -72,9 +73,10 @@
7273
end
7374

7475
it "logs the bounced submissions" do
75-
expect(Rails.logger).to receive(:info).with("Found 2 bounced submission deliveries for form with ID #{form_id}")
76-
expect(Rails.logger).to receive(:info).with "Submission reference: #{bounced_submission.reference}, created_at: #{bounced_submission.created_at}, last_attempt_at: #{bounced_submission.single_submission_delivery.last_attempt_at}"
77-
expect(Rails.logger).to receive(:info).with "Submission reference: #{another_bounced_submission.reference}, created_at: #{another_bounced_submission.created_at}, last_attempt_at: #{another_bounced_submission.single_submission_delivery.last_attempt_at}"
76+
expect(Rails.logger).to receive(:info).with("Found 3 bounced submission deliveries for form with ID #{form_id}")
77+
expect(Rails.logger).to receive(:info).with "Immediate delivery - submission reference: #{bounced_submission.reference}, created_at: #{bounced_submission.created_at}, last_attempt_at: #{bounced_submission.single_submission_delivery.last_attempt_at}"
78+
expect(Rails.logger).to receive(:info).with "Immediate delivery - submission reference: #{another_bounced_submission.reference}, created_at: #{another_bounced_submission.created_at}, last_attempt_at: #{another_bounced_submission.single_submission_delivery.last_attempt_at}"
79+
expect(Rails.logger).to receive(:info).with "Daily batch delivery - delivery_reference: #{bounced_daily_delivery.delivery_reference}, created_at: #{bounced_daily_delivery.created_at}, last_attempt_at: #{bounced_daily_delivery.last_attempt_at}"
7880
task.invoke(form_id)
7981
end
8082
end
@@ -89,6 +91,7 @@
8991
let(:other_form_id) { 2 }
9092
let!(:bounced_submission) { create :submission, :bounced, form_id: }
9193
let!(:pending_submission) { create :submission, :sent, form_id: }
94+
let!(:bounced_delivery) { create :delivery, :daily_scheduled_delivery, :failed, submissions: [bounced_submission] }
9295

9396
before do
9497
create :submission, :sent, form_id: other_form_id
@@ -99,7 +102,7 @@
99102

100103
it "logs how many deliveries to retry" do
101104
allow(Rails.logger).to receive(:info)
102-
expect(Rails.logger).to receive(:info).with("1 submission deliveries to retry for form with ID: #{form_id}")
105+
expect(Rails.logger).to receive(:info).with("2 deliveries to retry for form with ID: #{form_id}")
103106

104107
task.invoke(*valid_args)
105108
end
@@ -108,6 +111,7 @@
108111
it "logs submissions that are being retried" do
109112
allow(Rails.logger).to receive(:info)
110113
expect(Rails.logger).to receive(:info).with("Retrying submission with reference #{bounced_submission.reference} for form with ID: #{form_id}")
114+
expect(Rails.logger).to receive(:info).with("Retrying daily batch delivery with delivery_id: #{bounced_delivery.id} for date: #{bounced_submission.submission_time.to_date} for form with ID: #{form_id}")
111115

112116
task.invoke(*valid_args)
113117
end
@@ -118,6 +122,12 @@
118122
}.to have_enqueued_job.with(bounced_submission)
119123
end
120124

125+
it "enqueues bounced daily deliveries for retrying" do
126+
expect {
127+
task.invoke(*valid_args)
128+
}.to have_enqueued_job.with(delivery: bounced_delivery)
129+
end
130+
121131
it "does not enqueue pending submissions for retrying" do
122132
expect {
123133
task.invoke(*valid_args)
@@ -254,6 +264,11 @@
254264
create :delivery, :failed, submissions: [submission], created_at: Time.parse("2024-01-01T18:00:00Z")
255265
end
256266

267+
let!(:matching_batch_delivery) do
268+
submission = create(:submission, form_id:)
269+
create :delivery, :failed, :daily_scheduled_delivery, submissions: [submission], created_at: Time.parse("2024-01-01T12:00:00Z")
270+
end
271+
257272
let!(:not_bounced_delivery) do
258273
submission = create(:submission, form_id:)
259274
create :delivery, submissions: [submission], created_at: Time.parse("2024-01-01T12:00:00Z")
@@ -291,9 +306,10 @@
291306

292307
it "logs the deliveries to disregard" do
293308
allow(Rails.logger).to receive(:info)
294-
expect(Rails.logger).to receive(:info).with("Found 2 bounced submission deliveries to disregard for form ID #{form_id} in time range: #{Time.zone.parse(start_time)} to #{Time.zone.parse(end_time)}").once
309+
expect(Rails.logger).to receive(:info).with("Found 3 bounced submission deliveries to disregard for form ID #{form_id} in time range: #{Time.zone.parse(start_time)} to #{Time.zone.parse(end_time)}").once
295310
expect(Rails.logger).to receive(:info).with("Disregarded bounce of delivery with delivery_reference #{early_matching_delivery.delivery_reference}")
296311
expect(Rails.logger).to receive(:info).with("Disregarded bounce of delivery with delivery_reference #{late_matching_delivery.delivery_reference}")
312+
expect(Rails.logger).to receive(:info).with("Disregarded bounce of delivery with delivery_reference #{matching_batch_delivery.delivery_reference}")
297313
task.invoke(*valid_args)
298314
end
299315

@@ -310,6 +326,7 @@
310326
allow(Rails.logger).to receive(:info)
311327
expect(Rails.logger).to receive(:info).with("Would disregard bounce of delivery with delivery_reference #{early_matching_delivery.delivery_reference} which was created at #{early_matching_delivery.created_at}")
312328
expect(Rails.logger).to receive(:info).with("Would disregard bounce of delivery with delivery_reference #{late_matching_delivery.delivery_reference} which was created at #{late_matching_delivery.created_at}")
329+
expect(Rails.logger).to receive(:info).with("Would disregard bounce of delivery with delivery_reference #{matching_batch_delivery.delivery_reference} which was created at #{matching_batch_delivery.created_at}")
313330
task.invoke(*valid_args)
314331
end
315332
end
@@ -518,6 +535,164 @@
518535
end
519536
end
520537

538+
describe "submissions:redeliver_daily_batches_by_date" do
539+
subject(:task) do
540+
Rake::Task["submissions:redeliver_daily_batches_by_date"]
541+
.tap(&:reenable)
542+
end
543+
544+
let(:form_id) { 1 }
545+
let(:other_form_id) { 2 }
546+
let(:start_time) { "2024-01-01T00:00:00Z" }
547+
let(:end_time) { "2024-01-02T00:00:00Z" }
548+
let(:dry_run) { "false" }
549+
550+
let!(:batched_submission) do
551+
create :submission,
552+
:sent,
553+
form_id:,
554+
created_at: Time.parse("2024-01-01T12:00:00Z"),
555+
reference: "ref1"
556+
end
557+
558+
let!(:batch_delivery) do
559+
create :delivery,
560+
:daily_scheduled_delivery,
561+
:failed,
562+
submissions: [batched_submission],
563+
created_at: Time.parse("2024-01-02T02:00:00Z"),
564+
delivery_reference: "batch1"
565+
end
566+
567+
let!(:next_day_batch_delivery) do
568+
submission = create :submission,
569+
:sent,
570+
form_id:,
571+
created_at: Time.parse("2024-01-02T12:00:00Z"),
572+
reference: "ref1"
573+
create :delivery,
574+
:daily_scheduled_delivery,
575+
:failed,
576+
submissions: [submission],
577+
created_at: Time.parse("2024-01-03T02:00:00Z"),
578+
delivery_reference: "batch-next-day"
579+
end
580+
581+
let!(:other_form_batch_delivery) do
582+
submission = create :submission,
583+
:sent,
584+
form_id: other_form_id,
585+
created_at: Time.parse("2024-01-01T12:00:00Z"),
586+
reference: "ref2"
587+
create :delivery,
588+
:daily_scheduled_delivery,
589+
:failed,
590+
submissions: [submission],
591+
created_at: Time.parse("2024-01-02T02:00:00Z"),
592+
delivery_reference: "batch2"
593+
end
594+
595+
context "with valid arguments" do
596+
let(:valid_args) { [form_id, start_time, end_time, dry_run] }
597+
598+
it "enqueues matching batch delivery for re-delivery" do
599+
expect {
600+
task.invoke(*valid_args)
601+
}.to have_enqueued_job(SendSubmissionBatchJob).with(delivery: batch_delivery)
602+
end
603+
604+
it "does not enqueue a batch delivery outside the time range" do
605+
expect {
606+
task.invoke(*valid_args)
607+
}.not_to have_enqueued_job(SendSubmissionBatchJob).with(delivery: next_day_batch_delivery)
608+
end
609+
610+
it "does not enqueue another form's batch deliveries" do
611+
expect {
612+
task.invoke(*valid_args)
613+
}.not_to have_enqueued_job(SendSubmissionBatchJob).with(delivery: other_form_batch_delivery)
614+
end
615+
616+
context "when dry_run is true" do
617+
let(:dry_run) { "true" }
618+
619+
it "does not enqueue any jobs" do
620+
expect {
621+
task.invoke(*valid_args)
622+
}.not_to have_enqueued_job(SendSubmissionJob)
623+
end
624+
end
625+
626+
context "when no submissions took place between the given times" do
627+
let(:valid_args) { [form_id, "2025-01-01T00:00:00Z", "2025-01-02T00:00:00Z", dry_run] }
628+
629+
it "does not enqueue any jobs" do
630+
expect {
631+
task.invoke(*valid_args)
632+
}.not_to have_enqueued_job(SendSubmissionBatchJob)
633+
end
634+
end
635+
end
636+
637+
context "with invalid arguments" do
638+
it "aborts when form_id is missing" do
639+
expect {
640+
task.invoke("", start_time, end_time, dry_run)
641+
}.to raise_error(SystemExit)
642+
.and output("usage: rake submissions:redeliver_daily_batches_by_date[<form_id>,<start_timestamp>,<end_timestamp>,<dry_run>]\n").to_stderr
643+
end
644+
645+
it "aborts when start_timestamp is missing" do
646+
expect {
647+
task.invoke(form_id, "", end_time, dry_run)
648+
}.to raise_error(SystemExit)
649+
.and output("usage: rake submissions:redeliver_daily_batches_by_date[<form_id>,<start_timestamp>,<end_timestamp>,<dry_run>]\n").to_stderr
650+
end
651+
652+
it "aborts when end_timestamp is missing" do
653+
expect {
654+
task.invoke(form_id, start_time, "", dry_run)
655+
}.to raise_error(SystemExit)
656+
.and output("usage: rake submissions:redeliver_daily_batches_by_date[<form_id>,<start_timestamp>,<end_timestamp>,<dry_run>]\n").to_stderr
657+
end
658+
659+
it "aborts when start_timestamp is invalid" do
660+
expect {
661+
task.invoke(form_id, "invalid-date", end_time, dry_run)
662+
}.to raise_error(SystemExit)
663+
.and output("Error: Invalid timestamp format. Use ISO 8601 format (e.g. '2024-01-01T00:00:00Z')\n").to_stderr
664+
end
665+
666+
it "aborts when end_timestamp is invalid" do
667+
expect {
668+
task.invoke(form_id, start_time, "invalid-date", dry_run)
669+
}.to raise_error(SystemExit)
670+
.and output("Error: Invalid timestamp format. Use ISO 8601 format (e.g. '2024-01-01T00:00:00Z')\n").to_stderr
671+
end
672+
673+
it "aborts when start_timestamp is after end_timestamp" do
674+
expect {
675+
task.invoke(form_id, "2024-01-02T00:00:00Z", "2024-01-01T00:00:00Z", dry_run)
676+
}.to raise_error(SystemExit)
677+
.and output("Error: Start timestamp must be before end timestamp\n").to_stderr
678+
end
679+
680+
it "aborts when start_timestamp equals end_timestamp" do
681+
expect {
682+
task.invoke(form_id, start_time, start_time, dry_run)
683+
}.to raise_error(SystemExit)
684+
.and output("Error: Start timestamp must be before end timestamp\n").to_stderr
685+
end
686+
687+
it "aborts when dry_run is an invalid value" do
688+
expect {
689+
task.invoke(form_id, start_time, start_time, "foo")
690+
}.to raise_error(SystemExit)
691+
.and output("usage: rake submissions:redeliver_daily_batches_by_date[<form_id>,<start_timestamp>,<end_timestamp>,<dry_run>]\n").to_stderr
692+
end
693+
end
694+
end
695+
521696
describe "submissions:file_answers:fix_missing_original_filenames" do
522697
subject(:task) do
523698
Rake::Task["submissions:file_answers:fix_missing_original_filenames"]

0 commit comments

Comments
 (0)