-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- AlertCheckWorker likely to cause false alarms due to the callbacks being 429'd, so add a backup where if we have no delivered emails, we actively poll for them until we find a delivered one.
- Loading branch information
Showing
5 changed files
with
183 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
class CheckNotifyEmailService | ||
def initialize(status) | ||
@status = status | ||
@client = Notifications::Client.new(Rails.application.credentials.notify_api_key) | ||
end | ||
|
||
def present?(reference) | ||
results = @client.get_notifications( | ||
template_type: "email", | ||
status: @status, | ||
reference:, | ||
) | ||
|
||
results.collection.count.positive? | ||
rescue Notifications::Client::RequestError, Net::OpenTimeout, Net::ReadTimeout, SocketError => e | ||
Rails.logger.debug("Unable to contact Notify to determine status of email reference #{reference}: #{e}") | ||
end | ||
|
||
private | ||
|
||
attr_reader :email, :client | ||
|
||
def undeliverable_failure?(error) | ||
return false unless error.is_a?(Notifications::Client::BadRequestError) | ||
|
||
return true if error.message.end_with?("Not a valid email address") | ||
|
||
# This is a stop-gap fix for an issue where this system can produce emails | ||
# that are so long they exceed Notify's limit of 2MB. As emails of this | ||
# size will never succeed we mark them as failures. Ideally this will | ||
# eventually be resolved by some product thinking that only allows the | ||
# system to create emails within a reasonable length. | ||
error.message.start_with?("BadRequestError: Your message is too long") | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
class PollingAlertCheckWorker < ApplicationWorker | ||
include SearchAlertList | ||
|
||
def perform(document_type) | ||
content_items = get_alert_content_items(document_type:) | ||
delivered = 0 | ||
content_items.each do |ci| | ||
if any_emails_delivered_for?(ci[:content_id], ci[:valid_from], document_type) | ||
delivered += 1 | ||
else | ||
Rails.logger.warn("Couldn't find any delivered emails for #{document_type.titleize} records with content id #{ci[:content_id]} (at #{ci[:url]})") | ||
end | ||
end | ||
|
||
Rails.logger.info("Checking #{document_type.titleize} records: #{delivered} out of #{content_items.count} alerts have been delivered to at least one recipient") | ||
|
||
Rails.cache.write("current_#{document_type}s", content_items.count, expires_in: 15.minutes) | ||
Rails.cache.write("delivered_#{document_type}s", delivered, expires_in: 15.minutes) | ||
end | ||
|
||
def any_emails_delivered_for?(content_id, valid_from, document_type) | ||
return true if Email.where("notify_status = 'delivered' AND content_id = ? AND created_at > ?", content_id, valid_from).exists? | ||
|
||
Rails.logger.info("First pass couldn't find any delivered emails for #{document_type.titleize} records with content id #{content_id}, reverting to polling") | ||
|
||
service = CheckNotifyEmailService.new("delivered") | ||
Email.sent.where("content_id = ? AND created_at > ? AND notify_status IS NULL", content_id, valid_from).find_each do |email| | ||
return true if service.present?(email.id) | ||
end | ||
|
||
false | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,31 @@ def stub_notify | |
stub_request(:post, /notifications\.service\.gov\.uk/).to_return(body:) | ||
end | ||
|
||
def stub_notify_email_delivery_status_delivered(reference) | ||
stub_request(:get, "https://api.notifications.service.gov.uk/v2/notifications?reference=#{reference}&status=delivered&template_type=email").to_return( | ||
body: { | ||
notifications: [ | ||
{ | ||
"id": "740e5834-3a29-46b4-9a6f-16142fde533a", | ||
"reference": reference, | ||
"type": "email", | ||
"status": "delivered", | ||
}, | ||
], | ||
"links": {}, | ||
}.to_json, | ||
) | ||
end | ||
|
||
def stub_notify_email_delivery_status_delivered_empty(reference) | ||
stub_request(:get, "https://api.notifications.service.gov.uk/v2/notifications?reference=#{reference}&status=delivered&template_type=email").to_return( | ||
body: { | ||
notifications: [], | ||
"links": {}, | ||
}.to_json, | ||
) | ||
end | ||
|
||
def expect_an_email_was_sent(subject: /.*/, address: "[email protected]") | ||
request_data = nil | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
RSpec.describe PollingAlertCheckWorker do | ||
include SearchAlertListHelpers | ||
include NotifyRequestHelpers | ||
|
||
describe "#perform", caching: true do | ||
def perform | ||
described_class.new.perform("medical_safety_alert") | ||
end | ||
|
||
context "there are no alerts older than an hour" do | ||
before { stub_medical_safety_alert_feed(content_id: SecureRandom.uuid, age: 30.minutes) } | ||
|
||
it "should put 0/0 in the cache" do | ||
expect(Rails.cache).to receive(:write).with("current_medical_safety_alerts", 0, expires_in: 15.minutes) | ||
expect(Rails.cache).to receive(:write).with("delivered_medical_safety_alerts", 0, expires_in: 15.minutes) | ||
perform | ||
end | ||
end | ||
|
||
context "there are no alerts younger than 2 days" do | ||
before { stub_medical_safety_alert_feed(content_id: SecureRandom.uuid, age: 3.days) } | ||
|
||
it "should put 0/0 in the cache" do | ||
expect(Rails.cache).to receive(:write).with("current_medical_safety_alerts", 0, expires_in: 15.minutes) | ||
expect(Rails.cache).to receive(:write).with("delivered_medical_safety_alerts", 0, expires_in: 15.minutes) | ||
perform | ||
end | ||
end | ||
|
||
context "there is a valid alert with delivered emails" do | ||
before do | ||
content_id = SecureRandom.uuid | ||
stub_medical_safety_alert_feed(content_id:, age: 2.hours) | ||
create(:email, content_id:, notify_status: "delivered", status: "sent") | ||
end | ||
|
||
it "should put 1/1 in the cache" do | ||
expect(Rails.cache).to receive(:write).with("current_medical_safety_alerts", 1, expires_in: 15.minutes) | ||
expect(Rails.cache).to receive(:write).with("delivered_medical_safety_alerts", 1, expires_in: 15.minutes) | ||
perform | ||
end | ||
end | ||
|
||
context "there is a valid alert with undelivered emails" do | ||
before do | ||
content_id = SecureRandom.uuid | ||
stub_medical_safety_alert_feed(content_id:, age: 2.hours) | ||
email = create(:email, content_id:, notify_status: nil, status: "sent") | ||
stub_notify_email_delivery_status_delivered_empty(email.id) | ||
end | ||
|
||
it "should put 1/0 in the cache" do | ||
expect(Rails.cache).to receive(:write).with("current_medical_safety_alerts", 1, expires_in: 15.minutes) | ||
expect(Rails.cache).to receive(:write).with("delivered_medical_safety_alerts", 0, expires_in: 15.minutes) | ||
perform | ||
end | ||
end | ||
|
||
context "there is a valid alert with apparently undelivered emails but they actually were delivered" do | ||
before do | ||
content_id = SecureRandom.uuid | ||
stub_medical_safety_alert_feed(content_id:, age: 2.hours) | ||
email = create(:email, content_id:, notify_status: nil, status: "sent") | ||
stub_notify_email_delivery_status_delivered(email.id) | ||
end | ||
|
||
it "should put 1/0 in the cache" do | ||
expect(Rails.cache).to receive(:write).with("current_medical_safety_alerts", 1, expires_in: 15.minutes) | ||
expect(Rails.cache).to receive(:write).with("delivered_medical_safety_alerts", 1, expires_in: 15.minutes) | ||
perform | ||
end | ||
end | ||
|
||
context "there is a valid alert with delivered emails that are too old to be valid" do | ||
before do | ||
content_id = SecureRandom.uuid | ||
stub_medical_safety_alert_feed(content_id:, age: 2.hours) | ||
create(:email, content_id:, notify_status: "delivered", created_at: Time.zone.now - 3.hours, status: "sent") | ||
end | ||
|
||
it "should put 1/0 in the cache" do | ||
expect(Rails.cache).to receive(:write).with("current_medical_safety_alerts", 1, expires_in: 15.minutes) | ||
expect(Rails.cache).to receive(:write).with("delivered_medical_safety_alerts", 0, expires_in: 15.minutes) | ||
perform | ||
end | ||
end | ||
end | ||
end |