Browse Source

Testing: Spec Channel::Driver::Twitter#send

This commit is part of a larger effort to overhaul test coverage
for Twitter-related logic throughout the application.

The logic tested in this commit _could_ have been tested
at other interfaces:

* Channel#deliver
  (higher abstraction; calls Channel::Driver::Twitter#send)

* TwitterSync#from_article
  (lower abstraction; is called from Channel::Driver::Twitter#send)

So why did we choose to test at the channel driver level?

Channel#deliver accepts many different adapters
(of which Channel::Driver::Twitter is just one).
Testing Channel#deliver with every possible combination of inputs
would quickly lead to a single, unmanageably large test file
with an explosion of context blocks.
Testing each adapter in isolation seemed the wiser approach.

TwitterSync is a helper class without a cohesive single responsibility,
and thus is strong candidate for refactoring.
It is safer to change its interface than the channel driver's.
Ryan Lue 5 years ago
parent
commit
a6ee25e15e

+ 6 - 6
lib/twitter_sync.rb

@@ -10,12 +10,12 @@ class TwitterSync
   attr_accessor :client
 
   def initialize(auth, payload = nil)
-    @client = Twitter::REST::Client.new do |config|
-      config.consumer_key        = auth[:consumer_key]
-      config.consumer_secret     = auth[:consumer_secret]
-      config.access_token        = auth[:oauth_token] || auth[:access_token]
-      config.access_token_secret = auth[:oauth_token_secret] || auth[:access_token_secret]
-    end
+    @client = Twitter::REST::Client.new(
+      consumer_key:        auth[:consumer_key],
+      consumer_secret:     auth[:consumer_secret],
+      access_token:        auth[:oauth_token] || auth[:access_token],
+      access_token_secret: auth[:oauth_token_secret] || auth[:access_token_secret],
+    )
     @payload = payload
   end
 

+ 5 - 1
spec/factories/ticket/article.rb

@@ -25,6 +25,10 @@ FactoryBot.define do
       association :ticket, factory: :twitter_ticket
       message_id { '775410014383026176' }
       body { Faker::Lorem.sentence }
+
+      trait :reply do
+        in_reply_to  { Faker::Number.number(19) }
+      end
     end
 
     factory :twitter_dm_article do
@@ -41,7 +45,7 @@ FactoryBot.define do
           sender_id { Faker::Number.number(10) }
         end
 
-        from         { User.with_permissions('ticket.agent').first.fullname }
+        from         { ticket.owner.fullname }
         to           { recipient.username }
         in_reply_to  { Faker::Number.number(19) }
         content_type { 'text/plain' }

+ 130 - 0
spec/models/channel/driver/twitter_spec.rb

@@ -3,6 +3,8 @@ require 'rails_helper'
 RSpec.describe Channel::Driver::Twitter do
   subject(:channel) { create(:twitter_channel) }
 
+  let(:external_credential) { ExternalCredential.find(channel.options[:auth][:external_credential_id]) }
+
   describe '#process', current_user_id: 1 do
     # Twitter channels must be configured to know whose account they're monitoring.
     subject(:channel) do
@@ -642,4 +644,132 @@ RSpec.describe Channel::Driver::Twitter do
       end
     end
   end
+
+  describe '#send', :use_vcr do
+    shared_examples 'for #send' do
+      # Channel#deliver takes a hash in the following format
+      # (see Observer::Ticket::Article::CommunicateTwitter::BackgroundJob#perform)
+      #
+      # Why not just accept the whole article?
+      # Presumably so all channels have a consistent interface...
+      # but it might be a good idea to let it accept both one day
+      # (the "robustness principle")
+      let(:delivery_payload) do
+        {
+          type:        outgoing_tweet.type.name,
+          to:          outgoing_tweet.to,
+          body:        outgoing_tweet.body,
+          in_reply_to: outgoing_tweet.in_reply_to
+        }
+      end
+
+      describe 'Import Mode behavior' do
+        before { Setting.set('import_mode', true) }
+
+        it 'is a no-op' do
+          expect(Twitter::REST::Client).not_to receive(:new)
+
+          channel.deliver(delivery_payload)
+        end
+      end
+
+      describe 'Twitter API authentication' do
+        let(:consumer_credentials) do
+          {
+            consumer_key:    external_credential.credentials[:consumer_key],
+            consumer_secret: external_credential.credentials[:consumer_secret],
+          }
+        end
+
+        let(:oauth_credentials) do
+          {
+            access_token:        channel.options[:auth][:oauth_token],
+            access_token_secret: channel.options[:auth][:oauth_token_secret],
+          }
+        end
+
+        it 'uses consumer key/secret stored on ExternalCredential' do
+          expect(Twitter::REST::Client)
+            .to receive(:new).with(hash_including(consumer_credentials))
+            .and_call_original
+
+          channel.deliver(delivery_payload)
+        end
+
+        it 'uses OAuth token/secret stored on #options hash' do
+          expect(Twitter::REST::Client)
+            .to receive(:new).with(hash_including(oauth_credentials))
+            .and_call_original
+
+          channel.deliver(delivery_payload)
+        end
+      end
+
+      describe 'Twitter API activity' do
+        it 'creates a tweet/DM via the API' do
+          channel.deliver(delivery_payload)
+
+          expect(WebMock)
+            .to have_requested(:post, "https://api.twitter.com/1.1#{endpoint}")
+            .with(body: request_body)
+        end
+
+        it 'returns the created tweet/DM' do
+          expect(channel.deliver(delivery_payload)).to match(return_value)
+        end
+      end
+    end
+
+    context 'for tweets' do
+      let!(:outgoing_tweet) { create(:twitter_article) }
+      let(:endpoint) { '/statuses/update.json' }
+      let(:request_body) { <<~BODY.chomp }
+        in_reply_to_status_id&status=#{URI.encode_www_form_component(outgoing_tweet.body)}
+      BODY
+      let(:return_value) { Twitter::Tweet }
+
+      include_examples 'for #send'
+
+      context 'in a thread' do
+        let!(:outgoing_tweet) { create(:twitter_article, :reply) }
+        let(:request_body) { <<~BODY.chomp }
+          in_reply_to_status_id=#{outgoing_tweet.in_reply_to}&status=#{URI.encode_www_form_component(outgoing_tweet.body)}
+        BODY
+
+        it 'creates a tweet via the API' do
+          channel.deliver(delivery_payload)
+
+          expect(WebMock)
+            .to have_requested(:post, "https://api.twitter.com/1.1#{endpoint}")
+            .with(body: request_body)
+        end
+      end
+
+      context 'containing an asterisk (workaround for sferik/twitter #677)' do
+        let!(:outgoing_tweet) { create(:twitter_article, body: 'foo * bar') }
+        let(:request_body) { <<~BODY.chomp }
+          in_reply_to_status_id&status=#{URI.encode_www_form_component('foo * bar')}
+        BODY
+
+        it 'converts it to a full-width asterisk (U+FF0A)' do
+          channel.deliver(delivery_payload)
+
+          expect(WebMock)
+            .to have_requested(:post, "https://api.twitter.com/1.1#{endpoint}")
+            .with(body: request_body)
+        end
+      end
+    end
+
+    context 'for DMs' do
+      let!(:outgoing_tweet) { create(:twitter_dm_article, :pending_delivery) }
+      let(:endpoint) { '/direct_messages/events/new.json' }
+      let(:request_body) { <<~BODY.chomp }
+        {"event":{"type":"message_create","message_create":{"target":{"recipient_id":"#{Authorization.last.uid}"},"message_data":{"text":"#{outgoing_tweet.body}"}}}}
+      BODY
+      let(:return_value) { { event: hash_including(type: 'message_create') } }
+
+      include_examples 'for #send'
+    end
+  end
 end

+ 164 - 0
test/data/vcr_cassettes/models/channel/driver/twitter/containing_an_asterisk_workaround_for_sferik_twitter_677_converts_it_to_a_full-width_asterisk_u_ff0a_.yml

@@ -0,0 +1,164 @@
+---
+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=Et+nesciunt+enim+alias.
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="34ead06d7c61a83c2d41cea2973c473b",
+        oauth_signature="ktmBeBgh80XcnhT9Pzg5%2BI2gaCo%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1582111213", 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:
+      - '1880'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Wed, 19 Feb 2020 11:20:13 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Wed, 19 Feb 2020 11:20:13 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158211121384643145; Max-Age=63072000; Expires=Fri, 18 Feb 2022
+        11:20:13 GMT; Path=/; Domain=.twitter.com; Secure
+      - lang=en; Path=/
+      - personalization_id="v1_W6UfKjXvyitQHGLlRoJ5zg=="; Max-Age=63072000; Expires=Fri,
+        18 Feb 2022 11:20:13 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:
+      - 42bb4b1416218bfac966beab44d963de
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '150'
+      X-Transaction:
+      - 00bdc79d00975228
+      X-Tsa-Request-Body-Time:
+      - '0'
+      X-Twitter-Response-Tags:
+      - BouncerCompliant
+      X-Xss-Protection:
+      - '0'
+    body:
+      encoding: UTF-8
+      string: '{"created_at":"Wed Feb 19 11:20:13 +0000 2020","id":1230089703166111745,"id_str":"1230089703166111745","text":"Et
+        nesciunt enim alias.","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":13,"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":"lt"}'
+    http_version: 
+  recorded_at: Wed, 19 Feb 2020 11:20:14 GMT
+- 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":"2975699229"},"message_data":{"text":"Dolorum
+        laborum laudantium nesciunt."}}}}'
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="fe989117ad21662a1fa1fd6898978618",
+        oauth_signature="lReUHWuMgwhrfyPY5NfAWZ1O6Zw%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1582259890", 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:
+      - '322'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Fri, 21 Feb 2020 04:38:12 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Fri, 21 Feb 2020 04:38:12 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158225989196619362; Max-Age=63072000; Expires=Sun, 20 Feb 2022
+        04:38:12 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - lang=en; Path=/
+      - personalization_id="v1_JOd5RPrYJ4Dq6miDjhBZ2A=="; Max-Age=63072000; Expires=Sun,
+        20 Feb 2022 04:38:12 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:
+      - 6c7b9217e2dc6750af571adafadd9101
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '170'
+      X-Transaction:
+      - 00ba8b0b002cb5b6
+      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":"1230713304420454405","created_timestamp":"1582259891978","message_create":{"target":{"recipient_id":"2975699229"},"sender_id":"1205290247124217856","message_data":{"text":"Dolorum
+        laborum laudantium nesciunt.","entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]}}}}}'
+    http_version: 
+  recorded_at: Fri, 21 Feb 2020 04:38:12 GMT
+recorded_with: VCR 4.0.0

