diff --git a/app/models/user_case_contact_types_reminder.rb b/app/models/user_case_contact_types_reminder.rb new file mode 100644 index 0000000000..943021eb2b --- /dev/null +++ b/app/models/user_case_contact_types_reminder.rb @@ -0,0 +1,22 @@ +class UserCaseContactTypesReminder < ApplicationRecord + belongs_to :user +end + +# == Schema Information +# +# Table name: user_case_contact_types_reminders +# +# id :bigint not null, primary key +# reminder_sent :datetime +# created_at :datetime not null +# updated_at :datetime not null +# user_id :bigint not null +# +# Indexes +# +# index_user_case_contact_types_reminders_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) +# diff --git a/app/services/short_url_service.rb b/app/services/short_url_service.rb index 1ac3634079..bc2be84cbf 100644 --- a/app/services/short_url_service.rb +++ b/app/services/short_url_service.rb @@ -8,9 +8,10 @@ class ShortUrlService headers RequestHeader::ACCEPT_JSON headers RequestHeader::CONTENT_TYPE_JSON - def initialize(short_domain = nil, api_key = nil) - @short_domain = short_domain - @short_api_key = api_key + def initialize + validate_credentials + @short_domain = Rails.application.credentials[:SHORT_IO_DOMAIN] + @short_api_key = Rails.application.credentials[:SHORT_IO_API_KEY] @short_url = nil end @@ -23,4 +24,15 @@ def create_short_url(original_url = nil) @short_url = JSON.parse(response.body)["shortURL"] response end + + private + + def validate_credentials + variables = [Rails.application.credentials[:SHORT_IO_DOMAIN], Rails.application.credentials[:SHORT_IO_API_KEY]] + variables.each do |var| + if var.blank? + raise "#{var} environment variable missing for Short IO serivce" + end + end + end end diff --git a/config/credentials/development.yml.enc b/config/credentials/development.yml.enc index e322b59642..4c0cc7ab67 100644 --- a/config/credentials/development.yml.enc +++ b/config/credentials/development.yml.enc @@ -1 +1 @@ -1Oa+HbXh12bAr8uQOAThcsDm9IZ+6QkW7XDQyKEehHicQ6r0Ujf6teoeEsoJeHqnbZCCuO1nTzlQxsFoGKTCm4pKzbMmuueq1lRAzADomODCzzJ5OPTGy0lZc9LD61PLp8YmKufNPIfoJTX1b6OhKgArVR320mH3D2HBKZ677cZvoFPcbPQujnbhlURXaZFtTi8gjOmFvgqNa+EMOz1voUUWDyFeImzwjC6fGe2/KEQQX/5e1P0wyqgjET4S--MdZnjuArDnv8aXVt--hrZS+Llnb4VJoqmuCS06og== \ No newline at end of file +svCtLWmi6TUWfy4jhsNxZgGKdzBrjq5JjKkGUaDA5tlP2XFn6XY8lJDVhF+T82kGjwT4EgsBheMZqPMbytlJ6iSDBIq/bHfjl1E5Zx3DqCkd4gDYgVK0roJffesKQPuWUSQUzvJV9pZ9VQEKbh+YA/I/N6aWGbkYlKXTOPHMY7F+rfiKXb8vHodUGWxCTycsWLpe/ohBvF7zzSwxkG7sEmbnRnqYd2Tmn0ASf6vNKXOzPamQ21rrgUss427/zjCjzWHCk4iUaHnhQQYwC2zJ+m1/0Uu+sM5CkYJhddsPbeeQkd7vgPjHBylgkT6L86XTz8sBrQDZB51TbmNouygu96NzQwE472c0csFEWwjz7fepy7sZkHN5KqQ=--dx6D/QqFOeacGYGg--+r3ffqcg8wONL9oMId9u5g== \ No newline at end of file diff --git a/config/credentials/production.yml.enc b/config/credentials/production.yml.enc index e34c37400e..e7e3d7ec79 100644 --- a/config/credentials/production.yml.enc +++ b/config/credentials/production.yml.enc @@ -1 +1 @@ -AxlCeUg6T11ONc/6YWPkWk+sJi5dMmJ0KhtVbxxZb+4s/CUFi5F3dLER1vqmyu9Zt+IDrwojE2gvm+Yw3ympbeg5km40o88QJRz76FZoRrvOPRsT76iYW9Fy2qXCUJyd04HNIbW2P2onAY4OtwVCQYf65EtazDh1B5BFJneEa/cIHs/QKFAqE9hxPA05Uvzc+O1F8xmg3v7Dp0cLvRl2JpSRTr0JDBktcWRsS4g+pnQwC2la22SVgg2RYgzerEU6BJKAs0wlf0VuytXdKNw92rfCl11pAZyh3/3Ocv2mY78sgpK3FOObXfpnPHAoNI1pLL1sJveM34L1s6tDAUSQ1rAe7fQO1qDtkooZ+Ak/IyHUUTvl62C5zFafnBmjrAUvHSr11l/LrSjkWlsV9KpFXBlKQYs4rJAsDPPr3AJ4YnmyZyxgrQYrB3jNRB1IENRD87t6pg6geFlbPbnTmmbMl2oW2HwllStgDFkOUgcGRBXgdDVv1YgR9aAIP2jZBhLD1jt3a+XxqsrJBRvdGMa3rtTGj4s8+FrVG0iLibvd/hoVlMldVEj6HTQYI2lIzTQ4cI/chw==--IinvJwfP8oNrT+d2--V47XslB/SUn6I78Fb4MUYg== \ No newline at end of file +Y85/mpXDwuasGg7odbPklWamaevc8nlJJExz5YTnGBR/S5wCGTkkH7hwUBPINNTkYXpe3N1QN2ILq+NuGqTJhgtpy6Glwnk743UlxsFUChUQmSlccJfML1YQWKx3R1VSAFWjOoHiV9vnA0RcKf9tIQ5FWY5S8/IMn4SlpRbhU0zJfkHAb3Hbx5juTWmOWHh4BD+AmKYqu9lpVCCwQkitRJX/8AwLUBprbcaHm0nh9u4PL/Q/Fj0/UadHF2RCWDnpRxUAygqgMpCBxqZwpHzN3cRrATL3aDYFZt0CZzpkpnHBKQcuSs1K/oki06Jas+Z585iO00S6r4NRJtkPVkznFqu0DJ8JkUKriLDXOwbvcEHOHtCS0t2hCxYLPZvSg51qBsZbgS5No3+cfXPeGjIecq+h1c7zpg70PNieO6290IM/jpNYA9ZEt9nPXwUodAauMkvcocXlOxX4mOaYsL7fMiXhgu/JzqwK64maSXpCBaEEPcOwIkaKhTwn+w+iABnTYnhZeD2t3TMoFpzkI83w7cg9Mbetm1N9faX43dsYXRW93tFwSkJtF4b//CIxOyG9xFxruGNwnBqy8+IuoaSyBVF3WecnWjj+7TGSgmgl7FmiaUSjDDJkh27K8QDHbpevQM/Rc5U=--8GtVk0JHu3Dkz4jI--/FAbx4kRZsAKWLg9/+IWbw== \ No newline at end of file diff --git a/config/credentials/test.yml.enc b/config/credentials/test.yml.enc index 4d01e24fbc..51801db2a1 100644 --- a/config/credentials/test.yml.enc +++ b/config/credentials/test.yml.enc @@ -1 +1 @@ -lsnR22as6mTVDa2pPJo+EjZ+OYq92HViKKhnKj1GwMWspKIJTkndzJ3xJYVkGJbrY2tAmHr+VXNauNaDukCsovBhHry9TN2Ngr4SzCU2x+hpzMgqBHt4mdIrAZWVTr+paA1hr/rS8k7ZGnK/uUx7ifboyMwHCX2tVV+m7ZHkBciHIFaZs2rwJDB4fsI9tKk/9DkXOmELV3VuRmw6eUnFyW84gjj2vQ18mczrNx6Sbq6zakhassqcXTgJRgey--KkLl5vLri+smsHTB--By5/dkptNeOUBKDJyAfRYA== \ No newline at end of file +zsvjeQzFV0vM7h3jsx7RUyA3WTQsEYWvEMreEvVbi4L0HN7sDNrP7kay2FZS5VEwrW5mJZdnu63BXa8fK/h1agvaaiOO9FhDXfyK+VT86TAfsLa1gsBK9mHjWSdXCJE9TZj9OjOtR3R/qHOI+2uPUnysh7Om4a0ckiu4Jwex3OcbgCYj2+G2JQtwHkWhlyBthGxLjuDDFfx+qxkJWkN7V9FhN0FkkPaflyj4FjR9BUf3/CB8pvHXqJ1lmxVScYsyhh50mc+CKyVptpqbLi9Jou3SiUePREX03ynV0KPR+7mT3FH9gCj4QyzzS1t3JOUfrgqeVFAzdV1TW01olinOyG2aMrZn1aA7GWfDeIr/GwnaPfUMmZNj4RQ=--KmPWCR7xHr6jUSx5--1L2S0bUzD2Bc+JFAHX7xKg== \ No newline at end of file diff --git a/db/migrate/20220526011848_create_user_case_contact_types_reminders.rb b/db/migrate/20220526011848_create_user_case_contact_types_reminders.rb new file mode 100644 index 0000000000..a46ec55d1c --- /dev/null +++ b/db/migrate/20220526011848_create_user_case_contact_types_reminders.rb @@ -0,0 +1,10 @@ +class CreateUserCaseContactTypesReminders < ActiveRecord::Migration[7.0] + def change + create_table :user_case_contact_types_reminders do |t| + t.belongs_to :user, null: false, foreign_key: true + t.datetime :reminder_sent + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index d3bcab1a02..858f329873 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -404,6 +404,14 @@ t.string "version", null: false end + create_table "user_case_contact_types_reminders", force: :cascade do |t| + t.bigint "user_id", null: false + t.datetime "reminder_sent" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["user_id"], name: "index_user_case_contact_types_reminders_on_user_id" + end + create_table "user_sms_notification_events", force: :cascade do |t| t.bigint "user_id", null: false t.bigint "sms_notification_event_id", null: false @@ -484,6 +492,7 @@ add_foreign_key "sent_emails", "users" add_foreign_key "supervisor_volunteers", "users", column: "supervisor_id" add_foreign_key "supervisor_volunteers", "users", column: "volunteer_id" + add_foreign_key "user_case_contact_types_reminders", "users" add_foreign_key "user_sms_notification_events", "sms_notification_events" add_foreign_key "user_sms_notification_events", "users" add_foreign_key "users", "casa_orgs" diff --git a/lib/tasks/case_contact_types_reminder.rb b/lib/tasks/case_contact_types_reminder.rb new file mode 100644 index 0000000000..1f9fb7e97f --- /dev/null +++ b/lib/tasks/case_contact_types_reminder.rb @@ -0,0 +1,97 @@ +class CaseContactTypesReminder + NEW_CASE_CONTACT_PAGE_PATH = Rails.application.credentials[:BASE_URL] + FIRST_MESSAGE = "It's been 60 days or more since you've reached out to these members of your youth's network:\n" + THIRD_MESSAGE = "If you have made contact with them in the past 60 days, remember to log it: " + + def send! + if NEW_CASE_CONTACT_PAGE_PATH.blank? + raise "NEW_CASE_CONTACT_PAGE_PATH environment variable not defined" + end + responses = [] + eligible_volunteers = Volunteer.where(receive_sms_notifications: true) + .where.not(phone_number: nil) + .select { |v| !last_reminder_within_quarter(v) } + + eligible_volunteers.each do |volunteer| + uncontacted_case_contact_type_names = uncontacted_case_contact_types(volunteer) + if uncontacted_case_contact_type_names.count > 0 + responses.push( + { + volunteer: volunteer, + messages: send_sms_messages(volunteer, uncontacted_case_contact_type_names) + } + ) + update_reminder_sent_time(volunteer) + end + end + + responses + end + + private + + def uncontacted_case_contact_types(volunteer) + contacted_types = volunteer.case_contacts.where("occurred_at > ?", 2.months.ago).joins(:contact_types).pluck(:name) + ContactType.all.pluck(:name).uniq - contacted_types + end + + def send_sms_messages(volunteer, uncontacted_case_contact_type_names) + volunteer_casa_org = volunteer.casa_org + if !valid_casa_twilio_creds(volunteer_casa_org) + return + end + + twilio_service = TwilioService.new(volunteer_casa_org.twilio_api_key_sid, volunteer_casa_org.twilio_api_key_secret, volunteer_casa_org.twilio_account_sid) + sms_params = { + From: volunteer_casa_org.twilio_phone_number, + Body: nil, + To: volunteer.phone_number + } + + messages = [ + FIRST_MESSAGE, + uncontacted_case_contact_type_names.map { |name| "• #{name}" }.join("\n"), + THIRD_MESSAGE + new_case_contact_page_short_link + ] + + responses = [] + messages.each do |content| + sms_params[:Body] = content + responses.push(twilio_service.send_sms(sms_params)) + end + + responses + end + + def valid_casa_twilio_creds(casa_org) + casa_org.twilio_phone_number? && casa_org.twilio_account_sid? && casa_org.twilio_api_key_sid? && casa_org.twilio_api_key_secret? + end + + def last_reminder_within_quarter(volunteer) + reminder = UserCaseContactTypesReminder.find_by(user_id: volunteer.id) + + if reminder + return reminder.reminder_sent > 3.months.ago + end + + false + end + + def update_reminder_sent_time(volunteer) + reminder = UserCaseContactTypesReminder.find_by(user_id: volunteer.id) + + if reminder + reminder.reminder_sent = DateTime.now + else + reminder = UserCaseContactTypesReminder.new(user_id: volunteer.id, reminder_sent: DateTime.now) + end + + reminder.save + end + + def new_case_contact_page_short_link + short_url_service = ShortUrlService.new + short_url_service.create_short_url(NEW_CASE_CONTACT_PAGE_PATH + "/case_contacts/new") + short_url_service.short_url + end +end diff --git a/lib/tasks/send_case_contact_types_reminder.rake b/lib/tasks/send_case_contact_types_reminder.rake new file mode 100644 index 0000000000..9f9ec15d3e --- /dev/null +++ b/lib/tasks/send_case_contact_types_reminder.rake @@ -0,0 +1,7 @@ +desc "Send an SMS to volunteers reminding them to connect with the contact types they have not connected with in the past 60 or more days" +require_relative "./case_contact_types_reminder" +task send_case_contact_types_reminder: :environment do + every 1.weeks do + CaseContactTypesReminder.new.send! + end +end diff --git a/spec/factories/user_case_contact_types_reminders.rb b/spec/factories/user_case_contact_types_reminders.rb new file mode 100644 index 0000000000..d528b7e713 --- /dev/null +++ b/spec/factories/user_case_contact_types_reminders.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :user_case_contact_types_reminder do + user { create(:user) } + reminder_sent { DateTime.now } + end +end diff --git a/spec/lib/tasks/case_contact_types_reminder_spec.rb b/spec/lib/tasks/case_contact_types_reminder_spec.rb new file mode 100644 index 0000000000..ba550cbb96 --- /dev/null +++ b/spec/lib/tasks/case_contact_types_reminder_spec.rb @@ -0,0 +1,95 @@ +require "rails_helper" +require_relative "../../../lib/tasks/case_contact_types_reminder" +require "support/webmock_helper" + +RSpec.describe CaseContactTypesReminder do + let!(:casa_org) do + create( + :casa_org, + twilio_phone_number: "+15555555555", + twilio_account_sid: "articuno34", + twilio_api_key_sid: "Aladdin", + twilio_api_key_secret: "open sesame" + ) + end + let!(:volunteer) do + create( + :volunteer, + casa_org_id: casa_org.id, + phone_number: "+12222222222", + receive_sms_notifications: true + ) + end + let!(:contact_type) { create(:contact_type, name: "test") } + let!(:case_contact) do + create( + :case_contact, + creator: volunteer, + contact_types: [contact_type], + occurred_at: 4.months.ago + ) + end + + before do + stubbed_requests + WebMock.disable_net_connect! + end + + context "volunteer with uncontacted contact types, sms notifications on, and no reminder in last quarter" do + it "should send sms reminder" do + responses = CaseContactTypesReminder.new.send! + expect(responses.count).to match 1 + expect(responses[0][:messages][0].body).to match CaseContactTypesReminder::FIRST_MESSAGE + expect(responses[0][:messages][1].body).to match contact_type.name + expect(responses[0][:messages][2].body).to match CaseContactTypesReminder::THIRD_MESSAGE + "https://42ni.short.gy/jzTwdF" + end + end + + context "volunteer with contacted contact types within last 60 days, sms notifications on, and no reminder in last quarter" do + it "should send not sms reminder" do + CaseContact.update_all(occurred_at: 1.months.ago) + responses = CaseContactTypesReminder.new.send! + expect(responses.count).to match 0 + end + end + + context "volunteer with uncontacted contact types, sms notifications off, and no reminder in last quarter" do + it "should not send sms reminder" do + Volunteer.update_all(receive_sms_notifications: false) + responses = CaseContactTypesReminder.new.send! + expect(responses.count).to match 0 + end + end + + context "volunteer with uncontacted contact types, sms notifications on, and reminder in last quarter" do + it "should not send sms reminder" do + create(:user_case_contact_types_reminder, user_id: volunteer.id) + Volunteer.update_all(receive_sms_notifications: true) + responses = CaseContactTypesReminder.new.send! + expect(responses.count).to match 0 + end + end + + context "volunteer with uncontacted contact types, sms notifications on, and reminder out of last quarter" do + it "should send sms reminder" do + UserCaseContactTypesReminder.destroy_all + Volunteer.all do |v| + create(:user_case_contact_types_reminder, user_id: v.id, reminder_sent: 4.months.ago) + end + responses = CaseContactTypesReminder.new.send! + expect(responses.count).to match 1 + expect(responses[0][:messages][0].body).to match CaseContactTypesReminder::FIRST_MESSAGE + expect(responses[0][:messages][1].body).to match contact_type.name + expect(responses[0][:messages][2].body).to match CaseContactTypesReminder::THIRD_MESSAGE + "https://42ni.short.gy/jzTwdF" + end + end + + context "volunteer with uncontacted contact types, sms notifications on, no reminder in last quarter, no phone number" do + it "should not send sms reminder" do + UserCaseContactTypesReminder.destroy_all + Volunteer.update_all(phone_number: nil) + responses = CaseContactTypesReminder.new.send! + expect(responses.count).to match 0 + end + end +end diff --git a/spec/models/user_case_contact_types_reminder_spec.rb b/spec/models/user_case_contact_types_reminder_spec.rb new file mode 100644 index 0000000000..63eff73a3c --- /dev/null +++ b/spec/models/user_case_contact_types_reminder_spec.rb @@ -0,0 +1,5 @@ +require "rails_helper" + +RSpec.describe UserCaseContactTypesReminder, type: :model do + it { is_expected.to belong_to(:user) } +end diff --git a/spec/services/short_url_service_spec.rb b/spec/services/short_url_service_spec.rb index 99fa5eda66..8284982869 100644 --- a/spec/services/short_url_service_spec.rb +++ b/spec/services/short_url_service_spec.rb @@ -2,27 +2,28 @@ require "support/webmock_helper" RSpec.describe ShortUrlService do + let!(:original_url) { "https://www.google.com/" } + let!(:notification_object) { ShortUrlService.new } + let!(:short_io_domain) { Rails.application.credentials[:SHORT_IO_DOMAIN] } + describe "short.io API" do before :each do stubbed_requests WebMock.disable_net_connect! - @original_url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" - @short_domain = "42ni.short.gy" - @notification_object = ShortUrlService.new(@short_domain, "1337") end it "returns a successful response with correct http request" do - response = @notification_object.create_short_url(@original_url) + response = notification_object.create_short_url(original_url) expect(a_request(:post, "https://api.short.io/links") - .with(body: {originalURL: @original_url, domain: @short_domain}.to_json, headers: {"Accept" => "application/json", "Content-Type" => "application/json", "Authorization" => "1337"})) + .with(body: {originalURL: original_url, domain: short_io_domain}.to_json, headers: {"Accept" => "application/json", "Content-Type" => "application/json", "Authorization" => "1337"})) .to have_been_made.once expect(response.code).to match 200 expect(response.body).to match "{\"shortURL\":\"https://42ni.short.gy/jzTwdF\"}" end it "returns a short url" do - @notification_object.create_short_url(@original_url) - short_url = @notification_object.short_url + notification_object.create_short_url(original_url) + short_url = notification_object.short_url expect(short_url).to be_an_instance_of(String) expect(short_url).to match "https://42ni.short.gy/jzTwdF" end diff --git a/spec/services/twilio_service_spec.rb b/spec/services/twilio_service_spec.rb index c13a8e717b..50a689ef8f 100644 --- a/spec/services/twilio_service_spec.rb +++ b/spec/services/twilio_service_spec.rb @@ -4,18 +4,18 @@ RSpec.describe TwilioService do describe "twilio API" do context "SMS messaging" do - before :each do + before :all do stubbed_requests WebMock.disable_net_connect! @acc_sid = "articuno34" @api_key = "Aladdin" @api_secret = "open sesame" - @short_url = ShortUrlService.new("42ni.short.gy", "1337") + @short_url = ShortUrlService.new @twilio = TwilioService.new(@api_key, @api_secret, @acc_sid) end it "can send a SMS with a short url successfully" do - @short_url.create_short_url("https://www.youtube.com/watch?v=dQw4w9WgXcQ") + @short_url.create_short_url("https://www.google.com/") params = { From: "+15555555555", Body: "Execute Order 66 - ", diff --git a/spec/support/webmock_helper.rb b/spec/support/webmock_helper.rb index 55d88cb8a2..cbf27457b8 100644 --- a/spec/support/webmock_helper.rb +++ b/spec/support/webmock_helper.rb @@ -1,16 +1,20 @@ def stubbed_requests - stub_request(:post, "https://api.twilio.com/2010-04-01/Accounts/articuno34/Messages.json") + # Short IO + stub_request(:post, "https://api.short.io/links") .with( - body: {From: "+15555555555", Body: "Execute Order 66 - https://42ni.short.gy/jzTwdF", To: "+12222222222"}, + body: {originalURL: "https://www.google.com/", domain: "42ni.short.gy"}.to_json, headers: { - "Content-Type" => "application/x-www-form-urlencoded", - "Authorization" => "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" + "Accept" => "application/json", + "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + "Authorization" => "1337", + "Content-Type" => "application/json", + "User-Agent" => "Ruby" } ) - .to_return(body: "{\"error_code\":null, \"status\":\"sent\", \"body\":\"Execute Order 66 - https://42ni.short.gy/jzTwdF\"}") + .to_return(status: 200, body: "{\"shortURL\":\"https://42ni.short.gy/jzTwdF\"}", headers: {}) stub_request(:post, "https://api.short.io/links") .with( - body: {originalURL: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", domain: "42ni.short.gy"}.to_json, + body: "{\"originalURL\":\"#{Rails.application.credentials[:BASE_URL]}/case_contacts/new\",\"domain\":\"42ni.short.gy\"}", headers: { "Accept" => "application/json", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", @@ -20,4 +24,48 @@ def stubbed_requests } ) .to_return(status: 200, body: "{\"shortURL\":\"https://42ni.short.gy/jzTwdF\"}", headers: {}) + + # Twilio Service + stub_request(:post, "https://api.twilio.com/2010-04-01/Accounts/articuno34/Messages.json") + .with( + body: {From: "+15555555555", Body: "Execute Order 66 - https://42ni.short.gy/jzTwdF", To: "+12222222222"}, + headers: { + "Content-Type" => "application/x-www-form-urlencoded", + "Authorization" => "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" + } + ) + .to_return(body: "{\"error_code\":null, \"status\":\"sent\", \"body\":\"Execute Order 66 - https://42ni.short.gy/jzTwdF\"}") + stub_request(:post, "https://api.twilio.com/2010-04-01/Accounts/articuno34/Messages.json") + .with( + body: {"Body" => "It's been 60 days or more since you've reached out to these members of your youth's network:\n", "From" => "+15555555555", "To" => "+12222222222"}, + headers: { + "Accept" => "application/json", + "Accept-Charset" => "utf-8", + "Authorization" => "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", + "Content-Type" => "application/x-www-form-urlencoded" + } + ) + .to_return(body: "{\"error_code\":null, \"status\":\"sent\", \"body\":\"It's been 60 days or more since you've reached out to these members of your youth's network:\\n\"}") + stub_request(:post, "https://api.twilio.com/2010-04-01/Accounts/articuno34/Messages.json") + .with( + body: {"Body" => "• test", "From" => "+15555555555", "To" => "+12222222222"}, + headers: { + "Accept" => "application/json", + "Accept-Charset" => "utf-8", + "Authorization" => "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", + "Content-Type" => "application/x-www-form-urlencoded" + } + ) + .to_return(body: "{\"error_code\":null, \"status\":\"sent\", \"body\":\"• test\"}") + stub_request(:post, "https://api.twilio.com/2010-04-01/Accounts/articuno34/Messages.json") + .with( + body: {"Body" => "If you have made contact with them in the past 60 days, remember to log it: https://42ni.short.gy/jzTwdF", "From" => "+15555555555", "To" => "+12222222222"}, + headers: { + "Accept" => "application/json", + "Accept-Charset" => "utf-8", + "Authorization" => "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", + "Content-Type" => "application/x-www-form-urlencoded" + } + ) + .to_return(body: "{\"error_code\":null, \"status\":\"sent\", \"body\":\"If you have made contact with them in the past 60 days, remember to log it: https://42ni.short.gy/jzTwdF\"}") end