Browse Source

Testing: Add coverage for CommunicateTwitter::BackgroundJob

Ryan Lue 5 years ago
parent
commit
284e504bcd
10 changed files with 990 additions and 0 deletions
  1. 238 0
      spec/models/observer/ticket/article/communicate_twitter/background_job_spec.rb
  2. 84 0
      test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/and_another_suitable_channel_exists_matching_on_ticket_preferences_channel_screen_name_uses_that_channel.yml
  3. 83 0
      test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_dms_dispatches_the_dm.yml
  4. 83 0
      test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_dms_increments_the_delivery_retry_preference.yml
  5. 83 0
      test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_dms_sets_the_appropriate_delivery_status_attributes.yml
  6. 83 0
      test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_dms_updates_the_article_with_dm_attributes.yml
  7. 84 0
      test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_dispatches_the_tweet.yml
  8. 84 0
      test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_increments_the_delivery_retry_preference.yml
  9. 84 0
      test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_sets_the_appropriate_delivery_status_attributes.yml
  10. 84 0
      test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_sets_the_appropriate_delivery_status_message.yml

+ 238 - 0
spec/models/observer/ticket/article/communicate_twitter/background_job_spec.rb

@@ -0,0 +1,238 @@
+require 'rails_helper'
+
+RSpec.describe Observer::Ticket::Article::CommunicateTwitter::BackgroundJob, type: :job do
+  let(:article) { create(:twitter_article, **(try(:factory_options) || {})) }
+
+  describe 'core behavior', :use_vcr do
+    # This job runs automatically whenever an article is created.
+    # We disable this auto-execution so we can invoke it manually in the tests below.
+    around do |example|
+      ActiveRecord::Base.observers.disable('observer::_ticket::_article::_communicate_twitter')
+      example.run
+      ActiveRecord::Base.observers.enable('observer::_ticket::_article::_communicate_twitter')
+    end
+
+    context 'for tweets' do
+      let(:tweet_attributes) do
+        {
+          'mention_ids'         => [],
+          'geo'                 => {},
+          'retweeted'           => false,
+          'possibly_sensitive'  => false,
+          'in_reply_to_user_id' => nil,
+          'place'               => {},
+          'retweet_count'       => 0,
+          'source'              => '<a href="https://zammad.com/" rel="nofollow">zammad</a>',
+          'favorited'           => false,
+          'truncated'           => false,
+        }
+      end
+
+      let(:links_array) do
+        [{
+          url:    'https://twitter.com/_/status/1244937367435108360',
+          target: '_blank',
+          name:   'on Twitter',
+        }]
+      end
+
+      it 'increments the "delivery_retry" preference' do
+        expect { described_class.new(article.id).perform }
+          .to change { article.reload.preferences[:delivery_retry] }.to(1)
+      end
+
+      it 'dispatches the tweet' do
+        described_class.new(article.id).perform
+
+        expect(WebMock)
+          .to have_requested(:post, 'https://api.twitter.com/1.1/statuses/update.json')
+          .with(body: "in_reply_to_status_id&status=#{CGI.escape(article.body)}" )
+      end
+
+      it 'updates the article with tweet attributes' do
+        expect { described_class.new(article.id).perform }
+          .to change { article.reload.message_id }.to('1244937367435108360')
+          .and change { article.reload.preferences[:twitter] }.to(hash_including(tweet_attributes))
+          .and change { article.reload.preferences[:links] }.to(links_array)
+      end
+
+      it 'sets the appropriate delivery status attributes' do
+        expect { described_class.new(article.id).perform }
+          .to change { article.reload.preferences[:delivery_status] }.to('success')
+          .and change { article.reload.preferences[:delivery_status_date] }.to(an_instance_of(ActiveSupport::TimeWithZone))
+          .and not_change { article.reload.preferences[:delivery_status_message] }.from(nil)
+      end
+
+      context 'with a user mention' do
+        let(:factory_options) { { body: '@twitter @twitterlive Don’t mind me, just testing the API' } }
+
+        it 'updates the article with tweet recipients' do
+          expect { described_class.new(article.id).perform }
+            .to change { article.reload.to }.to('@Twitter @TwitterLive')
+        end
+      end
+    end
+
+    context 'for DMs' do
+      let(:article) { create(:twitter_dm_article, :pending_delivery, recipient: recipient, body: 'Please ignore this message.') }
+      let(:recipient) { create(:twitter_authorization, uid: '2688148651', username: 'AirbnbHelp') }
+
+      let(:request_body) do
+        {
+          event: {
+            type:           'message_create',
+            message_create: {
+              target:       { recipient_id: recipient.uid },
+              message_data: { text: article.body }
+            }
+          }
+        }.to_json
+      end
+
+      let(:dm_attributes) do
+        {
+          'recipient_id' => recipient.uid,
+          'sender_id'    => '1205290247124217856',
+        }
+      end
+
+      let(:links_array) do
+        [{
+          url:    "https://twitter.com/messages/#{recipient.uid}-1205290247124217856",
+          target: '_blank',
+          name:   'on Twitter',
+        }]
+      end
+
+      it 'increments the "delivery_retry" preference' do
+        expect { described_class.new(article.id).perform }
+          .to change { article.reload.preferences[:delivery_retry] }.to(1)
+      end
+
+      it 'dispatches the DM' do
+        described_class.new(article.id).perform
+
+        expect(WebMock)
+          .to have_requested(:post, 'https://api.twitter.com/1.1/direct_messages/events/new.json')
+          .with(body: request_body)
+      end
+
+      it 'updates the article with DM attributes' do
+        expect { described_class.new(article.id).perform }
+          .to change { article.reload.message_id }.to('1244953398509617156')
+          .and change { article.reload.preferences[:twitter] }.to(hash_including(dm_attributes))
+          .and change { article.reload.preferences[:links] }.to(links_array)
+      end
+
+      it 'sets the appropriate delivery status attributes' do
+        expect { described_class.new(article.id).perform }
+          .to change { article.reload.preferences[:delivery_status] }.to('success')
+          .and change { article.reload.preferences[:delivery_status_date] }.to(an_instance_of(ActiveSupport::TimeWithZone))
+          .and not_change { article.reload.preferences[:delivery_status_message] }.from(nil)
+      end
+    end
+
+    describe 'failure cases' do
+      shared_examples 'for failure cases' do
+        it 'raises an error and sets the appropriate delivery status messages' do
+          expect { described_class.new(article.id).perform }
+            .to raise_error(error_message)
+            .and change { article.reload.preferences[:delivery_status] }.to('fail')
+            .and change { article.reload.preferences[:delivery_status_date] }.to(an_instance_of(ActiveSupport::TimeWithZone))
+            .and change { article.reload.preferences[:delivery_status_message] }.to(error_message)
+        end
+      end
+
+      context 'when article.ticket.preferences["channel_id"] is nil' do
+        before do
+          article.ticket.preferences.delete(:channel_id)
+          article.ticket.save
+        end
+
+        let(:error_message) { "Can't find ticket.preferences['channel_id'] for Ticket.find(#{article.ticket_id})" }
+
+        include_examples 'for failure cases'
+      end
+
+      context 'if article.ticket.preferences["channel_id"] has been removed' do
+        before { channel.destroy }
+
+        let(:channel) { Channel.find(article.ticket.preferences[:channel_id]) }
+        let(:error_message) { "No such channel id #{article.ticket.preferences['channel_id']}" }
+
+        include_examples 'for failure cases'
+
+        context 'and another suitable channel exists (matching on ticket.preferences[:channel_screen_name])' do
+          let!(:new_channel) { create(:twitter_channel, custom_options: { user: { screen_name: channel.options[:user][:screen_name] } }) }
+
+          it 'uses that channel' do
+            described_class.new(article.id).perform
+
+            expect(WebMock)
+              .to have_requested(:post, 'https://api.twitter.com/1.1/statuses/update.json')
+              .with(body: "in_reply_to_status_id&status=#{CGI.escape(article.body)}" )
+          end
+        end
+      end
+
+      context 'if article.ticket.preferences["channel_id"] isn’t actually a twitter channel' do
+        before do
+          article.ticket.preferences[:channel_id] = create(:email_channel).id
+          article.ticket.save
+        end
+
+        let(:error_message) { "Channel.find(#{article.ticket.preferences[:channel_id]}) isn't a twitter channel!" }
+
+        include_examples 'for failure cases'
+      end
+
+      context 'when tweet dispatch fails (e.g., due to authentication error)' do
+        before do
+          article.ticket.preferences[:channel_id] = create(:twitter_channel, :invalid).id
+          article.ticket.save
+        end
+
+        let(:error_message) { "Can't use Channel::Driver::Twitter: #<Twitter::Error::Unauthorized: Invalid or expired token.>" }
+
+        include_examples 'for failure cases'
+      end
+
+      context 'when tweet comes back nil' do
+        before do
+          allow(Twitter::REST::Client).to receive(:new).with(any_args).and_return(client_double)
+          allow(client_double).to receive(:update).with(any_args).and_return(nil)
+        end
+
+        let(:client_double) { double('Twitter::REST::Client') }
+        let(:error_message) { 'Got no tweet!' }
+
+        include_examples 'for failure cases'
+      end
+
+      context 'on the fourth time it fails' do
+        before { Channel.find(article.ticket.preferences[:channel_id]).destroy }
+
+        let(:error_message) { "No such channel id #{article.ticket.preferences['channel_id']}" }
+        let(:factory_options) { { preferences: { delivery_retry: 3 } } }
+
+        it 'adds a delivery failure note (article) to the ticket' do
+          expect { described_class.new(article.id).perform }
+            .to raise_error(error_message)
+            .and change { article.ticket.reload.articles.count }.by(1)
+
+          expect(Ticket::Article.last.attributes).to include(
+            'content_type' => 'text/plain',
+            'body'         => "Unable to send tweet: #{error_message}",
+            'internal'     => true,
+            'sender_id'    => Ticket::Article::Sender.find_by(name: 'System').id,
+            'type_id'      => Ticket::Article::Type.find_by(name: 'note').id,
+            'preferences'  => {
+              'delivery_article_id_related' => article.id,
+              'delivery_message'            => true,
+            },
+          )
+        end
+      end
+    end
+  end
+end