+ 164 - 0
test/data/vcr_cassettes/models/channel/driver/twitter/in_a_thread_creates_a_tweet_via_the_api.yml

@@ -0,0 +1,164 @@
+---
+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=Et+nesciunt+enim+alias.
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="34ead06d7c61a83c2d41cea2973c473b",
+        oauth_signature="ktmBeBgh80XcnhT9Pzg5%2BI2gaCo%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1582111213", 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:
+      - '1880'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Wed, 19 Feb 2020 11:20:13 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Wed, 19 Feb 2020 11:20:13 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158211121384643145; Max-Age=63072000; Expires=Fri, 18 Feb 2022
+        11:20:13 GMT; Path=/; Domain=.twitter.com; Secure
+      - lang=en; Path=/
+      - personalization_id="v1_W6UfKjXvyitQHGLlRoJ5zg=="; Max-Age=63072000; Expires=Fri,
+        18 Feb 2022 11:20:13 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:
+      - 42bb4b1416218bfac966beab44d963de
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '150'
+      X-Transaction:
+      - 00bdc79d00975228
+      X-Tsa-Request-Body-Time:
+      - '0'
+      X-Twitter-Response-Tags:
+      - BouncerCompliant
+      X-Xss-Protection:
+      - '0'
+    body:
+      encoding: UTF-8
+      string: '{"created_at":"Wed Feb 19 11:20:13 +0000 2020","id":1230089703166111745,"id_str":"1230089703166111745","text":"Et
+        nesciunt enim alias.","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":13,"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":"lt"}'
+    http_version: 
+  recorded_at: Wed, 19 Feb 2020 11:20:14 GMT
+- 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":"2975699229"},"message_data":{"text":"Dolorum
+        laborum laudantium nesciunt."}}}}'
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="fe989117ad21662a1fa1fd6898978618",
+        oauth_signature="lReUHWuMgwhrfyPY5NfAWZ1O6Zw%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1582259890", 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:
+      - '322'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Fri, 21 Feb 2020 04:38:12 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Fri, 21 Feb 2020 04:38:12 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158225989196619362; Max-Age=63072000; Expires=Sun, 20 Feb 2022
+        04:38:12 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - lang=en; Path=/
+      - personalization_id="v1_JOd5RPrYJ4Dq6miDjhBZ2A=="; Max-Age=63072000; Expires=Sun,
+        20 Feb 2022 04:38:12 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:
+      - 6c7b9217e2dc6750af571adafadd9101
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '170'
+      X-Transaction:
+      - 00ba8b0b002cb5b6
+      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":"1230713304420454405","created_timestamp":"1582259891978","message_create":{"target":{"recipient_id":"2975699229"},"sender_id":"1205290247124217856","message_data":{"text":"Dolorum
+        laborum laudantium nesciunt.","entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]}}}}}'
+    http_version: 
+  recorded_at: Fri, 21 Feb 2020 04:38:12 GMT
+recorded_with: VCR 4.0.0

+ 164 - 0
test/data/vcr_cassettes/models/channel/driver/twitter/twitter_api_activity_creates_a_tweet_dm_via_the_api.yml

@@ -0,0 +1,164 @@
+---
+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=Et+nesciunt+enim+alias.
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="34ead06d7c61a83c2d41cea2973c473b",
+        oauth_signature="ktmBeBgh80XcnhT9Pzg5%2BI2gaCo%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1582111213", 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:
+      - '1880'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Wed, 19 Feb 2020 11:20:13 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Wed, 19 Feb 2020 11:20:13 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158211121384643145; Max-Age=63072000; Expires=Fri, 18 Feb 2022
+        11:20:13 GMT; Path=/; Domain=.twitter.com; Secure
+      - lang=en; Path=/
+      - personalization_id="v1_W6UfKjXvyitQHGLlRoJ5zg=="; Max-Age=63072000; Expires=Fri,
+        18 Feb 2022 11:20:13 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:
+      - 42bb4b1416218bfac966beab44d963de
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '150'
+      X-Transaction:
+      - 00bdc79d00975228
+      X-Tsa-Request-Body-Time:
+      - '0'
+      X-Twitter-Response-Tags:
+      - BouncerCompliant
+      X-Xss-Protection:
+      - '0'
+    body:
+      encoding: UTF-8
+      string: '{"created_at":"Wed Feb 19 11:20:13 +0000 2020","id":1230089703166111745,"id_str":"1230089703166111745","text":"Et
+        nesciunt enim alias.","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":13,"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":"lt"}'
+    http_version: 
+  recorded_at: Wed, 19 Feb 2020 11:20:14 GMT
+- 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":"2975699229"},"message_data":{"text":"Dolorum
+        laborum laudantium nesciunt."}}}}'
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="fe989117ad21662a1fa1fd6898978618",
+        oauth_signature="lReUHWuMgwhrfyPY5NfAWZ1O6Zw%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1582259890", 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:
+      - '322'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Fri, 21 Feb 2020 04:38:12 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Fri, 21 Feb 2020 04:38:12 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158225989196619362; Max-Age=63072000; Expires=Sun, 20 Feb 2022
+        04:38:12 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - lang=en; Path=/
+      - personalization_id="v1_JOd5RPrYJ4Dq6miDjhBZ2A=="; Max-Age=63072000; Expires=Sun,
+        20 Feb 2022 04:38:12 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:
+      - 6c7b9217e2dc6750af571adafadd9101
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '170'
+      X-Transaction:
+      - 00ba8b0b002cb5b6
+      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":"1230713304420454405","created_timestamp":"1582259891978","message_create":{"target":{"recipient_id":"2975699229"},"sender_id":"1205290247124217856","message_data":{"text":"Dolorum
+        laborum laudantium nesciunt.","entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]}}}}}'
+    http_version: 
+  recorded_at: Fri, 21 Feb 2020 04:38:12 GMT
+recorded_with: VCR 4.0.0