+ 84 - 0
test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/and_another_suitable_channel_exists_matching_on_ticket_preferences_channel_screen_name_uses_that_channel.yml

@@ -0,0 +1,84 @@
+---
+http_interactions:
+- request:
+    method: post
+    uri: https://api.twitter.com/1.1/statuses/update.json
+    body:
+      encoding: UTF-8
+      string: in_reply_to_status_id&status=Consequatur+deserunt+sapiente+rerum.
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="c5c00913ad671a7bc4dd721564fa1ddb",
+        oauth_signature="RAol4gCi%2FtCzZDvdXB%2FMzAsVIBY%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1585658007", oauth_token="REDACTED",
+        oauth_version="1.0"
+      Connection:
+      - close
+      Content-Type:
+      - application/x-www-form-urlencoded
+      Host:
+      - api.twitter.com
+  response:
+    status:
+      code: 200
+      message: OK
+    headers:
+      Cache-Control:
+      - no-cache, no-store, must-revalidate, pre-check=0, post-check=0
+      Connection:
+      - close
+      Content-Disposition:
+      - attachment; filename=json.json
+      Content-Length:
+      - '1893'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Tue, 31 Mar 2020 12:33:27 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Tue, 31 Mar 2020 12:33:27 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158565800758345505; Max-Age=63072000; Expires=Thu, 31 Mar 2022
+        12:33:27 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - lang=en; Path=/
+      - personalization_id="v1_JWzIkRlgfSAhdmWPMCfNQw=="; Max-Age=63072000; Expires=Thu,
+        31 Mar 2022 12:33:27 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      Status:
+      - 200 OK
+      Strict-Transport-Security:
+      - max-age=631138519
+      X-Access-Level:
+      - read-write-directmessages
+      X-Connection-Hash:
+      - 7b19dfd8d697ce9c159a74d199c390d6
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '153'
+      X-Transaction:
+      - '009b8a42003fa36c'
+      X-Tsa-Request-Body-Time:
+      - '0'
+      X-Twitter-Response-Tags:
+      - BouncerCompliant
+      X-Xss-Protection:
+      - '0'
+    body:
+      encoding: UTF-8
+      string: '{"created_at":"Tue Mar 31 12:33:27 +0000 2020","id":1244966034336968704,"id_str":"1244966034336968704","text":"Consequatur
+        deserunt sapiente rerum.","truncated":false,"entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]},"source":"\u003ca
+        href=\"https:\/\/zammad.com\/\" rel=\"nofollow\"\u003ezammad\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":1205290247124217856,"id_str":"1205290247124217856","name":"pennbrooke","screen_name":"pennbrooke1","location":"","description":"","url":null,"entities":{"description":{"urls":[]}},"protected":false,"followers_count":0,"friends_count":1,"listed_count":0,"created_at":"Fri
+        Dec 13 00:56:10 +0000 2019","favourites_count":0,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":false,"statuses_count":25,"lang":null,"contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"F5F8FA","profile_background_image_url":null,"profile_background_image_url_https":null,"profile_background_tile":false,"profile_image_url":"http:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png","profile_image_url_https":"https:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png","profile_link_color":"1DA1F2","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"has_extended_profile":false,"default_profile":true,"default_profile_image":true,"following":false,"follow_request_sent":false,"notifications":false,"translator_type":"none"},"geo":null,"coordinates":null,"place":null,"contributors":null,"is_quote_status":false,"retweet_count":0,"favorite_count":0,"favorited":false,"retweeted":false,"lang":"de"}'
+    http_version: 
+  recorded_at: Tue, 31 Mar 2020 12:33:27 GMT
+recorded_with: VCR 4.0.0

+ 83 - 0
test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_dms_dispatches_the_dm.yml

@@ -0,0 +1,83 @@
+---
+http_interactions:
+- request:
+    method: post
+    uri: https://api.twitter.com/1.1/direct_messages/events/new.json
+    body:
+      encoding: UTF-8
+      string: '{"event":{"type":"message_create","message_create":{"target":{"recipient_id":"2688148651"},"message_data":{"text":"Please
+        ignore this message."}}}}'
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="89f9fb3a4a61083d5a43108631b561fb",
+        oauth_signature="9Kmi04TwHkzb1Qg1kc4UcFaAuzc%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1585654994", oauth_token="REDACTED",
+        oauth_version="1.0"
+      Connection:
+      - close
+      Content-Type:
+      - application/json; charset=UTF-8
+      Host:
+      - api.twitter.com
+  response:
+    status:
+      code: 200
+      message: OK
+    headers:
+      Cache-Control:
+      - no-cache, no-store, must-revalidate, pre-check=0, post-check=0
+      Connection:
+      - close
+      Content-Disposition:
+      - attachment; filename=json.json
+      Content-Length:
+      - '313'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Tue, 31 Mar 2020 11:43:15 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Tue, 31 Mar 2020 11:43:15 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158565499496785911; Max-Age=63072000; Expires=Thu, 31 Mar 2022
+        11:43:15 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - lang=en; Path=/
+      - personalization_id="v1_ixvQSJNpQwDdhsQnnFjMbg=="; Max-Age=63072000; Expires=Thu,
+        31 Mar 2022 11:43:15 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      Status:
+      - 200 OK
+      Strict-Transport-Security:
+      - max-age=631138519
+      X-Access-Level:
+      - read-write-directmessages
+      X-Connection-Hash:
+      - 89771d0fa3e3da3329775711776b1a24
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '215'
+      X-Transaction:
+      - 00e3834000e91bc3
+      X-Tsa-Request-Body-Time:
+      - '0'
+      X-Twitter-Response-Tags:
+      - BouncerCompliant
+      X-Xss-Protection:
+      - '0'
+    body:
+      encoding: UTF-8
+      string: '{"event":{"type":"message_create","id":"1244953398509617156","created_timestamp":"1585654994977","message_create":{"target":{"recipient_id":"2688148651"},"sender_id":"1205290247124217856","message_data":{"text":"Please
+        ignore this message.","entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]}}}}}'
+    http_version: 
+  recorded_at: Tue, 31 Mar 2020 11:43:15 GMT
+recorded_with: VCR 4.0.0