+ 164 - 0
test/data/vcr_cassettes/models/channel/driver/twitter/twitter_api_activity_returns_the_created_tweet_dm.yml

@@ -0,0 +1,164 @@
+---
+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=Et+nesciunt+enim+alias.
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="34ead06d7c61a83c2d41cea2973c473b",
+        oauth_signature="ktmBeBgh80XcnhT9Pzg5%2BI2gaCo%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1582111213", 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:
+      - '1880'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Wed, 19 Feb 2020 11:20:13 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Wed, 19 Feb 2020 11:20:13 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158211121384643145; Max-Age=63072000; Expires=Fri, 18 Feb 2022
+        11:20:13 GMT; Path=/; Domain=.twitter.com; Secure
+      - lang=en; Path=/
+      - personalization_id="v1_W6UfKjXvyitQHGLlRoJ5zg=="; Max-Age=63072000; Expires=Fri,
+        18 Feb 2022 11:20:13 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:
+      - 42bb4b1416218bfac966beab44d963de
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '150'
+      X-Transaction:
+      - 00bdc79d00975228
+      X-Tsa-Request-Body-Time:
+      - '0'
+      X-Twitter-Response-Tags:
+      - BouncerCompliant
+      X-Xss-Protection:
+      - '0'
+    body:
+      encoding: UTF-8
+      string: '{"created_at":"Wed Feb 19 11:20:13 +0000 2020","id":1230089703166111745,"id_str":"1230089703166111745","text":"Et
+        nesciunt enim alias.","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":13,"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":"lt"}'
+    http_version: 
+  recorded_at: Wed, 19 Feb 2020 11:20:14 GMT
+- 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":"2975699229"},"message_data":{"text":"Dolorum
+        laborum laudantium nesciunt."}}}}'
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="fe989117ad21662a1fa1fd6898978618",
+        oauth_signature="lReUHWuMgwhrfyPY5NfAWZ1O6Zw%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1582259890", 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:
+      - '322'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Fri, 21 Feb 2020 04:38:12 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Fri, 21 Feb 2020 04:38:12 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158225989196619362; Max-Age=63072000; Expires=Sun, 20 Feb 2022
+        04:38:12 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - lang=en; Path=/
+      - personalization_id="v1_JOd5RPrYJ4Dq6miDjhBZ2A=="; Max-Age=63072000; Expires=Sun,
+        20 Feb 2022 04:38:12 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:
+      - 6c7b9217e2dc6750af571adafadd9101
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '170'
+      X-Transaction:
+      - 00ba8b0b002cb5b6
+      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":"1230713304420454405","created_timestamp":"1582259891978","message_create":{"target":{"recipient_id":"2975699229"},"sender_id":"1205290247124217856","message_data":{"text":"Dolorum
+        laborum laudantium nesciunt.","entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]}}}}}'
+    http_version: 
+  recorded_at: Fri, 21 Feb 2020 04:38:12 GMT
+recorded_with: VCR 4.0.0