+ 83 - 0
test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_dms_increments_the_delivery_retry_preference.yml

@@ -0,0 +1,83 @@
+---
+http_interactions:
+- request:
+    method: post
+    uri: https://api.twitter.com/1.1/direct_messages/events/new.json
+    body:
+      encoding: UTF-8
+      string: '{"event":{"type":"message_create","message_create":{"target":{"recipient_id":"2688148651"},"message_data":{"text":"Please
+        ignore this message."}}}}'
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="89f9fb3a4a61083d5a43108631b561fb",
+        oauth_signature="9Kmi04TwHkzb1Qg1kc4UcFaAuzc%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1585654994", oauth_token="REDACTED",
+        oauth_version="1.0"
+      Connection:
+      - close
+      Content-Type:
+      - application/json; charset=UTF-8
+      Host:
+      - api.twitter.com
+  response:
+    status:
+      code: 200
+      message: OK
+    headers:
+      Cache-Control:
+      - no-cache, no-store, must-revalidate, pre-check=0, post-check=0
+      Connection:
+      - close
+      Content-Disposition:
+      - attachment; filename=json.json
+      Content-Length:
+      - '313'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Tue, 31 Mar 2020 11:43:15 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Tue, 31 Mar 2020 11:43:15 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158565499496785911; Max-Age=63072000; Expires=Thu, 31 Mar 2022
+        11:43:15 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - lang=en; Path=/
+      - personalization_id="v1_ixvQSJNpQwDdhsQnnFjMbg=="; Max-Age=63072000; Expires=Thu,
+        31 Mar 2022 11:43:15 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      Status:
+      - 200 OK
+      Strict-Transport-Security:
+      - max-age=631138519
+      X-Access-Level:
+      - read-write-directmessages
+      X-Connection-Hash:
+      - 89771d0fa3e3da3329775711776b1a24
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '215'
+      X-Transaction:
+      - 00e3834000e91bc3
+      X-Tsa-Request-Body-Time:
+      - '0'
+      X-Twitter-Response-Tags:
+      - BouncerCompliant
+      X-Xss-Protection:
+      - '0'
+    body:
+      encoding: UTF-8
+      string: '{"event":{"type":"message_create","id":"1244953398509617156","created_timestamp":"1585654994977","message_create":{"target":{"recipient_id":"2688148651"},"sender_id":"1205290247124217856","message_data":{"text":"Please
+        ignore this message.","entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]}}}}}'
+    http_version: 
+  recorded_at: Tue, 31 Mar 2020 11:43:15 GMT
+recorded_with: VCR 4.0.0

+ 83 - 0
test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_dms_sets_the_appropriate_delivery_status_attributes.yml