+ 164 - 0
test/data/vcr_cassettes/models/channel/driver/twitter/twitter_api_authentication_uses_consumer_key_secret_stored_on_externalcredential.yml

@@ -0,0 +1,164 @@
+---
+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=Et+nesciunt+enim+alias.
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="34ead06d7c61a83c2d41cea2973c473b",
+        oauth_signature="ktmBeBgh80XcnhT9Pzg5%2BI2gaCo%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1582111213", 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:
+      - '1880'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Wed, 19 Feb 2020 11:20:13 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Wed, 19 Feb 2020 11:20:13 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158211121384643145; Max-Age=63072000; Expires=Fri, 18 Feb 2022
+        11:20:13 GMT; Path=/; Domain=.twitter.com; Secure
+      - lang=en; Path=/
+      - personalization_id="v1_W6UfKjXvyitQHGLlRoJ5zg=="; Max-Age=63072000; Expires=Fri,
+        18 Feb 2022 11:20:13 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:
+      - 42bb4b1416218bfac966beab44d963de
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '150'
+      X-Transaction:
+      - 00bdc79d00975228
+      X-Tsa-Request-Body-Time:
+      - '0'
+      X-Twitter-Response-Tags:
+      - BouncerCompliant
+      X-Xss-Protection:
+      - '0'
+    body:
+      encoding: UTF-8
+      string: '{"created_at":"Wed Feb 19 11:20:13 +0000 2020","id":1230089703166111745,"id_str":"1230089703166111745","text":"Et
+        nesciunt enim alias.","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":13,"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":"lt"}'
+    http_version: 
+  recorded_at: Wed, 19 Feb 2020 11:20:14 GMT
+- 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":"2975699229"},"message_data":{"text":"Dolorum
+        laborum laudantium nesciunt."}}}}'
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="fe989117ad21662a1fa1fd6898978618",
+        oauth_signature="lReUHWuMgwhrfyPY5NfAWZ1O6Zw%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1582259890", 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:
+      - '322'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Fri, 21 Feb 2020 04:38:12 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Fri, 21 Feb 2020 04:38:12 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158225989196619362; Max-Age=63072000; Expires=Sun, 20 Feb 2022
+        04:38:12 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - lang=en; Path=/
+      - personalization_id="v1_JOd5RPrYJ4Dq6miDjhBZ2A=="; Max-Age=63072000; Expires=Sun,
+        20 Feb 2022 04:38:12 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:
+      - 6c7b9217e2dc6750af571adafadd9101
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '170'
+      X-Transaction:
+      - 00ba8b0b002cb5b6
+      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":"1230713304420454405","created_timestamp":"1582259891978","message_create":{"target":{"recipient_id":"2975699229"},"sender_id":"1205290247124217856","message_data":{"text":"Dolorum
+        laborum laudantium nesciunt.","entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]}}}}}'
+    http_version: 
+  recorded_at: Fri, 21 Feb 2020 04:38:12 GMT
+recorded_with: VCR 4.0.0