@@ -0,0 +1,83 @@
+---
+http_interactions:
+- request:
+    method: post
+    uri: https://api.twitter.com/1.1/direct_messages/events/new.json
+    body:
+      encoding: UTF-8
+      string: '{"event":{"type":"message_create","message_create":{"target":{"recipient_id":"2688148651"},"message_data":{"text":"Please
+        ignore this message."}}}}'
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="89f9fb3a4a61083d5a43108631b561fb",
+        oauth_signature="9Kmi04TwHkzb1Qg1kc4UcFaAuzc%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1585654994", oauth_token="REDACTED",
+        oauth_version="1.0"
+      Connection:
+      - close
+      Content-Type:
+      - application/json; charset=UTF-8
+      Host:
+      - api.twitter.com
+  response:
+    status:
+      code: 200
+      message: OK
+    headers:
+      Cache-Control:
+      - no-cache, no-store, must-revalidate, pre-check=0, post-check=0
+      Connection:
+      - close
+      Content-Disposition:
+      - attachment; filename=json.json
+      Content-Length:
+      - '313'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Tue, 31 Mar 2020 11:43:15 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Tue, 31 Mar 2020 11:43:15 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158565499496785911; Max-Age=63072000; Expires=Thu, 31 Mar 2022
+        11:43:15 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - lang=en; Path=/
+      - personalization_id="v1_ixvQSJNpQwDdhsQnnFjMbg=="; Max-Age=63072000; Expires=Thu,
+        31 Mar 2022 11:43:15 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      Status:
+      - 200 OK
+      Strict-Transport-Security:
+      - max-age=631138519
+      X-Access-Level:
+      - read-write-directmessages
+      X-Connection-Hash:
+      - 89771d0fa3e3da3329775711776b1a24
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '215'
+      X-Transaction:
+      - 00e3834000e91bc3
+      X-Tsa-Request-Body-Time:
+      - '0'
+      X-Twitter-Response-Tags:
+      - BouncerCompliant
+      X-Xss-Protection:
+      - '0'
+    body:
+      encoding: UTF-8
+      string: '{"event":{"type":"message_create","id":"1244953398509617156","created_timestamp":"1585654994977","message_create":{"target":{"recipient_id":"2688148651"},"sender_id":"1205290247124217856","message_data":{"text":"Please
+        ignore this message.","entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]}}}}}'
+    http_version: 
+  recorded_at: Tue, 31 Mar 2020 11:43:15 GMT
+recorded_with: VCR 4.0.0

+ 83 - 0
test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_dms_updates_the_article_with_dm_attributes.yml

@@ -0,0 +1,83 @@
+---
+http_interactions:
+- request:
+    method: post
+    uri: https://api.twitter.com/1.1/direct_messages/events/new.json
+    body:
+      encoding: UTF-8
+      string: '{"event":{"type":"message_create","message_create":{"target":{"recipient_id":"2688148651"},"message_data":{"text":"Please
+        ignore this message."}}}}'
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="89f9fb3a4a61083d5a43108631b561fb",
+        oauth_signature="9Kmi04TwHkzb1Qg1kc4UcFaAuzc%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1585654994", oauth_token="REDACTED",
+        oauth_version="1.0"
+      Connection:
+      - close
+      Content-Type:
+      - application/json; charset=UTF-8
+      Host:
+      - api.twitter.com
+  response:
+    status:
+      code: 200
+      message: OK
+    headers:
+      Cache-Control:
+      - no-cache, no-store, must-revalidate, pre-check=0, post-check=0
+      Connection:
+      - close
+      Content-Disposition:
+      - attachment; filename=json.json
+      Content-Length:
+      - '313'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Tue, 31 Mar 2020 11:43:15 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Tue, 31 Mar 2020 11:43:15 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158565499496785911; Max-Age=63072000; Expires=Thu, 31 Mar 2022
+        11:43:15 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - lang=en; Path=/
+      - personalization_id="v1_ixvQSJNpQwDdhsQnnFjMbg=="; Max-Age=63072000; Expires=Thu,
+        31 Mar 2022 11:43:15 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      Status:
+      - 200 OK
+      Strict-Transport-Security:
+      - max-age=631138519
+      X-Access-Level:
+      - read-write-directmessages
+      X-Connection-Hash:
+      - 89771d0fa3e3da3329775711776b1a24
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '215'
+      X-Transaction:
+      - 00e3834000e91bc3
+      X-Tsa-Request-Body-Time:
+      - '0'
+      X-Twitter-Response-Tags:
+      - BouncerCompliant
+      X-Xss-Protection:
+      - '0'
+    body:
+      encoding: UTF-8
+      string: '{"event":{"type":"message_create","id":"1244953398509617156","created_timestamp":"1585654994977","message_create":{"target":{"recipient_id":"2688148651"},"sender_id":"1205290247124217856","message_data":{"text":"Please
+        ignore this message.","entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]}}}}}'
+    http_version: 
+  recorded_at: Tue, 31 Mar 2020 11:43:15 GMT
+recorded_with: VCR 4.0.0