+ 164 - 0
test/data/vcr_cassettes/models/channel/driver/twitter/twitter_api_authentication_uses_oauth_token_secret_stored_on_options_hash.yml

@@ -0,0 +1,164 @@
+---
+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=Et+nesciunt+enim+alias.
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="34ead06d7c61a83c2d41cea2973c473b",
+        oauth_signature="ktmBeBgh80XcnhT9Pzg5%2BI2gaCo%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1582111213", 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:
+      - '1880'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Wed, 19 Feb 2020 11:20:13 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Wed, 19 Feb 2020 11:20:13 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158211121384643145; Max-Age=63072000; Expires=Fri, 18 Feb 2022
+        11:20:13 GMT; Path=/; Domain=.twitter.com; Secure
+      - lang=en; Path=/
+      - personalization_id="v1_W6UfKjXvyitQHGLlRoJ5zg=="; Max-Age=63072000; Expires=Fri,
+        18 Feb 2022 11:20:13 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:
+      - 42bb4b1416218bfac966beab44d963de
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '150'
+      X-Transaction:
+      - 00bdc79d00975228
+      X-Tsa-Request-Body-Time:
+      - '0'
+      X-Twitter-Response-Tags:
+      - BouncerCompliant
+      X-Xss-Protection:
+      - '0'
+    body:
+      encoding: UTF-8
+      string: '{"created_at":"Wed Feb 19 11:20:13 +0000 2020","id":1230089703166111745,"id_str":"1230089703166111745","text":"Et
+        nesciunt enim alias.","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":13,"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":"lt"}'
+    http_version: 
+  recorded_at: Wed, 19 Feb 2020 11:20:14 GMT
+- 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":"2975699229"},"message_data":{"text":"Dolorum
+        laborum laudantium nesciunt."}}}}'
+    headers:
+      User-Agent:
+      - TwitterRubyGem/6.2.0
+      Authorization:
+      - OAuth oauth_consumer_key="REDACTED", oauth_nonce="fe989117ad21662a1fa1fd6898978618",
+        oauth_signature="lReUHWuMgwhrfyPY5NfAWZ1O6Zw%3D", oauth_signature_method="HMAC-SHA1",
+        oauth_timestamp="1582259890", 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:
+      - '322'
+      Content-Type:
+      - application/json;charset=utf-8
+      Date:
+      - Fri, 21 Feb 2020 04:38:12 GMT
+      Expires:
+      - Tue, 31 Mar 1981 05:00:00 GMT
+      Last-Modified:
+      - Fri, 21 Feb 2020 04:38:12 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - tsa_m
+      Set-Cookie:
+      - guest_id=v1%3A158225989196619362; Max-Age=63072000; Expires=Sun, 20 Feb 2022
+        04:38:12 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
+      - lang=en; Path=/
+      - personalization_id="v1_JOd5RPrYJ4Dq6miDjhBZ2A=="; Max-Age=63072000; Expires=Sun,
+        20 Feb 2022 04:38:12 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:
+      - 6c7b9217e2dc6750af571adafadd9101
+      X-Content-Type-Options:
+      - nosniff
+      X-Frame-Options:
+      - SAMEORIGIN
+      X-Response-Time:
+      - '170'
+      X-Transaction:
+      - 00ba8b0b002cb5b6
+      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":"1230713304420454405","created_timestamp":"1582259891978","message_create":{"target":{"recipient_id":"2975699229"},"sender_id":"1205290247124217856","message_data":{"text":"Dolorum
+        laborum laudantium nesciunt.","entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]}}}}}'
+    http_version: 
+  recorded_at: Fri, 21 Feb 2020 04:38:12 GMT
+recorded_with: VCR 4.0.0