+ 84 - 0
test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_dispatches_the_tweet.yml

@@ -0,0 +1,84 @@
+---
+http_interactions:
+- request:
+    method: post
+    uri: https://api.twitter.com/1.1/statuses/update.json
+    body:
+      encoding: UTF-8
+      string: in_reply_to_status_id&status=Quos+nulla+asperiores+ut.
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="00732df7fa44269cd335acf38d6abe42",
+        oauth_signature="Qna858PdKKMrFjGZEYdm8oU8g6k%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1585651172", oauth_token="REDACTED",
+        oauth_version="1.0"
+      Connection:
+      - close
+      Content-Type:
+      - application/x-www-form-urlencoded
+      Host:
+      - api.twitter.com
+  response:
+    status:
+      code: 200
+      message: OK
+    headers:
+      Cache-Control:
+      - no-cache, no-store, must-revalidate, pre-check=0, post-check=0
+      Connection:
+      - close
+      Content-Disposition:
+      - attachment; filename=json.json
+      Content-Length:
+      - '1882'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Tue, 31 Mar 2020 10:39:32 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Tue, 31 Mar 2020 10:39:32 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158565117286241661; Max-Age=63072000; Expires=Thu, 31 Mar 2022
+        10:39:32 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - lang=en; Path=/
+      - personalization_id="v1_VZPA3OsrdQH41ZancPvXAg=="; Max-Age=63072000; Expires=Thu,
+        31 Mar 2022 10:39:32 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      Status:
+      - 200 OK
+      Strict-Transport-Security:
+      - max-age=631138519
+      X-Access-Level:
+      - read-write-directmessages
+      X-Connection-Hash:
+      - 2d2380f4b7cda58563ac424cc1897507
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '175'
+      X-Transaction:
+      - 0024cb7500e82636
+      X-Tsa-Request-Body-Time:
+      - '0'
+      X-Twitter-Response-Tags:
+      - BouncerCompliant
+      X-Xss-Protection:
+      - '0'
+    body:
+      encoding: UTF-8
+      string: '{"created_at":"Tue Mar 31 10:39:32 +0000 2020","id":1244937367435108360,"id_str":"1244937367435108360","text":"Quos
+        nulla asperiores ut.","truncated":false,"entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]},"source":"\u003ca
+        href=\"https:\/\/zammad.com\/\" rel=\"nofollow\"\u003ezammad\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":1205290247124217856,"id_str":"1205290247124217856","name":"pennbrooke","screen_name":"pennbrooke1","location":"","description":"","url":null,"entities":{"description":{"urls":[]}},"protected":false,"followers_count":0,"friends_count":1,"listed_count":0,"created_at":"Fri
+        Dec 13 00:56:10 +0000 2019","favourites_count":0,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":false,"statuses_count":21,"lang":null,"contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"F5F8FA","profile_background_image_url":null,"profile_background_image_url_https":null,"profile_background_tile":false,"profile_image_url":"http:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png","profile_image_url_https":"https:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png","profile_link_color":"1DA1F2","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"has_extended_profile":false,"default_profile":true,"default_profile_image":true,"following":false,"follow_request_sent":false,"notifications":false,"translator_type":"none"},"geo":null,"coordinates":null,"place":null,"contributors":null,"is_quote_status":false,"retweet_count":0,"favorite_count":0,"favorited":false,"retweeted":false,"lang":"ca"}'
+    http_version: 
+  recorded_at: Tue, 31 Mar 2020 10:39:33 GMT
+recorded_with: VCR 4.0.0

+ 84 - 0
test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_increments_the_delivery_retry_preference.yml

@@ -0,0 +1,84 @@
+---
+http_interactions:
+- request:
+    method: post
+    uri: https://api.twitter.com/1.1/statuses/update.json
+    body:
+      encoding: UTF-8
+      string: in_reply_to_status_id&status=Quos+nulla+asperiores+ut.
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="00732df7fa44269cd335acf38d6abe42",
+        oauth_signature="Qna858PdKKMrFjGZEYdm8oU8g6k%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1585651172", oauth_token="REDACTED",
+        oauth_version="1.0"
+      Connection:
+      - close
+      Content-Type:
+      - application/x-www-form-urlencoded
+      Host:
+      - api.twitter.com
+  response:
+    status:
+      code: 200
+      message: OK
+    headers:
+      Cache-Control:
+      - no-cache, no-store, must-revalidate, pre-check=0, post-check=0
+      Connection:
+      - close
+      Content-Disposition:
+      - attachment; filename=json.json
+      Content-Length:
+      - '1882'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Tue, 31 Mar 2020 10:39:32 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Tue, 31 Mar 2020 10:39:32 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158565117286241661; Max-Age=63072000; Expires=Thu, 31 Mar 2022
+        10:39:32 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - lang=en; Path=/
+      - personalization_id="v1_VZPA3OsrdQH41ZancPvXAg=="; Max-Age=63072000; Expires=Thu,
+        31 Mar 2022 10:39:32 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      Status:
+      - 200 OK
+      Strict-Transport-Security:
+      - max-age=631138519
+      X-Access-Level:
+      - read-write-directmessages
+      X-Connection-Hash:
+      - 2d2380f4b7cda58563ac424cc1897507
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '175'
+      X-Transaction:
+      - 0024cb7500e82636
+      X-Tsa-Request-Body-Time:
+      - '0'
+      X-Twitter-Response-Tags:
+      - BouncerCompliant
+      X-Xss-Protection:
+      - '0'
+    body:
+      encoding: UTF-8
+      string: '{"created_at":"Tue Mar 31 10:39:32 +0000 2020","id":1244937367435108360,"id_str":"1244937367435108360","text":"Quos
+        nulla asperiores ut.","truncated":false,"entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]},"source":"\u003ca
+        href=\"https:\/\/zammad.com\/\" rel=\"nofollow\"\u003ezammad\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":1205290247124217856,"id_str":"1205290247124217856","name":"pennbrooke","screen_name":"pennbrooke1","location":"","description":"","url":null,"entities":{"description":{"urls":[]}},"protected":false,"followers_count":0,"friends_count":1,"listed_count":0,"created_at":"Fri
+        Dec 13 00:56:10 +0000 2019","favourites_count":0,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":false,"statuses_count":21,"lang":null,"contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"F5F8FA","profile_background_image_url":null,"profile_background_image_url_https":null,"profile_background_tile":false,"profile_image_url":"http:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png","profile_image_url_https":"https:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png","profile_link_color":"1DA1F2","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"has_extended_profile":false,"default_profile":true,"default_profile_image":true,"following":false,"follow_request_sent":false,"notifications":false,"translator_type":"none"},"geo":null,"coordinates":null,"place":null,"contributors":null,"is_quote_status":false,"retweet_count":0,"favorite_count":0,"favorited":false,"retweeted":false,"lang":"ca"}'
+    http_version: 
+  recorded_at: Tue, 31 Mar 2020 10:39:33 GMT
+recorded_with: VCR 4.0.0

+ 84 - 0
test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_sets_the_appropriate_delivery_status_attributes.yml

@@ -0,0 +1,84 @@
+---
+http_interactions:
+- request:
+    method: post
+    uri: https://api.twitter.com/1.1/statuses/update.json
+    body:
+      encoding: UTF-8
+      string: in_reply_to_status_id&status=Nobis+consequatur+et+deleniti.
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="ca8e21d95937e7af2b7e1e758342bd3d",
+        oauth_signature="gGxlgbtogX4cd4VS5Dlso8XPNpg%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1585653843", oauth_token="REDACTED",
+        oauth_version="1.0"
+      Connection:
+      - close
+      Content-Type:
+      - application/x-www-form-urlencoded
+      Host:
+      - api.twitter.com
+  response:
+    status:
+      code: 200
+      message: OK
+    headers:
+      Cache-Control:
+      - no-cache, no-store, must-revalidate, pre-check=0, post-check=0
+      Connection:
+      - close
+      Content-Disposition:
+      - attachment; filename=json.json
+      Content-Length:
+      - '1887'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Tue, 31 Mar 2020 11:24:04 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Tue, 31 Mar 2020 11:24:04 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158565384450024079; Max-Age=63072000; Expires=Thu, 31 Mar 2022
+        11:24:04 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - lang=en; Path=/
+      - personalization_id="v1_aMVHuwyntSCutxT7HYN0tg=="; Max-Age=63072000; Expires=Thu,
+        31 Mar 2022 11:24:04 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      Status:
+      - 200 OK
+      Strict-Transport-Security:
+      - max-age=631138519
+      X-Access-Level:
+      - read-write-directmessages
+      X-Connection-Hash:
+      - 457eca514c649e33a27f1274228e3968
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '151'
+      X-Transaction:
+      - '00843cce00439a0a'
+      X-Tsa-Request-Body-Time:
+      - '0'
+      X-Twitter-Response-Tags:
+      - BouncerCompliant
+      X-Xss-Protection:
+      - '0'
+    body:
+      encoding: UTF-8
+      string: '{"created_at":"Tue Mar 31 11:24:04 +0000 2020","id":1244948573092896770,"id_str":"1244948573092896770","text":"Nobis
+        consequatur et deleniti.","truncated":false,"entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]},"source":"\u003ca
+        href=\"https:\/\/zammad.com\/\" rel=\"nofollow\"\u003ezammad\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":1205290247124217856,"id_str":"1205290247124217856","name":"pennbrooke","screen_name":"pennbrooke1","location":"","description":"","url":null,"entities":{"description":{"urls":[]}},"protected":false,"followers_count":0,"friends_count":1,"listed_count":0,"created_at":"Fri
+        Dec 13 00:56:10 +0000 2019","favourites_count":0,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":false,"statuses_count":23,"lang":null,"contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"F5F8FA","profile_background_image_url":null,"profile_background_image_url_https":null,"profile_background_tile":false,"profile_image_url":"http:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png","profile_image_url_https":"https:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png","profile_link_color":"1DA1F2","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"has_extended_profile":false,"default_profile":true,"default_profile_image":true,"following":false,"follow_request_sent":false,"notifications":false,"translator_type":"none"},"geo":null,"coordinates":null,"place":null,"contributors":null,"is_quote_status":false,"retweet_count":0,"favorite_count":0,"favorited":false,"retweeted":false,"lang":"ca"}'
+    http_version: 
+  recorded_at: Tue, 31 Mar 2020 11:24:04 GMT
+recorded_with: VCR 4.0.0

+ 84 - 0
test/data/vcr_cassettes/models/observer/ticket/article/communicate_twitter/background_job/for_tweets_sets_the_appropriate_delivery_status_message.yml

@@ -0,0 +1,84 @@
+---
+http_interactions:
+- request:
+    method: post
+    uri: https://api.twitter.com/1.1/statuses/update.json
+    body:
+      encoding: UTF-8
+      string: in_reply_to_status_id&status=Quos+nulla+asperiores+ut.
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="00732df7fa44269cd335acf38d6abe42",
+        oauth_signature="Qna858PdKKMrFjGZEYdm8oU8g6k%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1585651172", oauth_token="REDACTED",
+        oauth_version="1.0"
+      Connection:
+      - close
+      Content-Type:
+      - application/x-www-form-urlencoded
+      Host:
+      - api.twitter.com
+  response:
+    status:
+      code: 200
+      message: OK
+    headers:
+      Cache-Control:
+      - no-cache, no-store, must-revalidate, pre-check=0, post-check=0
+      Connection:
+      - close
+      Content-Disposition:
+      - attachment; filename=json.json
+      Content-Length:
+      - '1882'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Tue, 31 Mar 2020 10:39:32 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Tue, 31 Mar 2020 10:39:32 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158565117286241661; Max-Age=63072000; Expires=Thu, 31 Mar 2022
+        10:39:32 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - lang=en; Path=/
+      - personalization_id="v1_VZPA3OsrdQH41ZancPvXAg=="; Max-Age=63072000; Expires=Thu,
+        31 Mar 2022 10:39:32 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      Status:
+      - 200 OK
+      Strict-Transport-Security:
+      - max-age=631138519
+      X-Access-Level:
+      - read-write-directmessages
+      X-Connection-Hash:
+      - 2d2380f4b7cda58563ac424cc1897507
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '175'
+      X-Transaction:
+      - 0024cb7500e82636
+      X-Tsa-Request-Body-Time:
+      - '0'
+      X-Twitter-Response-Tags:
+      - BouncerCompliant
+      X-Xss-Protection:
+      - '0'
+    body:
+      encoding: UTF-8
+      string: '{"created_at":"Tue Mar 31 10:39:32 +0000 2020","id":1244937367435108360,"id_str":"1244937367435108360","text":"Quos
+        nulla asperiores ut.","truncated":false,"entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]},"source":"\u003ca
+        href=\"https:\/\/zammad.com\/\" rel=\"nofollow\"\u003ezammad\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":1205290247124217856,"id_str":"1205290247124217856","name":"pennbrooke","screen_name":"pennbrooke1","location":"","description":"","url":null,"entities":{"description":{"urls":[]}},"protected":false,"followers_count":0,"friends_count":1,"listed_count":0,"created_at":"Fri
+        Dec 13 00:56:10 +0000 2019","favourites_count":0,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":false,"statuses_count":21,"lang":null,"contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"F5F8FA","profile_background_image_url":null,"profile_background_image_url_https":null,"profile_background_tile":false,"profile_image_url":"http:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png","profile_image_url_https":"https:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png","profile_link_color":"1DA1F2","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"has_extended_profile":false,"default_profile":true,"default_profile_image":true,"following":false,"follow_request_sent":false,"notifications":false,"translator_type":"none"},"geo":null,"coordinates":null,"place":null,"contributors":null,"is_quote_status":false,"retweet_count":0,"favorite_count":0,"favorited":false,"retweeted":false,"lang":"ca"}'
+    http_version: 
+  recorded_at: Tue, 31 Mar 2020 10:39:33 GMT
+recorded_with: VCR 4.0.0

Some files were not shown because too many files changed in this diff