From 64c882b090941d79152e370a2612ef534027d23f Mon Sep 17 00:00:00 2001 From: Ryan Lue Date: Wed, 18 Dec 2019 14:39:45 +0800 Subject: [PATCH] Refactoring: Add coverage for TwitterSync#process_webhook Prior to this commit, the TwitterSync#process_webhook method had only partial test coverage (in `spec/requests/integration/twitter_webhook_spec.rb`). This commit replaces the existing, incomplete tests with a direct spec of the TwitterSync class, giving individual test cases for all reasonable edge cases of the #process_webhook method. Since this method is responsible for a lot of complex behavior, there are a _lot_ of such test cases. Putting them all in the same spec file makes it hard to read through, so a future goal is to break them up into smaller methods and move some of these methods into other, more appropriate classes. This could not be achieved with a request spec, since they enforce testing at a single interface / level of abstraction (i.e., HTTP requests). === Note: This commit will not pass CI. Some bugs in the existing code were uncovered in the process of adding comprehensive test coverage for the #process_webhook method. In the interest of isolating changes into discrete chunks, fixes for these bugs have been reserved for the next commit. --- spec/lib/twitter_sync_spec.rb | 661 ++++++++++++++++++ .../integration/twitter_webhook_spec.rb | 312 --------- spec/support/negated_matchers.rb | 1 + .../data/twitter/webhook1_direct_message.json | 70 -- test/data/twitter/webhook1_tweet.json | 167 ----- .../data/twitter/webhook2_direct_message.json | 118 ---- test/data/twitter/webhook2_tweet.json | 115 --- .../data/twitter/webhook3_direct_message.json | 62 -- .../direct_message-incoming.yml | 51 ++ .../direct_message-incoming_2.yml | 51 ++ .../direct_message-incoming_with_media.yml | 98 +++ .../direct_message-incoming_with_url.yml | 58 ++ .../direct_message-outgoing.yml | 57 ++ .../direct_message_indicate_typing.yml | 41 ++ .../direct_message_mark_read.yml | 42 ++ .../data/twitter/webhook_events/favorited.yml | 127 ++++ .../webhook_events/follow-unfollow.yml | 82 +++ test/data/twitter/webhook_events/follow.yml | 82 +++ .../webhook_events/tweet_create-outgoing.yml | 76 ++ .../webhook_events/tweet_create-response.yml | 87 +++ .../webhook_events/tweet_create-retweet.yml | 171 +++++ .../tweet_create-user_mention.yml | 84 +++ .../tweet_create-user_mention_extended.yml | 113 +++ .../tweet_create-user_mention_multiple.yml | 92 +++ .../tweet_create-user_mention_outgoing.yml | 85 +++ .../tweet_create-user_mention_with_media.yml | 158 +++++ .../tweet_create-user_mention_with_url.yml | 92 +++ ..._has_been_deleted_creates_a_new_ticket.yml | 247 +++++++ ...res_error_when_retrieving_parent_tweet.yml | 247 +++++++ ...es_the_sender_s_existing_avatar_record.yml | 169 +++++ ...reates_an_avatar_record_for_the_sender.yml | 169 +++++ ...been_imported_yet_creates_a_new_ticket.yml | 307 ++++++++ ...s_the_parent_tweet_via_the_twitter_api.yml | 307 ++++++++ ...res_it_as_an_attachment_on_the_article.yml | 117 ++++ 34 files changed, 3872 insertions(+), 844 deletions(-) delete mode 100644 spec/requests/integration/twitter_webhook_spec.rb delete mode 100644 test/data/twitter/webhook1_direct_message.json delete mode 100644 test/data/twitter/webhook1_tweet.json delete mode 100644 test/data/twitter/webhook2_direct_message.json delete mode 100644 test/data/twitter/webhook2_tweet.json delete mode 100644 test/data/twitter/webhook3_direct_message.json create mode 100644 test/data/twitter/webhook_events/direct_message-incoming.yml create mode 100644 test/data/twitter/webhook_events/direct_message-incoming_2.yml create mode 100644 test/data/twitter/webhook_events/direct_message-incoming_with_media.yml create mode 100644 test/data/twitter/webhook_events/direct_message-incoming_with_url.yml create mode 100644 test/data/twitter/webhook_events/direct_message-outgoing.yml create mode 100644 test/data/twitter/webhook_events/direct_message_indicate_typing.yml create mode 100644 test/data/twitter/webhook_events/direct_message_mark_read.yml create mode 100644 test/data/twitter/webhook_events/favorited.yml create mode 100644 test/data/twitter/webhook_events/follow-unfollow.yml create mode 100644 test/data/twitter/webhook_events/follow.yml create mode 100644 test/data/twitter/webhook_events/tweet_create-outgoing.yml create mode 100644 test/data/twitter/webhook_events/tweet_create-response.yml create mode 100644 test/data/twitter/webhook_events/tweet_create-retweet.yml create mode 100644 test/data/twitter/webhook_events/tweet_create-user_mention.yml create mode 100644 test/data/twitter/webhook_events/tweet_create-user_mention_extended.yml create mode 100644 test/data/twitter/webhook_events/tweet_create-user_mention_multiple.yml create mode 100644 test/data/twitter/webhook_events/tweet_create-user_mention_outgoing.yml create mode 100644 test/data/twitter/webhook_events/tweet_create-user_mention_with_media.yml create mode 100644 test/data/twitter/webhook_events/tweet_create-user_mention_with_url.yml create mode 100644 test/data/vcr_cassettes/lib/twitter_sync/after_parent_tweet_has_been_deleted_creates_a_new_ticket.yml create mode 100644 test/data/vcr_cassettes/lib/twitter_sync/after_parent_tweet_has_been_deleted_silently_ignores_error_when_retrieving_parent_tweet.yml create mode 100644 test/data/vcr_cassettes/lib/twitter_sync/from_known_user_updates_the_sender_s_existing_avatar_record.yml create mode 100644 test/data/vcr_cassettes/lib/twitter_sync/from_unknown_user_creates_an_avatar_record_for_the_sender.yml create mode 100644 test/data/vcr_cassettes/lib/twitter_sync/that_hasn_t_been_imported_yet_creates_a_new_ticket.yml create mode 100644 test/data/vcr_cassettes/lib/twitter_sync/that_hasn_t_been_imported_yet_retrieves_the_parent_tweet_via_the_twitter_api.yml create mode 100644 test/data/vcr_cassettes/lib/twitter_sync/when_message_contains_a_media_attachment_e_g__jpg_stores_it_as_an_attachment_on_the_article.yml diff --git a/spec/lib/twitter_sync_spec.rb b/spec/lib/twitter_sync_spec.rb index c3e5c5c71..cf85eaaeb 100644 --- a/spec/lib/twitter_sync_spec.rb +++ b/spec/lib/twitter_sync_spec.rb @@ -1,6 +1,10 @@ require 'rails_helper' RSpec.describe TwitterSync do + subject(:twitter_sync) { described_class.new(channel.options[:auth], payload) } + + let(:channel) { create(:twitter_channel) } + describe '.preferences_cleanup' do shared_examples 'for normalizing input' do it 'is converted (from bare hash)' do @@ -65,4 +69,661 @@ RSpec.describe TwitterSync do include_examples 'for normalizing input' end end + + describe '#process_webhook' do + before do + # TODO: This is necessary to implicitly set #created_by_id and #updated_by_id on new records. + # It is usually performed by the ApplicationController::HasUser#set_user filter, + # but since we are testing this class in isolation of the controller, + # it has to be done manually here. + # + # Consider putting this in the method itself, rather than in a spec `before` hook. + UserInfo.current_user_id = 1 + + # Twitter channels must be configured to know whose account they're monitoring. + channel.options[:user][:id] = payload[:for_user_id] + channel.save! + end + + let(:payload) { YAML.safe_load(File.read(payload_file), [ActiveSupport::HashWithIndifferentAccess]) } + + # TODO: This aspect of #process_webhook's behavior involves deep interaction + # with the User, Avatar, and Authorization classes, + # and thus should really be refactored (moved) elsewhere. + # These specs are being written to support such a refactoring, + # and should be migrated as appropriate when the logic is eventually relocated. + shared_examples 'for user processing' do + let(:sender_attributes) do + { + 'login' => sender_profile[:screen_name], + 'firstname' => sender_profile[:name].capitalize, + 'web' => sender_profile[:url], + 'note' => sender_profile[:description], + 'address' => sender_profile[:location], + 'image_source' => sender_profile[:profile_image_url], + } + end + + let(:avatar_attributes) do + { + 'object_lookup_id' => ObjectLookup.by_name('User'), + 'deletable' => true, + 'source' => 'twitter', + 'source_url' => sender_profile[:profile_image_url], + } + end + + let(:authorization_attributes) do + { + 'uid' => sender_profile[:id], + 'username' => sender_profile[:screen_name], + 'provider' => 'twitter', + } + end + + context 'from unknown user' do + it 'creates a User record for the sender' do + expect { twitter_sync.process_webhook(channel) } + .to change(User, :count).by(1) + .and change { User.exists?(sender_attributes) }.to(true) + end + + it 'creates an Avatar record for the sender', :use_vcr do + # Why 2, and not 1? Avatar.add auto-generates a default (source: 'init') record + # before actually adding the specified (source: 'twitter') one. + expect { twitter_sync.process_webhook(channel) } + .to change(Avatar, :count).by_at_least(1) + .and change { Avatar.exists?(avatar_attributes) }.to(true) + + expect(User.last.image).to eq(Avatar.last.store_hash) + end + + it 'creates an Authorization record for the sender' do + expect { twitter_sync.process_webhook(channel) } + .to change(Authorization, :count).by(1) + .and change { Authorization.exists?(authorization_attributes) }.to(true) + end + end + + context 'from known user' do + let!(:user) { create(:user) } + + let!(:avatar) { create(:avatar, o_id: user.id, object_lookup_id: ObjectLookup.by_name('User'), source: 'twitter') } + + let!(:authorization) do + Authorization.create(user_id: user.id, uid: sender_profile[:id], provider: 'twitter') + end + + it 'updates the sender’s existing User record' do + expect { twitter_sync.process_webhook(channel) } + .to not_change(User, :count) + .and not_change { user.reload.attributes.slice('login', 'firstname') } + .and change { User.exists?(sender_attributes.except('login', 'firstname')) }.to(true) + end + + it 'updates the sender’s existing Avatar record', :use_vcr do + expect { twitter_sync.process_webhook(channel) } + .to not_change(Avatar, :count) + .and change { Avatar.exists?(avatar_attributes) }.to(true) + + expect(user.reload.image).to eq(avatar.reload.store_hash) + end + + it 'updates the sender’s existing Authorization record' do + expect { twitter_sync.process_webhook(channel) } + .to not_change(Authorization, :count) + .and change { Authorization.exists?(authorization_attributes) }.to(true) + end + end + end + + context 'for incoming DM' do + let(:payload_file) { Rails.root.join('test', 'data', 'twitter', 'webhook_events', 'direct_message-incoming.yml') } + + include_examples 'for user processing' do + # Payload sent by Twitter is { ..., users: [{ : }, { : }] } + let(:sender_profile) { payload[:users].values.first } + end + + describe 'ticket creation' do + let(:ticket_attributes) do + # NOTE: missing "customer_id" (because the value is generated as part of the #process_webhook method) + { + 'title' => title, + 'group_id' => channel.options[:sync][:direct_messages][:group_id], + 'state' => Ticket::State.find_by(default_create: true), + 'priority' => Ticket::Priority.find_by(default_create: true), + 'preferences' => { + 'channel_id' => channel.id, + 'channel_screen_name' => channel.options[:user][:screen_name], + }, + } + end + + let(:title) { payload[:direct_message_events].first[:message_create][:message_data][:text] } + + it 'creates a new ticket' do + expect { twitter_sync.process_webhook(channel) } + .to change(Ticket, :count).by(1) + .and change { Ticket.exists?(ticket_attributes) }.to(true) + end + + context 'for duplicate messages' do + before do + described_class.new( + channel.options[:auth], + YAML.safe_load(File.read(payload_file), [ActiveSupport::HashWithIndifferentAccess]) + ).process_webhook(channel) + end + + it 'does not create duplicate ticket' do + expect { twitter_sync.process_webhook(channel) } + .to not_change(Ticket, :count) + .and not_change(Ticket::Article, :count) + end + end + + context 'for message longer than 80 chars' do + before { payload[:direct_message_events].first[:message_create][:message_data][:text] = 'a' * 81 } + + let(:title) { "#{'a' * 80}..." } + + it 'creates ticket with truncated title' do + expect { twitter_sync.process_webhook(channel) } + .to change(Ticket, :count).by(1) + .and change { Ticket.exists?(ticket_attributes) }.to(true) + end + end + + context 'in reply to existing thread/ticket' do + # import parent DM + before do + described_class.new( + channel.options[:auth], + YAML.safe_load( + File.read(Rails.root.join('test', 'data', 'twitter', 'webhook_events', 'direct_message-incoming.yml')), + [ActiveSupport::HashWithIndifferentAccess] + ) + ).process_webhook(channel) + end + + let(:payload_file) { Rails.root.join('test', 'data', 'twitter', 'webhook_events', 'direct_message-incoming_2.yml') } + + it 'uses existing ticket' do + expect { twitter_sync.process_webhook(channel) } + .to not_change(Ticket, :count) + .and not_change { Ticket.last.state } + end + + context 'marked "closed" / "merged" / "removed"' do + before { Ticket.last.update(state: Ticket::State.find_by(name: 'closed')) } + + it 'creates a new ticket' do + expect { twitter_sync.process_webhook(channel) } + .to change(Ticket, :count).by(1) + .and change { Ticket.exists?(ticket_attributes) }.to(true) + end + end + + context 'marked "pending reminder" / "pending close"' do + before { Ticket.last.update(state: Ticket::State.find_by(name: 'pending reminder')) } + + it 'sets existing ticket to "open"' do + expect { twitter_sync.process_webhook(channel) } + .to not_change(Ticket, :count) + .and change { Ticket.last.state.name }.to('open') + end + end + end + end + + describe 'article creation' do + let(:article_attributes) do + # NOTE: missing "ticket_id" (because the value is generated as part of the #process_webhook method) + { + 'from' => "@#{payload[:users].values.first[:screen_name]}", + 'to' => "@#{payload[:users].values.second[:screen_name]}", + 'body' => payload[:direct_message_events].first[:message_create][:message_data][:text], + 'message_id' => payload[:direct_message_events].first[:id], + 'in_reply_to' => nil, + 'type_id' => Ticket::Article::Type.find_by(name: 'twitter direct-message').id, + 'sender_id' => Ticket::Article::Sender.find_by(name: 'Customer').id, + 'internal' => false, + 'preferences' => { 'twitter' => twitter_prefs, 'links' => link_array } + } + end + + let(:twitter_prefs) do + { + 'created_at' => payload[:direct_message_events].first[:created_timestamp], + 'recipient_id' => payload[:direct_message_events].first[:message_create][:target][:recipient_id], + 'recipient_screen_name' => payload[:users].values.second[:screen_name], + 'sender_id' => payload[:direct_message_events].first[:message_create][:sender_id], + 'sender_screen_name' => payload[:users].values.first[:screen_name], + 'app_id' => payload[:apps]&.values&.first&.dig(:app_id), + 'app_name' => payload[:apps]&.values&.first&.dig(:app_name), + 'geo' => {}, + 'place' => {}, + } + end + + let(:link_array) do + [ + { + 'url' => "https://twitter.com/messages/#{user_ids.map(&:to_i).sort.join('-')}", + 'target' => '_blank', + 'name' => 'on Twitter', + }, + ] + end + + let(:user_ids) { payload[:users].values.map { |u| u[:id] } } + + it 'creates a new article' do + expect { twitter_sync.process_webhook(channel) } + .to change(Ticket::Article, :count).by(1) + .and change { Ticket::Article.exists?(article_attributes) }.to(true) + end + + context 'for duplicate messages' do + before do + described_class.new( + channel.options[:auth], + YAML.safe_load(File.read(payload_file), [ActiveSupport::HashWithIndifferentAccess]) + ).process_webhook(channel) + end + + it 'does not create duplicate article' do + expect { twitter_sync.process_webhook(channel) } + .to not_change(Ticket::Article, :count) + end + end + + context 'when message contains shortened (t.co) url' do + let(:payload_file) { Rails.root.join('test', 'data', 'twitter', 'webhook_events', 'direct_message-incoming_with_url.yml') } + + it 'replaces the t.co url for the original' do + expect { twitter_sync.process_webhook(channel) } + .to change { Ticket::Article.exists?(body: <<~BODY.chomp) }.to(true) + Did you know about this? https://en.wikipedia.org/wiki/Frankenstein#Composition + BODY + end + end + end + end + + context 'for outgoing DM' do + let(:payload_file) { Rails.root.join('test', 'data', 'twitter', 'webhook_events', 'direct_message-outgoing.yml') } + + describe 'ticket creation' do + let(:ticket_attributes) do + # NOTE: missing "customer_id" (because User.last changes before and after the method is called) + { + 'title' => payload[:direct_message_events].first[:message_create][:message_data][:text], + 'group_id' => channel.options[:sync][:direct_messages][:group_id], + 'state' => Ticket::State.find_by(name: 'closed'), + 'priority' => Ticket::Priority.find_by(default_create: true), + 'preferences' => { + 'channel_id' => channel.id, + 'channel_screen_name' => channel.options[:user][:screen_name], + }, + } + end + + it 'creates a closed ticket' do + expect { twitter_sync.process_webhook(channel) } + .to change(Ticket, :count).by(1) + .and change { Ticket.exists?(ticket_attributes) }.to(true) + end + end + + describe 'article creation' do + let(:article_attributes) do + # NOTE: missing "ticket_id" (because the value is generated as part of the #process_webhook method) + { + 'from' => "@#{payload[:users].values.first[:screen_name]}", + 'to' => "@#{payload[:users].values.second[:screen_name]}", + 'body' => payload[:direct_message_events].first[:message_create][:message_data][:text], + 'message_id' => payload[:direct_message_events].first[:id], + 'in_reply_to' => nil, + 'type_id' => Ticket::Article::Type.find_by(name: 'twitter direct-message').id, + 'sender_id' => Ticket::Article::Sender.find_by(name: 'Customer').id, + 'internal' => false, + 'preferences' => { 'twitter' => twitter_prefs, 'links' => link_array } + } + end + + let(:twitter_prefs) do + { + 'created_at' => payload[:direct_message_events].first[:created_timestamp], + 'recipient_id' => payload[:direct_message_events].first[:message_create][:target][:recipient_id], + 'recipient_screen_name' => payload[:users].values.second[:screen_name], + 'sender_id' => payload[:direct_message_events].first[:message_create][:sender_id], + 'sender_screen_name' => payload[:users].values.first[:screen_name], + 'app_id' => payload[:apps]&.values&.first&.dig(:app_id), + 'app_name' => payload[:apps]&.values&.first&.dig(:app_name), + 'geo' => {}, + 'place' => {}, + } + end + + let(:link_array) do + [ + { + 'url' => "https://twitter.com/messages/#{user_ids.map(&:to_i).sort.join('-')}", + 'target' => '_blank', + 'name' => 'on Twitter', + }, + ] + end + + let(:user_ids) { payload[:users].values.map { |u| u[:id] } } + + it 'creates a new article' do + expect { twitter_sync.process_webhook(channel) } + .to change(Ticket::Article, :count).by(1) + .and change { Ticket::Article.exists?(article_attributes) }.to(true) + end + + context 'when message contains shortened (t.co) url' do + let(:payload_file) { Rails.root.join('test', 'data', 'twitter', 'webhook_events', 'direct_message-incoming_with_url.yml') } + + it 'replaces the t.co url for the original' do + expect { twitter_sync.process_webhook(channel) } + .to change { Ticket::Article.exists?(body: <<~BODY.chomp) }.to(true) + Did you know about this? https://en.wikipedia.org/wiki/Frankenstein#Composition + BODY + end + end + + context 'when message contains a media attachment (e.g., JPG)' do + let(:payload_file) { Rails.root.join('test', 'data', 'twitter', 'webhook_events', 'direct_message-incoming_with_media.yml') } + + it 'does not store it as an attachment on the article' do + twitter_sync.process_webhook(channel) + + expect(Ticket::Article.last.attachments).to be_empty + end + end + end + end + + context 'for incoming tweet' do + let(:payload_file) { Rails.root.join('test', 'data', 'twitter', 'webhook_events', 'tweet_create-user_mention.yml') } + + include_examples 'for user processing' do + # Payload sent by Twitter is { ..., tweet_create_events: [{ ..., user: }] } + let(:sender_profile) { payload[:tweet_create_events].first[:user] } + end + + describe 'ticket creation' do + let(:ticket_attributes) do + # NOTE: missing "customer_id" (because User.last changes before and after the method is called) + { + 'title' => payload[:tweet_create_events].first[:text], + 'group_id' => channel.options[:sync][:direct_messages][:group_id], + 'state' => Ticket::State.find_by(default_create: true), + 'priority' => Ticket::Priority.find_by(default_create: true), + 'preferences' => { + 'channel_id' => channel.id, + 'channel_screen_name' => channel.options[:user][:screen_name], + }, + } + end + + it 'creates a new ticket' do + expect { twitter_sync.process_webhook(channel) } + .to change(Ticket, :count).by(1) + end + + context 'for duplicate tweets' do + before do + described_class.new( + channel.options[:auth], + YAML.safe_load(File.read(payload_file), [ActiveSupport::HashWithIndifferentAccess]) + ).process_webhook(channel) + end + + it 'does not create duplicate ticket' do + expect { twitter_sync.process_webhook(channel) } + .to not_change(Ticket, :count) + .and not_change(Ticket::Article, :count) + end + end + + context 'in response to existing tweet thread' do + let(:payload_file) { Rails.root.join('test', 'data', 'twitter', 'webhook_events', 'tweet_create-response.yml') } + + let(:parent_tweet_payload) do + YAML.safe_load( + File.read(Rails.root.join('test', 'data', 'twitter', 'webhook_events', 'tweet_create-user_mention.yml')), + [ActiveSupport::HashWithIndifferentAccess] + ) + end + + context 'that hasn’t been imported yet', :use_vcr do + it 'creates a new ticket' do + expect { twitter_sync.process_webhook(channel) } + .to change(Ticket, :count).by(1) + end + + it 'retrieves the parent tweet via the Twitter API' do + expect { twitter_sync.process_webhook(channel) } + .to change(Ticket::Article, :count).by(2) + + expect(Ticket::Article.second_to_last.body).to eq(parent_tweet_payload[:tweet_create_events].first[:text]) + end + + context 'after parent tweet has been deleted' do + before do + payload[:tweet_create_events].first[:in_reply_to_status_id] = 1207610954160037890 # rubocop:disable Style/NumericLiterals + payload[:tweet_create_events].first[:in_reply_to_status_id_str] = '1207610954160037890' + end + + it 'creates a new ticket' do + expect { twitter_sync.process_webhook(channel) } + .to change(Ticket, :count).by(1) + end + + it 'silently ignores error when retrieving parent tweet' do + expect { twitter_sync.process_webhook(channel) }.to not_raise_error + end + end + end + + context 'that was previously imported' do + # import parent tweet + before { described_class.new(channel.options[:auth], parent_tweet_payload).process_webhook(channel) } + + it 'uses existing ticket' do + expect { twitter_sync.process_webhook(channel) } + .to not_change(Ticket, :count) + .and not_change { Ticket.last.state } + end + + context 'and marked "closed" / "merged" / "removed" / "pending reminder" / "pending close"' do + before { Ticket.last.update(state: Ticket::State.find_by(name: 'closed')) } + + it 'sets existing ticket to "open"' do + expect { twitter_sync.process_webhook(channel) } + .to not_change(Ticket, :count) + .and change { Ticket.last.state.name }.to('open') + end + end + end + end + end + + describe 'article creation' do + let(:article_attributes) do + # NOTE: missing "ticket_id" (because the value is generated as part of the #process_webhook method) + { + 'from' => "@#{payload[:tweet_create_events].first[:user][:screen_name]}", + 'to' => "@#{payload[:tweet_create_events].first[:entities][:user_mentions].first[:screen_name]}", + 'body' => payload[:tweet_create_events].first[:text], + 'message_id' => payload[:tweet_create_events].first[:id_str], + 'in_reply_to' => payload[:tweet_create_events].first[:in_reply_to_status_id], + 'type_id' => Ticket::Article::Type.find_by(name: 'twitter status').id, + 'sender_id' => Ticket::Article::Sender.find_by(name: 'Customer').id, + 'internal' => false, + 'preferences' => { 'twitter' => twitter_prefs, 'links' => link_array } + } + end + + let(:twitter_prefs) do + { + 'mention_ids' => payload[:tweet_create_events].first[:entities][:user_mentions].map { |um| um[:id] }, + 'geo' => payload[:tweet_create_events].first[:geo].to_h, + 'retweeted' => payload[:tweet_create_events].first[:retweeted], + 'possibly_sensitive' => payload[:tweet_create_events].first[:possibly_sensitive], + 'in_reply_to_user_id' => payload[:tweet_create_events].first[:in_reply_to_user_id], + 'place' => payload[:tweet_create_events].first[:place].to_h, + 'retweet_count' => payload[:tweet_create_events].first[:retweet_count], + 'source' => payload[:tweet_create_events].first[:source], + 'favorited' => payload[:tweet_create_events].first[:favorited], + 'truncated' => payload[:tweet_create_events].first[:truncated], + } + end + + let(:link_array) do + [ + { + 'url' => "https://twitter.com/_/status/#{payload[:tweet_create_events].first[:id]}", + 'target' => '_blank', + 'name' => 'on Twitter', + }, + ] + end + + it 'creates a new article' do + expect { twitter_sync.process_webhook(channel) } + .to change(Ticket::Article, :count).by(1) + .and change { Ticket::Article.exists?(article_attributes) }.to(true) + end + + context 'when message mentions multiple users' do + let(:payload_file) { Rails.root.join('test', 'data', 'twitter', 'webhook_events', 'tweet_create-user_mention_multiple.yml') } + + let(:mentionees) { "@#{payload[:tweet_create_events].first[:entities][:user_mentions].map { |um| um[:screen_name] }.join(', @')}" } + + it 'records all mentionees in comma-separated "to" attribute' do + expect { twitter_sync.process_webhook(channel) } + .to change { Ticket::Article.exists?(to: mentionees) }.to(true) + end + end + + context 'when message exceeds 140 characters' do + let(:payload_file) { Rails.root.join('test', 'data', 'twitter', 'webhook_events', 'tweet_create-user_mention_extended.yml') } + + let(:full_body) { payload[:tweet_create_events].first[:extended_tweet][:full_text] } + + it 'records the full (extended) tweet body' do + expect { twitter_sync.process_webhook(channel) } + .to change { Ticket::Article.exists?(body: full_body) }.to(true) + end + end + + context 'when message contains shortened (t.co) url' do + let(:payload_file) { Rails.root.join('test', 'data', 'twitter', 'webhook_events', 'tweet_create-user_mention_with_url.yml') } + + it 'replaces the t.co url for the original' do + expect { twitter_sync.process_webhook(channel) } + .to change { Ticket::Article.exists?(body: <<~BODY.chomp) }.to(true) + @ScruffyMcG https://zammad.org/ + BODY + end + end + + context 'when message contains a media attachment (e.g., JPG)' do + let(:payload_file) { Rails.root.join('test', 'data', 'twitter', 'webhook_events', 'tweet_create-user_mention_with_media.yml') } + + it 'replaces the t.co url for the original' do + expect { twitter_sync.process_webhook(channel) } + .to change { Ticket::Article.exists?(body: <<~BODY.chomp) }.to(true) + @ScruffyMcG https://twitter.com/pennbrooke1/status/1209101446706122752/photo/1 + BODY + end + + it 'stores it as an attachment on the article', :use_vcr do + twitter_sync.process_webhook(channel) + + expect(Ticket::Article.last.attachments).to be_one + end + end + end + end + + context 'for outgoing tweet' do + let(:payload_file) { Rails.root.join('test', 'data', 'twitter', 'webhook_events', 'tweet_create-user_mention_outgoing.yml') } + + describe 'ticket creation' do + let(:ticket_attributes) do + # NOTE: missing "customer_id" (because User.last changes before and after the method is called) + { + 'title' => payload[:tweet_create_events].first[:text], + 'group_id' => channel.options[:sync][:direct_messages][:group_id], + 'state' => Ticket::State.find_by(name: 'closed'), + 'priority' => Ticket::Priority.find_by(default_create: true), + 'preferences' => { + 'channel_id' => channel.id, + 'channel_screen_name' => channel.options[:user][:screen_name], + }, + } + end + + it 'creates a closed ticket' do + expect { twitter_sync.process_webhook(channel) } + .to change(Ticket, :count).by(1) + end + end + + describe 'article creation' do + let(:article_attributes) do + # NOTE: missing "ticket_id" (because the value is generated as part of the #process_webhook method) + { + 'from' => "@#{payload[:tweet_create_events].first[:user][:screen_name]}", + 'to' => "@#{payload[:tweet_create_events].first[:entities][:user_mentions].first[:screen_name]}", + 'body' => payload[:tweet_create_events].first[:text], + 'message_id' => payload[:tweet_create_events].first[:id_str], + 'in_reply_to' => payload[:tweet_create_events].first[:in_reply_to_status_id], + 'type_id' => Ticket::Article::Type.find_by(name: 'twitter status').id, + 'sender_id' => Ticket::Article::Sender.find_by(name: 'Customer').id, + 'internal' => false, + 'preferences' => { 'twitter' => twitter_prefs, 'links' => link_array } + } + end + + let(:twitter_prefs) do + { + 'mention_ids' => payload[:tweet_create_events].first[:entities][:user_mentions].map { |um| um[:id] }, + 'geo' => payload[:tweet_create_events].first[:geo].to_h, + 'retweeted' => payload[:tweet_create_events].first[:retweeted], + 'possibly_sensitive' => payload[:tweet_create_events].first[:possibly_sensitive], + 'in_reply_to_user_id' => payload[:tweet_create_events].first[:in_reply_to_user_id], + 'place' => payload[:tweet_create_events].first[:place].to_h, + 'retweet_count' => payload[:tweet_create_events].first[:retweet_count], + 'source' => payload[:tweet_create_events].first[:source], + 'favorited' => payload[:tweet_create_events].first[:favorited], + 'truncated' => payload[:tweet_create_events].first[:truncated], + } + end + + let(:link_array) do + [ + { + 'url' => "https://twitter.com/_/status/#{payload[:tweet_create_events].first[:id]}", + 'target' => '_blank', + 'name' => 'on Twitter', + }, + ] + end + + it 'creates a new article' do + expect { twitter_sync.process_webhook(channel) } + .to change(Ticket::Article, :count).by(1) + .and change { Ticket::Article.exists?(article_attributes) }.to(true) + end + end + end + end end diff --git a/spec/requests/integration/twitter_webhook_spec.rb b/spec/requests/integration/twitter_webhook_spec.rb deleted file mode 100644 index 67e833409..000000000 --- a/spec/requests/integration/twitter_webhook_spec.rb +++ /dev/null @@ -1,312 +0,0 @@ -require 'rails_helper' - -RSpec.describe 'Twitter Webhook Integration', type: :request do - let!(:external_credential) { create(:twitter_credential, credentials: credentials) } - let(:credentials) { { consumer_key: 'CCC', consumer_secret: 'DDD' } } - - describe '#webhook_incoming' do - let!(:channel) do - create( - :twitter_channel, - custom_options: { - auth: { - external_credential_id: external_credential.id, - oauth_token: 'AAA', - oauth_token_secret: 'BBB', - consumer_key: 'CCC', - consumer_secret: 'DDD', - }, - user: { - id: 123, - name: 'Zammad HQ', - screen_name: 'zammadhq', - }, - sync: { - limit: 20, - track_retweets: false, - search: [ - { - term: '#zammad', group_id: Group.first.id.to_s - }, - { - term: '#hello1234', group_id: Group.first.id.to_s - } - ], - } - } - ) - end - - describe 'auto-creation of tickets/articles on webhook receipt' do - let(:webhook_payload) do - JSON.parse(File.read(Rails.root.join('test/data/twitter', payload_file))).symbolize_keys - end - - context 'for outbound DMs' do - context 'not matching any admin-defined filters' do - let(:payload_file) { 'webhook1_direct_message.json' } - - it 'returns 200' do - post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json - - expect(response).to have_http_status(:ok) - end - - it 'creates closed ticket' do - expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json } - .to change(Ticket, :count).by(1) - - expect(Ticket.last.attributes) - .to include( - 'title' => 'Hey! Hello!', - 'state_id' => Ticket::State.find_by(name: 'closed').id, - 'priority_id' => Ticket::Priority.find_by(name: '2 normal').id, - 'customer_id' => User.find_by(login: 'zammadhq', firstname: 'Zammad', lastname: 'Hq').id, - 'created_by_id' => User.find_by(login: 'zammadhq', firstname: 'Zammad', lastname: 'Hq').id - ) - end - - it 'creates first article on closed ticket' do - expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json } - .to change { Ticket::Article.count }.by(1) - - expect(Ticket::Article.last.attributes) - .to include( - 'from' => '@zammadhq', - 'to' => '@medenhofer', - 'message_id' => '1062015437679050760', - 'created_by_id' => User.find_by(login: 'zammadhq', firstname: 'Zammad', lastname: 'Hq').id - ) - end - - it 'does not add any attachments to newly created ticket' do - post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json - - expect(Ticket::Article.last.attachments).to be_empty - end - end - end - - context 'for inbound DMs' do - context 'matching admin-defined #hashtag filter, with a link to an image' do - let(:payload_file) { 'webhook2_direct_message.json' } - - it 'returns 200' do - post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json - - expect(response).to have_http_status(:ok) - end - - it 'creates new ticket' do - expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json } - .to change(Ticket, :count).by(1) - - expect(Ticket.last.attributes) - .to include( - 'title' => 'Hello Zammad #zammad @znuny Yeah! https://t.co/UfaCwi9cUb', - 'state_id' => Ticket::State.find_by(name: 'new').id, - 'priority_id' => Ticket::Priority.find_by(name: '2 normal').id, - 'customer_id' => User.find_by(login: 'medenhofer', firstname: 'Martin', lastname: 'Edenhofer').id, - ) - end - - it 'creates first article on new ticket' do - expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json } - .to change { Ticket::Article.count }.by(1) - - expect(Ticket::Article.last.attributes) - .to include( - 'to' => '@zammadhq', - 'from' => '@medenhofer', - 'body' => "Hello Zammad #zammad @znuny\n\nYeah! https://twitter.com/messages/media/1063077238797725700", - 'message_id' => '1063077238797725700', - 'created_by_id' => User.find_by(login: 'medenhofer', firstname: 'Martin', lastname: 'Edenhofer').id - ) - end - - it 'does not add linked image as attachment to newly created ticket' do - post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json - - expect(Ticket::Article.last.attachments).to be_empty - end - end - - context 'from same sender as previously imported DMs' do - let(:payload_file) { 'webhook3_direct_message.json' } - - before { post '/api/v1/channels_twitter_webhook', **previous_webhook_payload, as: :json } - - let(:previous_webhook_payload) do - JSON.parse(File.read(Rails.root.join('test/data/twitter/webhook2_direct_message.json'))).symbolize_keys - end - - it 'returns 200' do - post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json - - expect(response).to have_http_status(:ok) - end - - it 'does not create new ticket' do - expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json } - .not_to change(Ticket, :count) - end - - it 'adds new article to existing, open ticket' do - expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json } - .to change { Ticket::Article.count }.by(1) - - expect(Ticket::Article.last.attributes) - .to include( - 'to' => '@zammadhq', - 'from' => '@medenhofer', - 'body' => 'Hello again!', - 'message_id' => '1063077238797725701', - 'created_by_id' => User.find_by(login: 'medenhofer', firstname: 'Martin', lastname: 'Edenhofer').id, - 'ticket_id' => Ticket.find_by(title: 'Hello Zammad #zammad @znuny Yeah! https://t.co/UfaCwi9cUb').id - ) - end - - it 'does not add any attachments to newly created ticket' do - post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json - - expect(Ticket::Article.last.attachments).to be_empty - end - end - end - - context 'when receiving duplicate DMs' do - let(:payload_file) { 'webhook1_direct_message.json' } - - it 'still returns 200' do - 2.times { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json } - - expect(response).to have_http_status(:ok) - end - - it 'does not create duplicate articles' do - expect do - 2.times { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json } - end.to change { Ticket::Article.count }.by(1) - end - end - - context 'for tweets' do - context 'matching admin-defined #hashtag filter, with an image link' do - let(:payload_file) { 'webhook1_tweet.json' } - - before do - stub_request(:get, 'http://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_bigger.jpg') - .to_return(status: 200, body: 'some_content') - - stub_request(:get, 'https://pbs.twimg.com/media/DsFKfJRWkAAFEbo.jpg') - .to_return(status: 200, body: 'some_content') - end - - it 'returns 200' do - post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json - - expect(response).to have_http_status(:ok) - end - - it 'creates a closed ticket' do - expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json } - .to change(Ticket, :count).by(1) - - expect(Ticket.last.attributes) - .to include( - 'title' => 'Hey @medenhofer ! #hello1234 https://t.co/f1kffFlwpN', - 'state_id' => Ticket::State.find_by(name: 'closed').id, - 'priority_id' => Ticket::Priority.find_by(name: '2 normal').id, - 'customer_id' => User.find_by(login: 'zammadhq', firstname: 'Zammad', lastname: 'Hq').id, - 'created_by_id' => User.find_by(login: 'zammadhq', firstname: 'Zammad', lastname: 'Hq').id, - ) - end - - it 'creates first article on closed ticket' do - expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json } - .to change { Ticket::Article.count }.by(1) - - expect(Ticket::Article.last.attributes) - .to include( - 'from' => '@zammadhq', - 'to' => '@medenhofer', - 'body' => 'Hey @medenhofer ! #hello1234 https://twitter.com/zammadhq/status/1063212927510081536/photo/1', - 'message_id' => '1063212927510081536', - 'created_by_id' => User.find_by(login: 'zammadhq', firstname: 'Zammad', lastname: 'Hq').id - ) - end - - it 'add linked image as attachment to newly created article' do - expect(Ticket::Article.last.attachments) - .to match_array(Store.where(filename: 'DsFKfJRWkAAFEbo.jpg')) - end - end - - context 'longer than 140 characters (with no media links)' do - let(:payload_file) { 'webhook2_tweet.json' } - - before do - stub_request(:get, 'http://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_bigger.jpg') - .to_return(status: 200, body: 'some_content') - end - - it 'returns 200' do - post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json - - expect(response).to have_http_status(:ok) - end - - it 'creates a new ticket' do - expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json } - .to change(Ticket, :count).by(1) - - expect(Ticket.last.attributes) - .to include( - 'title' => '@znuny Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy ...', - 'state_id' => Ticket::State.find_by(name: 'new').id, - 'priority_id' => Ticket::Priority.find_by(name: '2 normal').id, - 'customer_id' => User.find_by(login: 'medenhofer', firstname: 'Martin', lastname: 'Edenhofer').id, - 'created_by_id' => User.find_by(login: 'medenhofer', firstname: 'Martin', lastname: 'Edenhofer').id, - ) - end - - it 'creates first article on new ticket' do - expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json } - .to change { Ticket::Article.count }.by(1) - - expect(Ticket::Article.last.attributes) - .to include( - 'from' => '@medenhofer', - 'to' => '@znuny', - 'body' => '@znuny Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lore', - 'created_by_id' => User.find_by(login: 'medenhofer', firstname: 'Martin', lastname: 'Edenhofer').id, - 'message_id' => '1065035365336141825' - ) - end - - it 'does not add any attachments to newly created ticket' do - post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json - - expect(Ticket::Article.last.attachments).to be_empty - end - end - - context 'when receiving duplicate messages' do - let(:payload_file) { 'webhook1_tweet.json' } - - it 'still returns 200' do - 2.times { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json } - - expect(response).to have_http_status(:ok) - end - - it 'does not create duplicate articles' do - expect do - 2.times { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json } - end.to change { Ticket::Article.count }.by(1) - end - end - end - end - end -end diff --git a/spec/support/negated_matchers.rb b/spec/support/negated_matchers.rb index 03e45fd3b..ebd018bc6 100644 --- a/spec/support/negated_matchers.rb +++ b/spec/support/negated_matchers.rb @@ -1,3 +1,4 @@ RSpec::Matchers.define_negated_matcher :not_change, :change RSpec::Matchers.define_negated_matcher :not_include, :include RSpec::Matchers.define_negated_matcher :not_eq, :eq +RSpec::Matchers.define_negated_matcher :not_raise_error, :raise_error diff --git a/test/data/twitter/webhook1_direct_message.json b/test/data/twitter/webhook1_direct_message.json deleted file mode 100644 index f295ecbfa..000000000 --- a/test/data/twitter/webhook1_direct_message.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "headers" : { - "x-twitter-webhooks-signature" : "sha256=xXu7qrPhqXfo8Ot14c0si9HrdQdBNru5fkSdoMZi+Ms=" - }, - "params" : { - "for_user_id": "123", - "direct_message_events": [ - { - "type": "message_create", - "id": "1062015437679050760", - "created_timestamp": "1542039186292", - "message_create": { - "target": { - "recipient_id": "456" - }, - "sender_id": "123", - "source_app_id": "268278", - "message_data": { - "text": "Hey! Hello!", - "entities": { - "hashtags": [], - "symbols": [], - "user_mentions": [], - "urls": [] - } - } - } - } - ], - "apps": { - "268278": { - "id": "268278", - "name": "Twitter Web Client", - "url": "http://twitter.com" - } - }, - "users": { - "123": { - "id": "123", - "created_timestamp": "1476091912921", - "name": "Zammad HQ", - "screen_name": "zammadhq", - "description": "Helpdesk and Customer Support made easy. Open Source for download or to go with SaaS. #zammad", - "url": "https://t.co/XITyrXmhTP", - "protected": false, - "verified": false, - "followers_count": 426, - "friends_count": 509, - "statuses_count": 436, - "profile_image_url": "http://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg", - "profile_image_url_https": "https://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg" - }, - "456": { - "id": "456", - "created_timestamp": "1290730789000", - "name": "Martin Edenhofer", - "screen_name": "medenhofer", - "description": "Open Source professional and geek. Also known as #OTRS and #Zammad inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.", - "url": "https://t.co/whm4HTWdMw", - "protected": false, - "verified": false, - "followers_count": 312, - "friends_count": 314, - "statuses_count": 222, - "profile_image_url": "http://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg", - "profile_image_url_https": "https://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg" - } - } - } -} diff --git a/test/data/twitter/webhook1_tweet.json b/test/data/twitter/webhook1_tweet.json deleted file mode 100644 index 0ff0452f0..000000000 --- a/test/data/twitter/webhook1_tweet.json +++ /dev/null @@ -1,167 +0,0 @@ -{ - "headers": { - "x-twitter-webhooks-signature": "sha256=DmARpz6wdgte6Vj+ePeqC+RHvEDokmwOIIqr4//utkk=" - }, - "params": { - "for_user_id": "123", - "tweet_create_events": [ - { - "created_at": "Thu Nov 15 23:31:30 +0000 2018", - "id": 1063212927510081536, - "id_str": "1063212927510081536", - "text": "Hey @medenhofer ! #hello1234 https://t.co/f1kffFlwpN", - "display_text_range": [0, 29], - "source": "Twitter Web Client", - "truncated": false, - "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": 123, - "id_str": "123", - "name": "Zammad HQ", - "screen_name": "zammadhq", - "location": null, - "url": "http://zammad.com", - "description": "Helpdesk and Customer Support made easy. Open Source for download or to go with SaaS. #zammad", - "translator_type": "none", - "protected": false, - "verified": false, - "followers_count": 427, - "friends_count": 512, - "listed_count": 20, - "favourites_count": 280, - "statuses_count": 438, - "created_at": "Mon Oct 10 09:31:52 +0000 2016", - "utc_offset": null, - "time_zone": null, - "geo_enabled": false, - "lang": "en", - "contributors_enabled": false, - "is_translator": false, - "profile_background_color": "000000", - "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", - "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", - "profile_background_tile": false, - "profile_link_color": "31B068", - "profile_sidebar_border_color": "000000", - "profile_sidebar_fill_color": "000000", - "profile_text_color": "000000", - "profile_use_background_image": false, - "profile_image_url": "http://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg", "profile_image_url_https": "https://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg", - "profile_banner_url": "https://pbs.twimg.com/profile_banners/123/1476097853", - "default_profile": false, - "default_profile_image": false, - "following": null, - "follow_request_sent": null, - "notifications": null - }, - "geo": null, - "coordinates": null, - "place": null, - "contributors": null, - "is_quote_status": false, - "quote_count": 0, - "reply_count": 0, - "retweet_count": 0, - "favorite_count": 0, - "entities": { - "hashtags": [ - {"text": "hello1234", "indices": [19, 29]} - ], - "urls": [], - "user_mentions": [ - { - "screen_name": "medenhofer", - "name": "Martin Edenhofer", - "id": 456, - "id_str": "456", - "indices": [4, 15] - } - ], - "symbols": [], - "media": [ - { - "id": 1063212885961248768, - "id_str": "1063212885961248768", - "indices": [30, 53], - "media_url": "http://pbs.twimg.com/media/DsFKfJRWkAAFEbo.jpg", - "media_url_https": "https://pbs.twimg.com/media/DsFKfJRWkAAFEbo.jpg", - "url": "https://t.co/f1kffFlwpN", - "display_url": "pic.twitter.com/f1kffFlwpN", - "expanded_url": "https://twitter.com/zammadhq/status/1063212927510081536/photo/1", - "type": "photo", - "sizes": { - "thumb": { - "w": 150, - "h": 150, - "resize": "crop" - }, - "large": { - "w": 852, - "h": 462, - "resize": "fit" - }, - "medium": { - "w": 852, - "h": 462, - "resize": "fit" - }, - "small": { - "w": 680, - "h": 369, - "resize": "fit" - } - } - } - ] - }, - "extended_entities": { - "media": [ - { - "id": 1063212885961248768, - "id_str": "1063212885961248768", - "indices": [30, 53], - "media_url": "http://pbs.twimg.com/media/DsFKfJRWkAAFEbo.jpg", - "media_url_https": "https://pbs.twimg.com/media/DsFKfJRWkAAFEbo.jpg", - "url": "https://t.co/f1kffFlwpN", - "display_url": "pic.twitter.com/f1kffFlwpN", - "expanded_url": "https://twitter.com/zammadhq/status/1063212927510081536/photo/1", - "type": "photo", - "sizes": { - "thumb": { - "w": 150, - "h": 150, - "resize": "crop" - }, - "large": { - "w": 852, - "h": 462, - "resize": "fit" - }, - "medium": { - "w": 852, - "h": 462, - "resize": "fit" - }, - "small": { - "w": 680, - "h": 369, - "resize": "fit" - } - } - } - ] - }, - "favorited": false, - "retweeted": false, - "possibly_sensitive": false, - "filter_level": "low", - "lang": "und", - "timestamp_ms": "1542324690116" - } - ] - } -} diff --git a/test/data/twitter/webhook2_direct_message.json b/test/data/twitter/webhook2_direct_message.json deleted file mode 100644 index fec2a5884..000000000 --- a/test/data/twitter/webhook2_direct_message.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "headers" : { - "x-twitter-webhooks-signature" : "sha256=wYiCk7gfAgrnerCpj3XD58hozfVDjcQvcYPZCFH+stU=" - }, - "params": { - "for_user_id": "123", - "direct_message_events": [ - { - "type": "message_create", - "id": "1063077238797725700", - "created_timestamp": "1542292339406", - "message_create": { - "target": { - "recipient_id": "123" - }, - "sender_id": "456", - "message_data": { - "text": "Hello Zammad #zammad @znuny\n\nYeah! https://t.co/UfaCwi9cUb", - "entities": { - "hashtags": [ - { - "text": "zammad", - "indices": [13,20] - } - ], - "symbols": [], - "user_mentions": [ - { - "screen_name": "znuny", - "name": "Znuny / ES for OTRS", - "id": 789, - "id_str": "789", - "indices": [21, 27] - } - ], - "urls": [ - { - "url": "https://t.co/UfaCwi9cUb", - "expanded_url": "https://twitter.com/messages/media/1063077238797725700", - "display_url": "pic.twitter.com/UfaCwi9cUb", - "indices": [35, 58] - } - ] - }, - "attachment": { - "type": "media", - "media": { - "id": 1063077198536556545, - "id_str": "1063077198536556545", - "indices": [35, 58], - "media_url": "https://ton.twitter.com/1.1/ton/data/dm/1063077238797725700/1063077198536556545/9FZgsMdV.jpg", - "media_url_https": "https://ton.twitter.com/1.1/ton/data/dm/1063077238797725700/1063077198536556545/9FZgsMdV.jpg", - "url": "https://t.co/UfaCwi9cUb", - "display_url": "pic.twitter.com/UfaCwi9cUb", - "expanded_url": "https://twitter.com/messages/media/1063077238797725700", - "type": "photo", - "sizes": { - "thumb": { - "w": 150, - "h": 150, - "resize": "crop" - }, - "medium": { - "w": 1200, - "h": 313, - "resize": "fit" - }, - "small": { - "w": 680, - "h": 177, - "resize": "fit" - }, - "large": { - "w": 1472, - "h": 384, - "resize": "fit" - } - } - } - } - } - } - } - ], - "users": { - "456": { - "id": "456", - "created_timestamp": "1290730789000", - "name": "Martin Edenhofer", - "screen_name": "medenhofer", - "description": "Open Source professional and geek. Also known as #OTRS and #Zammad inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.", - "url": "https://t.co/whm4HTWdMw", - "protected": false, - "verified": false, - "followers_count": 312, - "friends_count": 314, - "statuses_count": 222, - "profile_image_url": "http://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg", - "profile_image_url_https": "https://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg" - }, - "123": { - "id": "123", - "created_timestamp": "1476091912921", - "name": "Zammad HQ", - "screen_name": "zammadhq", - "description": "Helpdesk and Customer Support made easy. Open Source for download or to go with SaaS. #zammad", - "url": "https://t.co/XITyrXmhTP", - "protected": false, - "verified": false, - "followers_count": 427, - "friends_count": 512, - "statuses_count": 437, - "profile_image_url": "http://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg", - "profile_image_url_https": "https://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg" - } - } - } -} diff --git a/test/data/twitter/webhook2_tweet.json b/test/data/twitter/webhook2_tweet.json deleted file mode 100644 index 55f371aed..000000000 --- a/test/data/twitter/webhook2_tweet.json +++ /dev/null @@ -1,115 +0,0 @@ -{ - "headers": { - "x-twitter-webhooks-signature": "sha256=U7bglX19JitI2xuvyONAc0d/fowIFEeUzkEgnWdGyUM=" - }, - "params": { - "for_user_id": "123", - "tweet_create_events": [ - { - "created_at": "Wed Nov 21 00:13:13 +0000 2018", - "id": 1065035365336141825, - "id_str": "1065035365336141825", - "text": "@znuny Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et… https://t.co/b9woj0QXNZ", - "source": "Twitter Web Client", - "truncated": true, - "in_reply_to_status_id": null, - "in_reply_to_status_id_str": null, - "in_reply_to_user_id": 123, - "in_reply_to_user_id_str": "123", - "in_reply_to_screen_name": "znuny", - "user": { - "id": 219826253, - "id_str": "219826253", - "name": "Martin Edenhofer", - "screen_name": "medenhofer", - "location": null, - "url": "http://edenhofer.de/", - "description": "Open Source professional and geek. Also known as #OTRS and #Zammad inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.", - "translator_type": "regular", - "protected": false, - "verified": false, - "followers_count": 310, - "friends_count": 314, - "listed_count": 16, - "favourites_count": 129, - "statuses_count": 225, - "created_at": "Fri Nov 26 00:19:49 +0000 2010", - "utc_offset": null, - "time_zone": null, - "geo_enabled": false, - "lang": "en", - "contributors_enabled": false, - "is_translator": false, - "profile_background_color": "C0DEED", - "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", - "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", - "profile_background_tile": true, "profile_link_color": "0084B4", "profile_sidebar_border_color": "FFFFFF", - "profile_sidebar_fill_color": "DDEEF6", - "profile_text_color": "333333", - "profile_use_background_image": true, - "profile_image_url": "http://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg", - "profile_image_url_https": "https://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg", - "profile_banner_url": "https://pbs.twimg.com/profile_banners/219826253/1349428277", - "default_profile": false, - "default_profile_image": false, - "following": null, - "follow_request_sent": null, - "notifications": null - }, - "geo": null, - "coordinates": null, - "place": null, - "contributors": null, - "is_quote_status": false, - "extended_tweet": { - "full_text": "@znuny Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lore", - "display_text_range": [0, 279], - "entities": { - "hashtags": [], - "urls": [], - "user_mentions": [ - { - "screen_name": "znuny", - "name": "Znuny / ES for OTRS", - "id": 123, - "id_str": "123", - "indices": [0, 6] - } - ], - "symbols": [] - } - }, - "quote_count": 0, - "reply_count": 0, - "retweet_count": 0, - "favorite_count": 0, - "entities": { - "hashtags": [], - "urls": [ - { - "url": "https://t.co/b9woj0QXNZ", - "expanded_url": "https://twitter.com/i/web/status/1065035365336141825", - "display_url": "twitter.com/i/web/status/1…", - "indices": [117, 140] - } - ], - "user_mentions": [ - { - "screen_name": "znuny", - "name": "Znuny / ES for OTRS", - "id": 123, - "id_str": "123", - "indices": [0, 6] - } - ], - "symbols": [] - }, - "favorited": false, - "retweeted": false, - "filter_level": "low", - "lang": "ro", - "timestamp_ms": "1542759193153" - } - ] - } -} diff --git a/test/data/twitter/webhook3_direct_message.json b/test/data/twitter/webhook3_direct_message.json deleted file mode 100644 index 2fbd141f5..000000000 --- a/test/data/twitter/webhook3_direct_message.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "headers" : { - "x-twitter-webhooks-signature" : "sha256=OTguUdchBdxNal/csZsRkytKL5srrUuezZ3hp/2E404=" - }, - "params" : { - "for_user_id": "123", - "direct_message_events": [ - { - "type": "message_create", - "id": "1063077238797725701", - "created_timestamp": "1542292339406", - "message_create": { - "target": { - "recipient_id": "123" - }, - "sender_id": "456", - "message_data": { - "text": "Hello again!", - "entities": { - "hashtags": [], - "symbols": [], - "user_mentions": [], - "urls": [] - } - } - } - } - ], - "users": { - "456": { - "id": "456", - "created_timestamp": "1290730789000", - "name": "Martin Edenhofer", - "screen_name": "medenhofer", - "description": "Open Source professional and geek. Also known as #OTRS and #Zammad inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.", - "url": "https://t.co/whm4HTWdMw", - "protected": false, - "verified": false, - "followers_count": 312, - "friends_count": 314, - "statuses_count": 222, - "profile_image_url": "http://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg", - "profile_image_url_https": "https://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg" - }, - "123": { - "id": "123", - "created_timestamp": "1476091912921", - "name": "Zammad HQ", - "screen_name": "zammadhq", - "description": "Helpdesk and Customer Support made easy. Open Source for download or to go with SaaS. #zammad", - "url": "https://t.co/XITyrXmhTP", - "protected": false, - "verified": false, - "followers_count": 427, - "friends_count": 512, - "statuses_count": 437, - "profile_image_url": "http://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg", - "profile_image_url_https": "https://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg" - } - } - } -} diff --git a/test/data/twitter/webhook_events/direct_message-incoming.yml b/test/data/twitter/webhook_events/direct_message-incoming.yml new file mode 100644 index 000000000..f78570f3a --- /dev/null +++ b/test/data/twitter/webhook_events/direct_message-incoming.yml @@ -0,0 +1,51 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +direct_message_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + type: message_create + id: '1206724334791753732' + created_timestamp: '1576540475925' + message_create: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + target: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + recipient_id: '2975699229' + sender_id: '1205290247124217856' + message_data: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + text: Yes, you are! + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + symbols: [] + user_mentions: [] + urls: [] +users: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + '1205290247124217856': !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '1205290247124217856' + created_timestamp: '1576198570797' + name: pennbrooke + screen_name: pennbrooke1 + location: London + description: More like penn-broke, amirite? + url: https://t.co/Y9Umsm0AJJ + protected: false + verified: false + followers_count: 1 + friends_count: 1 + statuses_count: 2 + 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 + '2975699229': !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '2975699229' + created_timestamp: '1421116717416' + name: Ryan Lue + screen_name: ScruffyMcG + location: Taipei + description: I like turtles. + url: https://t.co/Y9Umsm0AJJ + protected: false + verified: false + followers_count: 31 + friends_count: 57 + statuses_count: 6 + profile_image_url: http://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_image_url_https: https://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/direct_message-incoming_2.yml b/test/data/twitter/webhook_events/direct_message-incoming_2.yml new file mode 100644 index 000000000..2ee591a13 --- /dev/null +++ b/test/data/twitter/webhook_events/direct_message-incoming_2.yml @@ -0,0 +1,51 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +direct_message_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + type: message_create + id: '1207556329834835973' + created_timestamp: '1576738839001' + message_create: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + target: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + recipient_id: '2975699229' + sender_id: '1205290247124217856' + message_data: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + text: and I'm a hungry, hungry hippo! + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + symbols: [] + user_mentions: [] + urls: [] +users: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + '1205290247124217856': !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '1205290247124217856' + created_timestamp: '1576198570797' + name: pennbrooke + screen_name: pennbrooke1 + location: London + description: More like penn-broke, amirite? + url: https://t.co/Y9Umsm0AJJ + protected: false + verified: false + followers_count: 0 + friends_count: 1 + statuses_count: 2 + 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 + '2975699229': !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '2975699229' + created_timestamp: '1421116717416' + name: Ryan Lue + screen_name: ScruffyMcG + location: Taipei + description: I like turtles. + url: https://t.co/Y9Umsm0AJJ + protected: false + verified: false + followers_count: 31 + friends_count: 56 + statuses_count: 6 + profile_image_url: http://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_image_url_https: https://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/direct_message-incoming_with_media.yml b/test/data/twitter/webhook_events/direct_message-incoming_with_media.yml new file mode 100644 index 000000000..8a49ebd2d --- /dev/null +++ b/test/data/twitter/webhook_events/direct_message-incoming_with_media.yml @@ -0,0 +1,98 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +direct_message_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + type: message_create + id: '1209117907864571908' + created_timestamp: '1577111148216' + message_create: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + target: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + recipient_id: '2975699229' + sender_id: '1205290247124217856' + message_data: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + text: " https://t.co/amoKo1v19w" + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + symbols: [] + user_mentions: [] + urls: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + url: https://t.co/amoKo1v19w + expanded_url: https://twitter.com/messages/media/1209117907864571908 + display_url: pic.twitter.com/amoKo1v19w + indices: + - 1 + - 24 + attachment: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + type: media + media: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: 1209117874389843969 + id_str: '1209117874389843969' + indices: + - 1 + - 24 + media_url: https://pbs.twimg.com/dm_gif_preview/1209117874389843969/Lgs2ZRqicjZjx6kFdwQcu2T5rmEq7br_m8QKHj-RybsPv0yeq5.jpg + media_url_https: https://pbs.twimg.com/dm_gif_preview/1209117874389843969/Lgs2ZRqicjZjx6kFdwQcu2T5rmEq7br_m8QKHj-RybsPv0yeq5.jpg + url: https://t.co/amoKo1v19w + display_url: pic.twitter.com/amoKo1v19w + expanded_url: https://twitter.com/messages/media/1209117907864571908 + type: animated_gif + sizes: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + small: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + w: 270 + h: 480 + resize: fit + thumb: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + w: 150 + h: 150 + resize: crop + medium: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + w: 270 + h: 480 + resize: fit + large: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + w: 270 + h: 480 + resize: fit + video_info: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + aspect_ratio: + - 9 + - 16 + variants: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + bitrate: 0 + content_type: video/mp4 + url: https://video.twimg.com/dm_gif/1209117874389843969/Lgs2ZRqicjZjx6kFdwQcu2T5rmEq7br_m8QKHj-RybsPv0yeq5.mp4 +users: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + '1205290247124217856': !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '1205290247124217856' + created_timestamp: '1576198570797' + name: pennbrooke + screen_name: pennbrooke1 + location: London + description: More like penn-broke, amirite? + url: https://t.co/Y9Umsm0AJJ + protected: false + verified: false + followers_count: 0 + friends_count: 1 + statuses_count: 8 + 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 + '2975699229': !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '2975699229' + created_timestamp: '1421116717416' + name: Ryan Lue + screen_name: ScruffyMcG + location: Taipei + description: I like turtles. + url: https://t.co/Y9Umsm0AJJ + protected: false + verified: false + followers_count: 31 + friends_count: 56 + statuses_count: 7 + profile_image_url: http://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_image_url_https: https://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/direct_message-incoming_with_url.yml b/test/data/twitter/webhook_events/direct_message-incoming_with_url.yml new file mode 100644 index 000000000..967037cf9 --- /dev/null +++ b/test/data/twitter/webhook_events/direct_message-incoming_with_url.yml @@ -0,0 +1,58 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +direct_message_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + type: message_create + id: '1209004344399429636' + created_timestamp: '1577084072576' + message_create: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + target: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + recipient_id: '2975699229' + sender_id: '1205290247124217856' + message_data: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + text: Did you know about this? https://t.co/SJ8nWqjwhz + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + symbols: [] + user_mentions: [] + urls: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + url: https://t.co/SJ8nWqjwhz + expanded_url: https://en.wikipedia.org/wiki/Frankenstein#Composition + display_url: en.wikipedia.org/wiki/Frankenst… + indices: + - 25 + - 48 +users: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + '1205290247124217856': !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '1205290247124217856' + created_timestamp: '1576198570797' + name: pennbrooke + screen_name: pennbrooke1 + location: London + description: More like penn-broke, amirite? + url: https://t.co/Y9Umsm0AJJ + protected: false + verified: false + followers_count: 0 + friends_count: 1 + statuses_count: 4 + 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 + '2975699229': !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '2975699229' + created_timestamp: '1421116717416' + name: Ryan Lue + screen_name: ScruffyMcG + location: Taipei + description: I like turtles. + url: https://t.co/Y9Umsm0AJJ + protected: false + verified: false + followers_count: 31 + friends_count: 56 + statuses_count: 6 + profile_image_url: http://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_image_url_https: https://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/direct_message-outgoing.yml b/test/data/twitter/webhook_events/direct_message-outgoing.yml new file mode 100644 index 000000000..88664376b --- /dev/null +++ b/test/data/twitter/webhook_events/direct_message-outgoing.yml @@ -0,0 +1,57 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +direct_message_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + type: message_create + id: '1206724203514228741' + created_timestamp: '1576540444626' + message_create: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + target: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + recipient_id: '1205290247124217856' + sender_id: '2975699229' + source_app_id: '3033300' + message_data: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + text: I'm a banana! + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + symbols: [] + user_mentions: [] + urls: [] +apps: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + '3033300': !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '3033300' + name: Twitter Web App + url: https://mobile.twitter.com +users: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + '2975699229': !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '2975699229' + created_timestamp: '1421116717416' + name: Ryan Lue + screen_name: ScruffyMcG + location: Taipei + description: I like turtles. + url: https://t.co/Y9Umsm0AJJ + protected: false + verified: false + followers_count: 31 + friends_count: 57 + statuses_count: 6 + profile_image_url: http://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_image_url_https: https://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + '1205290247124217856': !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '1205290247124217856' + created_timestamp: '1576198570797' + name: pennbrooke + screen_name: pennbrooke1 + location: London + description: More like penn-broke, amirite? + url: https://t.co/Y9Umsm0AJJ + protected: false + verified: false + followers_count: 1 + friends_count: 1 + statuses_count: 2 + 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 +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/direct_message_indicate_typing.yml b/test/data/twitter/webhook_events/direct_message_indicate_typing.yml new file mode 100644 index 000000000..92cc28f70 --- /dev/null +++ b/test/data/twitter/webhook_events/direct_message_indicate_typing.yml @@ -0,0 +1,41 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +direct_message_indicate_typing_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + created_timestamp: '1576540471102' + sender_id: '1205290247124217856' + target: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + recipient_id: '2975699229' +users: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + '1205290247124217856': !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '1205290247124217856' + created_timestamp: '1576198570797' + name: pennbrooke + screen_name: pennbrooke1 + location: London + description: More like penn-broke, amirite? + url: https://t.co/Y9Umsm0AJJ + protected: false + verified: false + followers_count: 1 + friends_count: 1 + statuses_count: 2 + 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 + '2975699229': !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '2975699229' + created_timestamp: '1421116717416' + name: Ryan Lue + screen_name: ScruffyMcG + location: Taipei + description: I like turtles. + url: https://t.co/Y9Umsm0AJJ + protected: false + verified: false + followers_count: 31 + friends_count: 57 + statuses_count: 6 + profile_image_url: http://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_image_url_https: https://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/direct_message_mark_read.yml b/test/data/twitter/webhook_events/direct_message_mark_read.yml new file mode 100644 index 000000000..59f6eda0e --- /dev/null +++ b/test/data/twitter/webhook_events/direct_message_mark_read.yml @@ -0,0 +1,42 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +direct_message_mark_read_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + created_timestamp: '1576540445711' + sender_id: '1205290247124217856' + target: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + recipient_id: '2975699229' + last_read_event_id: '1206724203514228741' +users: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + '1205290247124217856': !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '1205290247124217856' + created_timestamp: '1576198570797' + name: pennbrooke + screen_name: pennbrooke1 + location: London + description: More like penn-broke, amirite? + url: https://t.co/Y9Umsm0AJJ + protected: false + verified: false + followers_count: 1 + friends_count: 1 + statuses_count: 2 + 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 + '2975699229': !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '2975699229' + created_timestamp: '1421116717416' + name: Ryan Lue + screen_name: ScruffyMcG + location: Taipei + description: I like turtles. + url: https://t.co/Y9Umsm0AJJ + protected: false + verified: false + followers_count: 31 + friends_count: 57 + statuses_count: 6 + profile_image_url: http://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_image_url_https: https://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/favorited.yml b/test/data/twitter/webhook_events/favorited.yml new file mode 100644 index 000000000..fe045bbbc --- /dev/null +++ b/test/data/twitter/webhook_events/favorited.yml @@ -0,0 +1,127 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +favorite_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: 306365f99a761d92af4753775e89f7a4 + created_at: Mon Dec 16 23:49:59 +0000 2019 + timestamp_ms: 1576540199643 + favorited_status: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + created_at: Mon Dec 16 12:20:39 +0000 2019 + id: 1206549698791657473 + id_str: '1206549698791657473' + text: https://t.co/qywSsecZ1I is now running on Falcon. + source: Twitter Web App + truncated: false + in_reply_to_status_id: + in_reply_to_status_id_str: + in_reply_to_user_id: + in_reply_to_user_id_str: + in_reply_to_screen_name: + user: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: 28451785 + id_str: '28451785' + name: Samuel Williams + screen_name: ioquatix + location: Christchurch, New Zealand + url: http://www.codeotaku.com + description: '"A candle loses nothing by lighting another"' + translator_type: none + protected: false + verified: false + followers_count: 574 + friends_count: 149 + listed_count: 22 + favourites_count: 1683 + statuses_count: 1467 + created_at: Thu Apr 02 23:52:51 +0000 2009 + utc_offset: + time_zone: + geo_enabled: true + lang: + contributors_enabled: false + is_translator: false + profile_background_color: '000000' + profile_background_image_url: http://abs.twimg.com/images/themes/theme5/bg.gif + profile_background_image_url_https: https://abs.twimg.com/images/themes/theme5/bg.gif + profile_background_tile: true + profile_link_color: '000000' + profile_sidebar_border_color: 4025AD + profile_sidebar_fill_color: DEF0FF + profile_text_color: '000000' + profile_use_background_image: true + profile_image_url: http://pbs.twimg.com/profile_images/1343681082/Chi_Keyboard_normal.png + profile_image_url_https: https://pbs.twimg.com/profile_images/1343681082/Chi_Keyboard_normal.png + profile_banner_url: https://pbs.twimg.com/profile_banners/28451785/1353638161 + default_profile: false + default_profile_image: false + following: + follow_request_sent: + notifications: + geo: + coordinates: + place: + contributors: + is_quote_status: false + quote_count: 0 + reply_count: 0 + retweet_count: 4 + favorite_count: 36 + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + urls: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + url: https://t.co/qywSsecZ1I + expanded_url: https://rubyapi.org + display_url: rubyapi.org + indices: + - 0 + - 23 + user_mentions: [] + symbols: [] + favorited: false + retweeted: false + possibly_sensitive: false + filter_level: low + lang: en + user: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: 2975699229 + id_str: '2975699229' + name: Ryan Lue + screen_name: ScruffyMcG + location: Taipei + url: http://ryanlue.com + description: I like turtles. + translator_type: none + protected: false + verified: false + followers_count: 30 + friends_count: 56 + listed_count: 0 + favourites_count: 20 + statuses_count: 6 + created_at: Tue Jan 13 02:38:37 +0000 2015 + utc_offset: + time_zone: + geo_enabled: false + lang: + contributors_enabled: false + is_translator: false + profile_background_color: C0DEED + profile_background_image_url: http://abs.twimg.com/images/themes/theme1/bg.png + profile_background_image_url_https: https://abs.twimg.com/images/themes/theme1/bg.png + profile_background_tile: false + profile_link_color: 1DA1F2 + profile_sidebar_border_color: C0DEED + profile_sidebar_fill_color: DDEEF6 + profile_text_color: '333333' + profile_use_background_image: true + profile_image_url: http://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_image_url_https: https://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_banner_url: https://pbs.twimg.com/profile_banners/2975699229/1497946434 + default_profile: true + default_profile_image: false + following: + follow_request_sent: + notifications: +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/follow-unfollow.yml b/test/data/twitter/webhook_events/follow-unfollow.yml new file mode 100644 index 000000000..3b48b6380 --- /dev/null +++ b/test/data/twitter/webhook_events/follow-unfollow.yml @@ -0,0 +1,82 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +follow_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + type: unfollow + created_timestamp: '1576540568600' + target: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '1205290247124217856' + default_profile_image: false + profile_background_image_url: '' + friends_count: 1 + favourites_count: 0 + profile_link_color: -1 + profile_background_image_url_https: '' + utc_offset: 0 + screen_name: pennbrooke1 + is_translator: false + followers_count: 1 + name: pennbrooke + lang: '' + profile_use_background_image: false + created_at: Fri Dec 13 00:56:10 +0000 2019 + profile_text_color: -1 + notifications: false + protected: false + statuses_count: 2 + url: https://t.co/Y9Umsm0AJJ + contributors_enabled: false + default_profile: true + profile_sidebar_border_color: -1 + time_zone: '' + geo_enabled: false + verified: false + profile_image_url: http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png + following: false + profile_image_url_https: https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png + profile_background_tile: false + listed_count: 0 + profile_sidebar_fill_color: -1 + location: London + follow_request_sent: false + description: More like penn-broke, amirite? + profile_background_color: -1 + source: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '2975699229' + default_profile_image: false + profile_background_image_url: '' + friends_count: 57 + favourites_count: 20 + profile_link_color: -1 + profile_background_image_url_https: '' + utc_offset: 0 + screen_name: ScruffyMcG + is_translator: false + followers_count: 31 + name: Ryan Lue + lang: '' + profile_use_background_image: false + created_at: Tue Jan 13 02:38:37 +0000 2015 + profile_text_color: -1 + notifications: false + protected: false + statuses_count: 6 + url: https://t.co/Y9Umsm0AJJ + contributors_enabled: false + default_profile: true + profile_sidebar_border_color: -1 + time_zone: '' + geo_enabled: false + verified: false + profile_image_url: http://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + following: false + profile_image_url_https: https://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_background_tile: false + listed_count: 0 + profile_sidebar_fill_color: -1 + location: Taipei + follow_request_sent: false + description: I like turtles. + profile_background_color: -1 +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/follow.yml b/test/data/twitter/webhook_events/follow.yml new file mode 100644 index 000000000..ec58849a7 --- /dev/null +++ b/test/data/twitter/webhook_events/follow.yml @@ -0,0 +1,82 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +follow_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + type: follow + created_timestamp: '1576212630171' + target: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '1205290247124217856' + default_profile_image: false + profile_background_image_url: '' + friends_count: 0 + favourites_count: 0 + profile_link_color: -1 + profile_background_image_url_https: '' + utc_offset: 0 + screen_name: pennbrooke1 + is_translator: false + followers_count: 1 + name: pennbrooke + lang: '' + profile_use_background_image: false + created_at: Fri Dec 13 00:56:10 +0000 2019 + profile_text_color: -1 + notifications: false + protected: false + statuses_count: 2 + url: https://t.co/Y9Umsm0AJJ + contributors_enabled: false + default_profile: true + profile_sidebar_border_color: -1 + time_zone: '' + geo_enabled: false + verified: false + profile_image_url: http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png + following: false + profile_image_url_https: https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png + profile_background_tile: false + listed_count: 0 + profile_sidebar_fill_color: -1 + location: London + follow_request_sent: false + description: More like penn-broke, amirite? + profile_background_color: -1 + source: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: '2975699229' + default_profile_image: false + profile_background_image_url: '' + friends_count: 57 + favourites_count: 19 + profile_link_color: -1 + profile_background_image_url_https: '' + utc_offset: 0 + screen_name: ScruffyMcG + is_translator: false + followers_count: 30 + name: Ryan Lue + lang: '' + profile_use_background_image: false + created_at: Tue Jan 13 02:38:37 +0000 2015 + profile_text_color: -1 + notifications: false + protected: false + statuses_count: 6 + url: https://t.co/Y9Umsm0AJJ + contributors_enabled: false + default_profile: true + profile_sidebar_border_color: -1 + time_zone: '' + geo_enabled: false + verified: false + profile_image_url: http://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + following: false + profile_image_url_https: https://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_background_tile: false + listed_count: 0 + profile_sidebar_fill_color: -1 + location: Taipei + follow_request_sent: false + description: I like turtles. + profile_background_color: -1 +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/tweet_create-outgoing.yml b/test/data/twitter/webhook_events/tweet_create-outgoing.yml new file mode 100644 index 000000000..a2a9f8cb6 --- /dev/null +++ b/test/data/twitter/webhook_events/tweet_create-outgoing.yml @@ -0,0 +1,76 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +tweet_create_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + created_at: Thu Dec 19 10:37:42 +0000 2019 + id: 1207610954160037890 + id_str: '1207610954160037890' + text: Bleep bloop. + source: Twitter Web App + truncated: false + in_reply_to_status_id: + in_reply_to_status_id_str: + in_reply_to_user_id: + in_reply_to_user_id_str: + in_reply_to_screen_name: + user: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: 2975699229 + id_str: '2975699229' + name: Ryan Lue + screen_name: ScruffyMcG + location: Taipei + url: http://ryanlue.com + description: I like turtles. + translator_type: none + protected: false + verified: false + followers_count: 31 + friends_count: 56 + listed_count: 0 + favourites_count: 20 + statuses_count: 7 + created_at: Tue Jan 13 02:38:37 +0000 2015 + utc_offset: + time_zone: + geo_enabled: false + lang: + contributors_enabled: false + is_translator: false + profile_background_color: C0DEED + profile_background_image_url: http://abs.twimg.com/images/themes/theme1/bg.png + profile_background_image_url_https: https://abs.twimg.com/images/themes/theme1/bg.png + profile_background_tile: false + profile_link_color: 1DA1F2 + profile_sidebar_border_color: C0DEED + profile_sidebar_fill_color: DDEEF6 + profile_text_color: '333333' + profile_use_background_image: true + profile_image_url: http://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_image_url_https: https://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_banner_url: https://pbs.twimg.com/profile_banners/2975699229/1497946434 + default_profile: true + default_profile_image: false + following: + follow_request_sent: + notifications: + geo: + coordinates: + place: + contributors: + is_quote_status: false + quote_count: 0 + reply_count: 0 + retweet_count: 0 + favorite_count: 0 + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + urls: [] + user_mentions: [] + symbols: [] + favorited: false + retweeted: false + filter_level: low + lang: en + timestamp_ms: '1576751862455' +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/tweet_create-response.yml b/test/data/twitter/webhook_events/tweet_create-response.yml new file mode 100644 index 000000000..b00f45922 --- /dev/null +++ b/test/data/twitter/webhook_events/tweet_create-response.yml @@ -0,0 +1,87 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +user_has_blocked: false +tweet_create_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + created_at: Thu Dec 19 11:27:49 +0000 2019 + id: 1207623566339919872 + id_str: '1207623566339919872' + text: "@ScruffyMcG This is only a test." + display_text_range: + - 12 + - 32 + source: Twitter Web App + truncated: false + in_reply_to_status_id: 1205348660982083584 + in_reply_to_status_id_str: '1205348660982083584' + in_reply_to_user_id: 1205290247124217856 + in_reply_to_user_id_str: '1205290247124217856' + in_reply_to_screen_name: pennbrooke1 + user: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: 1205290247124217856 + id_str: '1205290247124217856' + name: pennbrooke + screen_name: pennbrooke1 + location: London + url: https://t.co/Y9Umsm0AJJ + description: More like penn-broke, amirite? + translator_type: none + protected: false + verified: false + followers_count: 0 + friends_count: 1 + listed_count: 0 + favourites_count: 0 + statuses_count: 3 + created_at: Fri Dec 13 00:56:10 +0000 2019 + utc_offset: + time_zone: + geo_enabled: false + lang: + contributors_enabled: false + is_translator: false + profile_background_color: F5F8FA + profile_background_image_url: '' + profile_background_image_url_https: '' + profile_background_tile: false + profile_link_color: 1DA1F2 + profile_sidebar_border_color: C0DEED + profile_sidebar_fill_color: DDEEF6 + profile_text_color: '333333' + profile_use_background_image: true + 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 + default_profile: true + default_profile_image: false + following: + follow_request_sent: + notifications: + geo: + coordinates: + place: + contributors: + is_quote_status: false + quote_count: 0 + reply_count: 0 + retweet_count: 0 + favorite_count: 0 + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + urls: [] + user_mentions: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + screen_name: ScruffyMcG + name: Ryan Lue + id: 2975699229 + id_str: '2975699229' + indices: + - 0 + - 11 + symbols: [] + favorited: false + retweeted: false + filter_level: low + lang: en + timestamp_ms: '1576754869433' +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/tweet_create-retweet.yml b/test/data/twitter/webhook_events/tweet_create-retweet.yml new file mode 100644 index 000000000..76e7a5adf --- /dev/null +++ b/test/data/twitter/webhook_events/tweet_create-retweet.yml @@ -0,0 +1,171 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +tweet_create_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + created_at: Fri Dec 13 04:48:35 +0000 2019 + id: 1205348768473899008 + id_str: '1205348768473899008' + text: 'RT @ScruffyMcG: Verifying myself: I am ryanlue on Keybase.io. 5Ts1MQtWo3WOaJ48lZqiVJ3G-96xWzqvCIx3 + / https://t.co/ukrDDemda2' + source: Twitter Web App + truncated: false + in_reply_to_status_id: + in_reply_to_status_id_str: + in_reply_to_user_id: + in_reply_to_user_id_str: + in_reply_to_screen_name: + user: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: 1205290247124217856 + id_str: '1205290247124217856' + name: pennbrooke + screen_name: pennbrooke1 + location: London + url: https://t.co/Y9Umsm0AJJ + description: More like penn-broke, amirite? + translator_type: none + protected: false + verified: false + followers_count: 0 + friends_count: 0 + listed_count: 0 + favourites_count: 0 + statuses_count: 2 + created_at: Fri Dec 13 00:56:10 +0000 2019 + utc_offset: + time_zone: + geo_enabled: false + lang: + contributors_enabled: false + is_translator: false + profile_background_color: F5F8FA + profile_background_image_url: '' + profile_background_image_url_https: '' + profile_background_tile: false + profile_link_color: 1DA1F2 + profile_sidebar_border_color: C0DEED + profile_sidebar_fill_color: DDEEF6 + profile_text_color: '333333' + profile_use_background_image: true + 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 + default_profile: true + default_profile_image: false + following: + follow_request_sent: + notifications: + geo: + coordinates: + place: + contributors: + retweeted_status: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + created_at: Fri Jun 17 08:51:11 +0000 2016 + id: 743727678734245889 + id_str: '743727678734245889' + text: 'Verifying myself: I am ryanlue on Keybase.io. 5Ts1MQtWo3WOaJ48lZqiVJ3G-96xWzqvCIx3 + / https://t.co/ukrDDemda2' + source: Twitter Web Client + truncated: false + in_reply_to_status_id: + in_reply_to_status_id_str: + in_reply_to_user_id: + in_reply_to_user_id_str: + in_reply_to_screen_name: + user: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: 2975699229 + id_str: '2975699229' + name: Ryan Lue + screen_name: ScruffyMcG + location: Taipei + url: http://ryanlue.com + description: I like turtles. + translator_type: none + protected: false + verified: false + followers_count: 30 + friends_count: 56 + listed_count: 0 + favourites_count: 19 + statuses_count: 6 + created_at: Tue Jan 13 02:38:37 +0000 2015 + utc_offset: + time_zone: + geo_enabled: false + lang: + contributors_enabled: false + is_translator: false + profile_background_color: C0DEED + profile_background_image_url: http://abs.twimg.com/images/themes/theme1/bg.png + profile_background_image_url_https: https://abs.twimg.com/images/themes/theme1/bg.png + profile_background_tile: false + profile_link_color: 1DA1F2 + profile_sidebar_border_color: C0DEED + profile_sidebar_fill_color: DDEEF6 + profile_text_color: '333333' + profile_use_background_image: true + profile_image_url: http://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_image_url_https: https://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_banner_url: https://pbs.twimg.com/profile_banners/2975699229/1497946434 + default_profile: true + default_profile_image: false + following: + follow_request_sent: + notifications: + geo: + coordinates: + place: + contributors: + is_quote_status: false + quote_count: 0 + reply_count: 0 + retweet_count: 1 + favorite_count: 4 + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + urls: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + url: https://t.co/ukrDDemda2 + expanded_url: https://keybase.io/ryanlue/sigs/5Ts1MQtWo3WOaJ48lZqiVJ3G-96xWzqvCIx3 + display_url: keybase.io/ryanlue/sigs/5… + indices: + - 85 + - 108 + user_mentions: [] + symbols: [] + favorited: false + retweeted: false + possibly_sensitive: false + filter_level: low + lang: fi + is_quote_status: false + quote_count: 0 + reply_count: 0 + retweet_count: 0 + favorite_count: 0 + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + urls: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + url: https://t.co/ukrDDemda2 + expanded_url: https://keybase.io/ryanlue/sigs/5Ts1MQtWo3WOaJ48lZqiVJ3G-96xWzqvCIx3 + display_url: keybase.io/ryanlue/sigs/5… + indices: + - 101 + - 124 + user_mentions: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + screen_name: ScruffyMcG + name: Ryan Lue + id: 2975699229 + id_str: '2975699229' + indices: + - 3 + - 14 + symbols: [] + favorited: false + retweeted: false + possibly_sensitive: false + filter_level: low + lang: fi + timestamp_ms: '1576212515358' +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/tweet_create-user_mention.yml b/test/data/twitter/webhook_events/tweet_create-user_mention.yml new file mode 100644 index 000000000..d4fde0389 --- /dev/null +++ b/test/data/twitter/webhook_events/tweet_create-user_mention.yml @@ -0,0 +1,84 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +user_has_blocked: false +tweet_create_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + created_at: Fri Dec 13 04:48:09 +0000 2019 + id: 1205348660982083584 + id_str: '1205348660982083584' + text: "@ScruffyMcG This is a test of Twitter API webhooks." + source: Twitter Web App + truncated: false + in_reply_to_status_id: + in_reply_to_status_id_str: + in_reply_to_user_id: 2975699229 + in_reply_to_user_id_str: '2975699229' + in_reply_to_screen_name: ScruffyMcG + user: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: 1205290247124217856 + id_str: '1205290247124217856' + name: pennbrooke + screen_name: pennbrooke1 + location: London + url: https://t.co/Y9Umsm0AJJ + description: More like penn-broke, amirite? + translator_type: none + protected: false + verified: false + followers_count: 0 + friends_count: 0 + listed_count: 0 + favourites_count: 0 + statuses_count: 1 + created_at: Fri Dec 13 00:56:10 +0000 2019 + utc_offset: + time_zone: + geo_enabled: false + lang: + contributors_enabled: false + is_translator: false + profile_background_color: F5F8FA + profile_background_image_url: '' + profile_background_image_url_https: '' + profile_background_tile: false + profile_link_color: 1DA1F2 + profile_sidebar_border_color: C0DEED + profile_sidebar_fill_color: DDEEF6 + profile_text_color: '333333' + profile_use_background_image: true + 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 + default_profile: true + default_profile_image: false + following: + follow_request_sent: + notifications: + geo: + coordinates: + place: + contributors: + is_quote_status: false + quote_count: 0 + reply_count: 0 + retweet_count: 0 + favorite_count: 0 + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + urls: [] + user_mentions: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + screen_name: ScruffyMcG + name: Ryan Lue + id: 2975699229 + id_str: '2975699229' + indices: + - 0 + - 11 + symbols: [] + favorited: false + retweeted: false + filter_level: low + lang: en + timestamp_ms: '1576212489730' +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/tweet_create-user_mention_extended.yml b/test/data/twitter/webhook_events/tweet_create-user_mention_extended.yml new file mode 100644 index 000000000..0f67fb483 --- /dev/null +++ b/test/data/twitter/webhook_events/tweet_create-user_mention_extended.yml @@ -0,0 +1,113 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +user_has_blocked: false +tweet_create_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + created_at: Mon Dec 23 13:03:29 +0000 2019 + id: 1209097192914313216 + id_str: '1209097192914313216' + text: |- + @ScruffyMcG + This is a test of Zammad's handling of extended tweets. When Twitter sends tweet data (as JSON) to web… https://t.co/cXMQkk9sQb + source: Twitter Web App + truncated: true + in_reply_to_status_id: + in_reply_to_status_id_str: + in_reply_to_user_id: 2975699229 + in_reply_to_user_id_str: '2975699229' + in_reply_to_screen_name: ScruffyMcG + user: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: 1205290247124217856 + id_str: '1205290247124217856' + name: pennbrooke + screen_name: pennbrooke1 + location: London + url: https://t.co/Y9Umsm0AJJ + description: More like penn-broke, amirite? + translator_type: none + protected: false + verified: false + followers_count: 0 + friends_count: 1 + listed_count: 0 + favourites_count: 0 + statuses_count: 5 + created_at: Fri Dec 13 00:56:10 +0000 2019 + utc_offset: + time_zone: + geo_enabled: false + lang: + contributors_enabled: false + is_translator: false + profile_background_color: F5F8FA + profile_background_image_url: '' + profile_background_image_url_https: '' + profile_background_tile: false + profile_link_color: 1DA1F2 + profile_sidebar_border_color: C0DEED + profile_sidebar_fill_color: DDEEF6 + profile_text_color: '333333' + profile_use_background_image: true + 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 + default_profile: true + default_profile_image: false + following: + follow_request_sent: + notifications: + geo: + coordinates: + place: + contributors: + is_quote_status: false + extended_tweet: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + full_text: |- + @ScruffyMcG + This is a test of Zammad's handling of extended tweets. When Twitter sends tweet data (as JSON) to webhook endpoints, tweets exceeding 140 characters are truncated (for backwards compatibility). The full tweet body is passed in an "extended_tweet" property. + display_text_range: + - 0 + - 270 + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + urls: [] + user_mentions: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + screen_name: ScruffyMcG + name: Ryan Lue + id: 2975699229 + id_str: '2975699229' + indices: + - 0 + - 11 + symbols: [] + quote_count: 0 + reply_count: 0 + retweet_count: 0 + favorite_count: 0 + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + urls: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + url: https://t.co/cXMQkk9sQb + expanded_url: https://twitter.com/i/web/status/1209097192914313216 + display_url: twitter.com/i/web/status/1… + indices: + - 117 + - 140 + user_mentions: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + screen_name: ScruffyMcG + name: Ryan Lue + id: 2975699229 + id_str: '2975699229' + indices: + - 0 + - 11 + symbols: [] + favorited: false + retweeted: false + filter_level: low + lang: en + timestamp_ms: '1577106209387' +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/tweet_create-user_mention_multiple.yml b/test/data/twitter/webhook_events/tweet_create-user_mention_multiple.yml new file mode 100644 index 000000000..efa417dd9 --- /dev/null +++ b/test/data/twitter/webhook_events/tweet_create-user_mention_multiple.yml @@ -0,0 +1,92 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +user_has_blocked: false +tweet_create_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + created_at: Mon Dec 23 13:24:07 +0000 2019 + id: 1209102385076826113 + id_str: '1209102385076826113' + text: "@ScruffyMcG @MrThorstenEckel This is a test of Twitter API webhooks." + source: Twitter Web App + truncated: false + in_reply_to_status_id: + in_reply_to_status_id_str: + in_reply_to_user_id: 2975699229 + in_reply_to_user_id_str: '2975699229' + in_reply_to_screen_name: ScruffyMcG + user: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: 1205290247124217856 + id_str: '1205290247124217856' + name: pennbrooke + screen_name: pennbrooke1 + location: + url: + description: + translator_type: none + protected: false + verified: false + followers_count: 0 + friends_count: 1 + listed_count: 0 + favourites_count: 0 + statuses_count: 8 + created_at: Fri Dec 13 00:56:10 +0000 2019 + utc_offset: + time_zone: + geo_enabled: false + lang: + contributors_enabled: false + is_translator: false + profile_background_color: F5F8FA + profile_background_image_url: '' + profile_background_image_url_https: '' + profile_background_tile: false + profile_link_color: 1DA1F2 + profile_sidebar_border_color: C0DEED + profile_sidebar_fill_color: DDEEF6 + profile_text_color: '333333' + profile_use_background_image: true + 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 + default_profile: true + default_profile_image: false + following: + follow_request_sent: + notifications: + geo: + coordinates: + place: + contributors: + is_quote_status: false + quote_count: 0 + reply_count: 0 + retweet_count: 0 + favorite_count: 0 + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + urls: [] + user_mentions: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + screen_name: ScruffyMcG + name: Ryan Lue + id: 2975699229 + id_str: '2975699229' + indices: + - 0 + - 11 + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + screen_name: MrThorstenEckel + name: Thorsten Eckel + id: 2474118319 + id_str: '2474118319' + indices: + - 12 + - 28 + symbols: [] + favorited: false + retweeted: false + filter_level: low + lang: en + timestamp_ms: '1577107447295' +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/tweet_create-user_mention_outgoing.yml b/test/data/twitter/webhook_events/tweet_create-user_mention_outgoing.yml new file mode 100644 index 000000000..17603548d --- /dev/null +++ b/test/data/twitter/webhook_events/tweet_create-user_mention_outgoing.yml @@ -0,0 +1,85 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +user_has_blocked: false +tweet_create_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + created_at: Mon Dec 23 15:06:52 +0000 2019 + id: 1209128243955027968 + id_str: '1209128243955027968' + text: "@ScruffyMcG Who do you think you are?" + source: Twitter Web App + truncated: false + in_reply_to_status_id: + in_reply_to_status_id_str: + in_reply_to_user_id: 2975699229 + in_reply_to_user_id_str: '2975699229' + in_reply_to_screen_name: ScruffyMcG + user: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: 2975699229 + id_str: '2975699229' + name: Ryan Lue + screen_name: ScruffyMcG + location: Taipei + url: http://ryanlue.com + description: I like turtles. + translator_type: none + protected: false + verified: false + followers_count: 31 + friends_count: 56 + listed_count: 0 + favourites_count: 20 + statuses_count: 8 + created_at: Tue Jan 13 02:38:37 +0000 2015 + utc_offset: + time_zone: + geo_enabled: false + lang: + contributors_enabled: false + is_translator: false + profile_background_color: C0DEED + profile_background_image_url: http://abs.twimg.com/images/themes/theme1/bg.png + profile_background_image_url_https: https://abs.twimg.com/images/themes/theme1/bg.png + profile_background_tile: false + profile_link_color: 1DA1F2 + profile_sidebar_border_color: C0DEED + profile_sidebar_fill_color: DDEEF6 + profile_text_color: '333333' + profile_use_background_image: true + profile_image_url: http://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_image_url_https: https://pbs.twimg.com/profile_images/877073703330238464/fnxECj4z_normal.jpg + profile_banner_url: https://pbs.twimg.com/profile_banners/2975699229/1497946434 + default_profile: true + default_profile_image: false + following: + follow_request_sent: + notifications: + geo: + coordinates: + place: + contributors: + is_quote_status: false + quote_count: 0 + reply_count: 0 + retweet_count: 0 + favorite_count: 0 + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + urls: [] + user_mentions: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + screen_name: ScruffyMcG + name: Ryan Lue + id: 2975699229 + id_str: '2975699229' + indices: + - 0 + - 11 + symbols: [] + favorited: false + retweeted: false + filter_level: low + lang: en + timestamp_ms: '1577113612532' +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/tweet_create-user_mention_with_media.yml b/test/data/twitter/webhook_events/tweet_create-user_mention_with_media.yml new file mode 100644 index 000000000..3ea958295 --- /dev/null +++ b/test/data/twitter/webhook_events/tweet_create-user_mention_with_media.yml @@ -0,0 +1,158 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +user_has_blocked: false +tweet_create_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + created_at: Mon Dec 23 13:20:23 +0000 2019 + id: 1209101446706122752 + id_str: '1209101446706122752' + text: "@ScruffyMcG https://t.co/DHD7xWvvh5" + display_text_range: + - 0 + - 11 + source: Twitter Web App + truncated: false + in_reply_to_status_id: + in_reply_to_status_id_str: + in_reply_to_user_id: 2975699229 + in_reply_to_user_id_str: '2975699229' + in_reply_to_screen_name: ScruffyMcG + user: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: 1205290247124217856 + id_str: '1205290247124217856' + name: pennbrooke + screen_name: pennbrooke1 + location: + url: + description: + translator_type: none + protected: false + verified: false + followers_count: 0 + friends_count: 1 + listed_count: 0 + favourites_count: 0 + statuses_count: 7 + created_at: Fri Dec 13 00:56:10 +0000 2019 + utc_offset: + time_zone: + geo_enabled: false + lang: + contributors_enabled: false + is_translator: false + profile_background_color: F5F8FA + profile_background_image_url: '' + profile_background_image_url_https: '' + profile_background_tile: false + profile_link_color: 1DA1F2 + profile_sidebar_border_color: C0DEED + profile_sidebar_fill_color: DDEEF6 + profile_text_color: '333333' + profile_use_background_image: true + 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 + default_profile: true + default_profile_image: false + following: + follow_request_sent: + notifications: + geo: + coordinates: + place: + contributors: + is_quote_status: false + quote_count: 0 + reply_count: 0 + retweet_count: 0 + favorite_count: 0 + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + urls: [] + user_mentions: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + screen_name: ScruffyMcG + name: Ryan Lue + id: 2975699229 + id_str: '2975699229' + indices: + - 0 + - 11 + symbols: [] + media: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: 1209101411717242881 + id_str: '1209101411717242881' + indices: + - 12 + - 35 + media_url: http://pbs.twimg.com/tweet_video_thumb/EMeXU-7UYAEm71w.jpg + media_url_https: https://pbs.twimg.com/tweet_video_thumb/EMeXU-7UYAEm71w.jpg + url: https://t.co/DHD7xWvvh5 + display_url: pic.twitter.com/DHD7xWvvh5 + expanded_url: https://twitter.com/pennbrooke1/status/1209101446706122752/photo/1 + type: photo + sizes: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + small: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + w: 270 + h: 480 + resize: fit + thumb: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + w: 150 + h: 150 + resize: crop + medium: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + w: 270 + h: 480 + resize: fit + large: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + w: 270 + h: 480 + resize: fit + extended_entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + media: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: 1209101411717242881 + id_str: '1209101411717242881' + indices: + - 12 + - 35 + media_url: http://pbs.twimg.com/tweet_video_thumb/EMeXU-7UYAEm71w.jpg + media_url_https: https://pbs.twimg.com/tweet_video_thumb/EMeXU-7UYAEm71w.jpg + url: https://t.co/DHD7xWvvh5 + display_url: pic.twitter.com/DHD7xWvvh5 + expanded_url: https://twitter.com/pennbrooke1/status/1209101446706122752/photo/1 + type: animated_gif + video_info: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + aspect_ratio: + - 9 + - 16 + variants: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + bitrate: 0 + content_type: video/mp4 + url: https://video.twimg.com/tweet_video/EMeXU-7UYAEm71w.mp4 + sizes: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + small: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + w: 270 + h: 480 + resize: fit + thumb: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + w: 150 + h: 150 + resize: crop + medium: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + w: 270 + h: 480 + resize: fit + large: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + w: 270 + h: 480 + resize: fit + favorited: false + retweeted: false + possibly_sensitive: false + filter_level: low + lang: und + timestamp_ms: '1577107223570' +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/twitter/webhook_events/tweet_create-user_mention_with_url.yml b/test/data/twitter/webhook_events/tweet_create-user_mention_with_url.yml new file mode 100644 index 000000000..f363e84b6 --- /dev/null +++ b/test/data/twitter/webhook_events/tweet_create-user_mention_with_url.yml @@ -0,0 +1,92 @@ +--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess +for_user_id: '2975699229' +user_has_blocked: false +tweet_create_events: +- !ruby/hash:ActiveSupport::HashWithIndifferentAccess + created_at: Mon Dec 23 13:12:50 +0000 2019 + id: 1209099545700786176 + id_str: '1209099545700786176' + text: "@ScruffyMcG https://t.co/AyVD0yiqOT" + source: Twitter Web App + truncated: false + in_reply_to_status_id: + in_reply_to_status_id_str: + in_reply_to_user_id: 2975699229 + in_reply_to_user_id_str: '2975699229' + in_reply_to_screen_name: ScruffyMcG + user: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + id: 1205290247124217856 + id_str: '1205290247124217856' + name: pennbrooke + screen_name: pennbrooke1 + location: + url: + description: + translator_type: none + protected: false + verified: false + followers_count: 0 + friends_count: 1 + listed_count: 0 + favourites_count: 0 + statuses_count: 6 + created_at: Fri Dec 13 00:56:10 +0000 2019 + utc_offset: + time_zone: + geo_enabled: false + lang: + contributors_enabled: false + is_translator: false + profile_background_color: F5F8FA + profile_background_image_url: '' + profile_background_image_url_https: '' + profile_background_tile: false + profile_link_color: 1DA1F2 + profile_sidebar_border_color: C0DEED + profile_sidebar_fill_color: DDEEF6 + profile_text_color: '333333' + profile_use_background_image: true + 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 + default_profile: true + default_profile_image: false + following: + follow_request_sent: + notifications: + geo: + coordinates: + place: + contributors: + is_quote_status: false + quote_count: 0 + reply_count: 0 + retweet_count: 0 + favorite_count: 0 + entities: !ruby/hash:ActiveSupport::HashWithIndifferentAccess + hashtags: [] + urls: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + url: https://t.co/AyVD0yiqOT + expanded_url: https://zammad.org/ + display_url: zammad.org + indices: + - 12 + - 35 + user_mentions: + - !ruby/hash:ActiveSupport::HashWithIndifferentAccess + screen_name: ScruffyMcG + name: Ryan Lue + id: 2975699229 + id_str: '2975699229' + indices: + - 0 + - 11 + symbols: [] + favorited: false + retweeted: false + possibly_sensitive: false + filter_level: low + lang: und + timestamp_ms: '1577106770335' +controller: channels_twitter +action: webhook_incoming diff --git a/test/data/vcr_cassettes/lib/twitter_sync/after_parent_tweet_has_been_deleted_creates_a_new_ticket.yml b/test/data/vcr_cassettes/lib/twitter_sync/after_parent_tweet_has_been_deleted_creates_a_new_ticket.yml new file mode 100644 index 000000000..abf2a2add --- /dev/null +++ b/test/data/vcr_cassettes/lib/twitter_sync/after_parent_tweet_has_been_deleted_creates_a_new_ticket.yml @@ -0,0 +1,247 @@ +--- +http_interactions: +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=London&sensor=true + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - maps.googleapis.com + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Fri, 27 Dec 2019 03:45:35 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Vary: + - Accept-Language + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '202' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=2 + body: + encoding: ASCII-8BIT + string: | + { + "error_message" : "You must use an API key to authenticate each request to Google Maps Platform APIs. For additional information, please refer to http://g.co/dev/maps-no-account", + "results" : [], + "status" : "REQUEST_DENIED" + } + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:35 GMT +- request: + method: get + uri: http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - abs.twimg.com + response: + status: + code: 200 + message: OK + headers: + Accept-Ranges: + - bytes + Access-Control-Allow-Origin: + - "*" + Content-Type: + - image/png + Date: + - Fri, 27 Dec 2019 03:45:35 GMT + Etag: + - '"QcuRPIhOAYpzfGh0vMtQAQ=="' + Expires: + - Sat, 26 Dec 2020 03:45:35 GMT + Last-Modified: + - Sat, 22 Dec 2018 15:21:58 GMT + Server: + - ECS (tpe/68A3) + Strict-Transport-Security: + - max-age=631138519 + Surrogate-Key: + - twitter-assets + X-Cache: + - HIT + X-Connection-Hash: + - 97b9e7d1341932a1fcad07625c3f5283 + X-Content-Type-Options: + - nosniff + X-Response-Time: + - '8' + X-Ton-Expected-Size: + - '504' + Content-Length: + - '504' + body: + encoding: ASCII-8BIT + string: !binary |- + iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAwFBMVEXM1t3K1dy5xc2xvca7xs+KmaVrfYtld4agrrhneYhoeoiksbufrbegrbe7xs66xs6Dk5+/ytKdqrWElKCDk6Byg5HG0NjEz9bH0dlqfIprfIuAkJ2ir7nJ09p1hpN0hZO9yNDH0tmbqbTBzNOzv8iqtsCotL6ntL7L1dy3w8yjsLqVpK+Onam8x9CRn6ttf43CzdWMm6dmeIeLmqauusSwvMVxgpCToa29ydFneIeRoKzK1Nttfo2yvseZp7KHl6OOgUPZAAAA80lEQVR4Ae3SA5oDQRgE0Aortm1zNub9T7Xm4O/+uMo7w8O/duNyezxuF3R5fX4+8vu80BII8lUoDA0RfhKFUizIT4JxqCT4RQIKSZokIUvRJAVZmiYZyLI0yUIWpEkQohwtcpDkaZGHpECLAkRFmpQgK9OkDFmFJhXIqjV+Ua9CoZHlJ9kGlJqtLF9lW01oaXe6vX6308aPMRiOxpPJeDQcQMd0Nuer+WwKlcXS4CfG3QKi1Zwm8xUEAYMWRkAZSbvT2qAtYw1bzQ0dbJqws6WjLWzs9nS038HqQMEBFscTBacjzDoUnWF2oegCsytFV7x6ADhgICRy9ELGAAAAAElFTkSuQmCC + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:35 GMT +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=London&sensor=true + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - maps.googleapis.com + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Fri, 27 Dec 2019 03:45:35 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Vary: + - Accept-Language + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '202' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=2 + body: + encoding: ASCII-8BIT + string: | + { + "error_message" : "You must use an API key to authenticate each request to Google Maps Platform APIs. For additional information, please refer to http://g.co/dev/maps-no-account", + "results" : [], + "status" : "REQUEST_DENIED" + } + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:35 GMT +- request: + method: get + uri: https://api.twitter.com/1.1/statuses/show/1207610954160037890.json + body: + encoding: UTF-8 + string: '' + headers: + User-Agent: + - TwitterRubyGem/6.2.0 + Authorization: + - OAuth oauth_consumer_key="", oauth_nonce="ccabd7f04882ae0e18d37758ac7c786b", + oauth_signature="", oauth_signature_method="HMAC-SHA1", + oauth_timestamp="1577418335", oauth_token="", + oauth_version="1.0" + Connection: + - close + Host: + - api.twitter.com + response: + status: + code: 404 + message: Not Found + 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: + - '67' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 27 Dec 2019 03:45:36 GMT + Expires: + - Tue, 31 Mar 1981 05:00:00 GMT + Last-Modified: + - Fri, 27 Dec 2019 03:45:36 GMT + Pragma: + - no-cache + Server: + - tsa_m + Set-Cookie: + - guest_id=v1%3A157741833648362103; Max-Age=63072000; Expires=Sun, 26 Dec 2021 + 03:45:36 GMT; Path=/; Domain=.twitter.com + - lang=en; Path=/ + - personalization_id="v1_fsbQON7x7dvQ0evtktqIBw=="; Max-Age=63072000; Expires=Sun, + 26 Dec 2021 03:45:36 GMT; Path=/; Domain=.twitter.com + Status: + - 404 Not Found + Strict-Transport-Security: + - max-age=631138519 + X-Connection-Hash: + - ccd18097041d5fe32735d89956045f2e + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Rate-Limit-Limit: + - '900' + X-Rate-Limit-Remaining: + - '897' + X-Rate-Limit-Reset: + - '1577419233' + X-Response-Time: + - '110' + X-Transaction: + - 001f68e7002996d0 + X-Twitter-Response-Tags: + - BouncerCompliant + X-Xss-Protection: + - '0' + body: + encoding: UTF-8 + string: '{"errors":[{"code":144,"message":"No status found with that ID."}]}' + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:36 GMT +recorded_with: VCR 4.0.0 diff --git a/test/data/vcr_cassettes/lib/twitter_sync/after_parent_tweet_has_been_deleted_silently_ignores_error_when_retrieving_parent_tweet.yml b/test/data/vcr_cassettes/lib/twitter_sync/after_parent_tweet_has_been_deleted_silently_ignores_error_when_retrieving_parent_tweet.yml new file mode 100644 index 000000000..fc4642894 --- /dev/null +++ b/test/data/vcr_cassettes/lib/twitter_sync/after_parent_tweet_has_been_deleted_silently_ignores_error_when_retrieving_parent_tweet.yml @@ -0,0 +1,247 @@ +--- +http_interactions: +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=London&sensor=true + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - maps.googleapis.com + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Fri, 27 Dec 2019 03:45:36 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Vary: + - Accept-Language + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '202' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=1 + body: + encoding: ASCII-8BIT + string: | + { + "error_message" : "You must use an API key to authenticate each request to Google Maps Platform APIs. For additional information, please refer to http://g.co/dev/maps-no-account", + "results" : [], + "status" : "REQUEST_DENIED" + } + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:36 GMT +- request: + method: get + uri: http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - abs.twimg.com + response: + status: + code: 200 + message: OK + headers: + Accept-Ranges: + - bytes + Access-Control-Allow-Origin: + - "*" + Content-Type: + - image/png + Date: + - Fri, 27 Dec 2019 03:45:36 GMT + Etag: + - '"QcuRPIhOAYpzfGh0vMtQAQ=="' + Expires: + - Sat, 26 Dec 2020 03:45:36 GMT + Last-Modified: + - Sat, 22 Dec 2018 15:21:58 GMT + Server: + - ECS (tpe/68A3) + Strict-Transport-Security: + - max-age=631138519 + Surrogate-Key: + - twitter-assets + X-Cache: + - HIT + X-Connection-Hash: + - 97b9e7d1341932a1fcad07625c3f5283 + X-Content-Type-Options: + - nosniff + X-Response-Time: + - '8' + X-Ton-Expected-Size: + - '504' + Content-Length: + - '504' + body: + encoding: ASCII-8BIT + string: !binary |- + iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAwFBMVEXM1t3K1dy5xc2xvca7xs+KmaVrfYtld4agrrhneYhoeoiksbufrbegrbe7xs66xs6Dk5+/ytKdqrWElKCDk6Byg5HG0NjEz9bH0dlqfIprfIuAkJ2ir7nJ09p1hpN0hZO9yNDH0tmbqbTBzNOzv8iqtsCotL6ntL7L1dy3w8yjsLqVpK+Onam8x9CRn6ttf43CzdWMm6dmeIeLmqauusSwvMVxgpCToa29ydFneIeRoKzK1Nttfo2yvseZp7KHl6OOgUPZAAAA80lEQVR4Ae3SA5oDQRgE0Aortm1zNub9T7Xm4O/+uMo7w8O/duNyezxuF3R5fX4+8vu80BII8lUoDA0RfhKFUizIT4JxqCT4RQIKSZokIUvRJAVZmiYZyLI0yUIWpEkQohwtcpDkaZGHpECLAkRFmpQgK9OkDFmFJhXIqjV+Ua9CoZHlJ9kGlJqtLF9lW01oaXe6vX6308aPMRiOxpPJeDQcQMd0Nuer+WwKlcXS4CfG3QKi1Zwm8xUEAYMWRkAZSbvT2qAtYw1bzQ0dbJqws6WjLWzs9nS038HqQMEBFscTBacjzDoUnWF2oegCsytFV7x6ADhgICRy9ELGAAAAAElFTkSuQmCC + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:36 GMT +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=London&sensor=true + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - maps.googleapis.com + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Fri, 27 Dec 2019 03:45:36 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Vary: + - Accept-Language + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '202' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=1 + body: + encoding: ASCII-8BIT + string: | + { + "error_message" : "You must use an API key to authenticate each request to Google Maps Platform APIs. For additional information, please refer to http://g.co/dev/maps-no-account", + "results" : [], + "status" : "REQUEST_DENIED" + } + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:37 GMT +- request: + method: get + uri: https://api.twitter.com/1.1/statuses/show/1207610954160037890.json + body: + encoding: UTF-8 + string: '' + headers: + User-Agent: + - TwitterRubyGem/6.2.0 + Authorization: + - OAuth oauth_consumer_key="", oauth_nonce="ab5fd57ec81489a5a223b56d9af20f0a", + oauth_signature="", oauth_signature_method="HMAC-SHA1", + oauth_timestamp="1577418337", oauth_token="", + oauth_version="1.0" + Connection: + - close + Host: + - api.twitter.com + response: + status: + code: 404 + message: Not Found + 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: + - '67' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 27 Dec 2019 03:45:37 GMT + Expires: + - Tue, 31 Mar 1981 05:00:00 GMT + Last-Modified: + - Fri, 27 Dec 2019 03:45:37 GMT + Pragma: + - no-cache + Server: + - tsa_m + Set-Cookie: + - guest_id=v1%3A157741833757006606; Max-Age=63072000; Expires=Sun, 26 Dec 2021 + 03:45:37 GMT; Path=/; Domain=.twitter.com + - lang=en; Path=/ + - personalization_id="v1_V1rxHe+i86BweSiS2JR1/A=="; Max-Age=63072000; Expires=Sun, + 26 Dec 2021 03:45:37 GMT; Path=/; Domain=.twitter.com + Status: + - 404 Not Found + Strict-Transport-Security: + - max-age=631138519 + X-Connection-Hash: + - ccc3c9dacffc76705aadbb69ebb5e093 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Rate-Limit-Limit: + - '900' + X-Rate-Limit-Remaining: + - '896' + X-Rate-Limit-Reset: + - '1577419233' + X-Response-Time: + - '110' + X-Transaction: + - 003a85720095790f + X-Twitter-Response-Tags: + - BouncerCompliant + X-Xss-Protection: + - '0' + body: + encoding: UTF-8 + string: '{"errors":[{"code":144,"message":"No status found with that ID."}]}' + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:37 GMT +recorded_with: VCR 4.0.0 diff --git a/test/data/vcr_cassettes/lib/twitter_sync/from_known_user_updates_the_sender_s_existing_avatar_record.yml b/test/data/vcr_cassettes/lib/twitter_sync/from_known_user_updates_the_sender_s_existing_avatar_record.yml new file mode 100644 index 000000000..de2f255ae --- /dev/null +++ b/test/data/vcr_cassettes/lib/twitter_sync/from_known_user_updates_the_sender_s_existing_avatar_record.yml @@ -0,0 +1,169 @@ +--- +http_interactions: +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=London&sensor=true + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - maps.googleapis.com + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Fri, 27 Dec 2019 03:45:27 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Vary: + - Accept-Language + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '202' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=1 + body: + encoding: ASCII-8BIT + string: | + { + "error_message" : "You must use an API key to authenticate each request to Google Maps Platform APIs. For additional information, please refer to http://g.co/dev/maps-no-account", + "results" : [], + "status" : "REQUEST_DENIED" + } + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:27 GMT +- request: + method: get + uri: http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - abs.twimg.com + response: + status: + code: 200 + message: OK + headers: + Accept-Ranges: + - bytes + Access-Control-Allow-Origin: + - "*" + Content-Type: + - image/png + Date: + - Fri, 27 Dec 2019 03:45:27 GMT + Etag: + - '"QcuRPIhOAYpzfGh0vMtQAQ=="' + Expires: + - Sat, 26 Dec 2020 03:45:27 GMT + Last-Modified: + - Sat, 22 Dec 2018 15:21:58 GMT + Server: + - ECS (tpe/68A3) + Strict-Transport-Security: + - max-age=631138519 + Surrogate-Key: + - twitter-assets + X-Cache: + - HIT + X-Connection-Hash: + - 97b9e7d1341932a1fcad07625c3f5283 + X-Content-Type-Options: + - nosniff + X-Response-Time: + - '8' + X-Ton-Expected-Size: + - '504' + Content-Length: + - '504' + body: + encoding: ASCII-8BIT + string: !binary |- + iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAwFBMVEXM1t3K1dy5xc2xvca7xs+KmaVrfYtld4agrrhneYhoeoiksbufrbegrbe7xs66xs6Dk5+/ytKdqrWElKCDk6Byg5HG0NjEz9bH0dlqfIprfIuAkJ2ir7nJ09p1hpN0hZO9yNDH0tmbqbTBzNOzv8iqtsCotL6ntL7L1dy3w8yjsLqVpK+Onam8x9CRn6ttf43CzdWMm6dmeIeLmqauusSwvMVxgpCToa29ydFneIeRoKzK1Nttfo2yvseZp7KHl6OOgUPZAAAA80lEQVR4Ae3SA5oDQRgE0Aortm1zNub9T7Xm4O/+uMo7w8O/duNyezxuF3R5fX4+8vu80BII8lUoDA0RfhKFUizIT4JxqCT4RQIKSZokIUvRJAVZmiYZyLI0yUIWpEkQohwtcpDkaZGHpECLAkRFmpQgK9OkDFmFJhXIqjV+Ua9CoZHlJ9kGlJqtLF9lW01oaXe6vX6308aPMRiOxpPJeDQcQMd0Nuer+WwKlcXS4CfG3QKi1Zwm8xUEAYMWRkAZSbvT2qAtYw1bzQ0dbJqws6WjLWzs9nS038HqQMEBFscTBacjzDoUnWF2oegCsytFV7x6ADhgICRy9ELGAAAAAElFTkSuQmCC + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:27 GMT +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=London&sensor=true + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - maps.googleapis.com + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Fri, 27 Dec 2019 03:45:27 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Vary: + - Accept-Language + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '202' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=1 + body: + encoding: ASCII-8BIT + string: | + { + "error_message" : "You must use an API key to authenticate each request to Google Maps Platform APIs. For additional information, please refer to http://g.co/dev/maps-no-account", + "results" : [], + "status" : "REQUEST_DENIED" + } + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:27 GMT +recorded_with: VCR 4.0.0 diff --git a/test/data/vcr_cassettes/lib/twitter_sync/from_unknown_user_creates_an_avatar_record_for_the_sender.yml b/test/data/vcr_cassettes/lib/twitter_sync/from_unknown_user_creates_an_avatar_record_for_the_sender.yml new file mode 100644 index 000000000..be82b8f9e --- /dev/null +++ b/test/data/vcr_cassettes/lib/twitter_sync/from_unknown_user_creates_an_avatar_record_for_the_sender.yml @@ -0,0 +1,169 @@ +--- +http_interactions: +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=London&sensor=true + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - maps.googleapis.com + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Fri, 27 Dec 2019 03:45:26 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Vary: + - Accept-Language + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '202' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=1 + body: + encoding: ASCII-8BIT + string: | + { + "error_message" : "You must use an API key to authenticate each request to Google Maps Platform APIs. For additional information, please refer to http://g.co/dev/maps-no-account", + "results" : [], + "status" : "REQUEST_DENIED" + } + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:26 GMT +- request: + method: get + uri: http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - abs.twimg.com + response: + status: + code: 200 + message: OK + headers: + Accept-Ranges: + - bytes + Access-Control-Allow-Origin: + - "*" + Content-Type: + - image/png + Date: + - Fri, 27 Dec 2019 03:45:26 GMT + Etag: + - '"QcuRPIhOAYpzfGh0vMtQAQ=="' + Expires: + - Sat, 26 Dec 2020 03:45:26 GMT + Last-Modified: + - Sat, 22 Dec 2018 15:21:58 GMT + Server: + - ECS (tpe/68A3) + Strict-Transport-Security: + - max-age=631138519 + Surrogate-Key: + - twitter-assets + X-Cache: + - HIT + X-Connection-Hash: + - 97b9e7d1341932a1fcad07625c3f5283 + X-Content-Type-Options: + - nosniff + X-Response-Time: + - '8' + X-Ton-Expected-Size: + - '504' + Content-Length: + - '504' + body: + encoding: ASCII-8BIT + string: !binary |- + iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAwFBMVEXM1t3K1dy5xc2xvca7xs+KmaVrfYtld4agrrhneYhoeoiksbufrbegrbe7xs66xs6Dk5+/ytKdqrWElKCDk6Byg5HG0NjEz9bH0dlqfIprfIuAkJ2ir7nJ09p1hpN0hZO9yNDH0tmbqbTBzNOzv8iqtsCotL6ntL7L1dy3w8yjsLqVpK+Onam8x9CRn6ttf43CzdWMm6dmeIeLmqauusSwvMVxgpCToa29ydFneIeRoKzK1Nttfo2yvseZp7KHl6OOgUPZAAAA80lEQVR4Ae3SA5oDQRgE0Aortm1zNub9T7Xm4O/+uMo7w8O/duNyezxuF3R5fX4+8vu80BII8lUoDA0RfhKFUizIT4JxqCT4RQIKSZokIUvRJAVZmiYZyLI0yUIWpEkQohwtcpDkaZGHpECLAkRFmpQgK9OkDFmFJhXIqjV+Ua9CoZHlJ9kGlJqtLF9lW01oaXe6vX6308aPMRiOxpPJeDQcQMd0Nuer+WwKlcXS4CfG3QKi1Zwm8xUEAYMWRkAZSbvT2qAtYw1bzQ0dbJqws6WjLWzs9nS038HqQMEBFscTBacjzDoUnWF2oegCsytFV7x6ADhgICRy9ELGAAAAAElFTkSuQmCC + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:26 GMT +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=London&sensor=true + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - maps.googleapis.com + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Fri, 27 Dec 2019 03:45:26 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Vary: + - Accept-Language + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '202' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=2 + body: + encoding: ASCII-8BIT + string: | + { + "error_message" : "You must use an API key to authenticate each request to Google Maps Platform APIs. For additional information, please refer to http://g.co/dev/maps-no-account", + "results" : [], + "status" : "REQUEST_DENIED" + } + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:26 GMT +recorded_with: VCR 4.0.0 diff --git a/test/data/vcr_cassettes/lib/twitter_sync/that_hasn_t_been_imported_yet_creates_a_new_ticket.yml b/test/data/vcr_cassettes/lib/twitter_sync/that_hasn_t_been_imported_yet_creates_a_new_ticket.yml new file mode 100644 index 000000000..74ed99d4f --- /dev/null +++ b/test/data/vcr_cassettes/lib/twitter_sync/that_hasn_t_been_imported_yet_creates_a_new_ticket.yml @@ -0,0 +1,307 @@ +--- +http_interactions: +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=London&sensor=true + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - maps.googleapis.com + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Fri, 27 Dec 2019 03:45:33 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Vary: + - Accept-Language + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '202' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=1 + body: + encoding: ASCII-8BIT + string: | + { + "error_message" : "You must use an API key to authenticate each request to Google Maps Platform APIs. For additional information, please refer to http://g.co/dev/maps-no-account", + "results" : [], + "status" : "REQUEST_DENIED" + } + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:33 GMT +- request: + method: get + uri: http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - abs.twimg.com + response: + status: + code: 200 + message: OK + headers: + Accept-Ranges: + - bytes + Access-Control-Allow-Origin: + - "*" + Content-Type: + - image/png + Date: + - Fri, 27 Dec 2019 03:45:33 GMT + Etag: + - '"QcuRPIhOAYpzfGh0vMtQAQ=="' + Expires: + - Sat, 26 Dec 2020 03:45:33 GMT + Last-Modified: + - Sat, 22 Dec 2018 15:21:58 GMT + Server: + - ECS (tpe/68A3) + Strict-Transport-Security: + - max-age=631138519 + Surrogate-Key: + - twitter-assets + X-Cache: + - HIT + X-Connection-Hash: + - 97b9e7d1341932a1fcad07625c3f5283 + X-Content-Type-Options: + - nosniff + X-Response-Time: + - '8' + X-Ton-Expected-Size: + - '504' + Content-Length: + - '504' + body: + encoding: ASCII-8BIT + string: !binary |- + iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAwFBMVEXM1t3K1dy5xc2xvca7xs+KmaVrfYtld4agrrhneYhoeoiksbufrbegrbe7xs66xs6Dk5+/ytKdqrWElKCDk6Byg5HG0NjEz9bH0dlqfIprfIuAkJ2ir7nJ09p1hpN0hZO9yNDH0tmbqbTBzNOzv8iqtsCotL6ntL7L1dy3w8yjsLqVpK+Onam8x9CRn6ttf43CzdWMm6dmeIeLmqauusSwvMVxgpCToa29ydFneIeRoKzK1Nttfo2yvseZp7KHl6OOgUPZAAAA80lEQVR4Ae3SA5oDQRgE0Aortm1zNub9T7Xm4O/+uMo7w8O/duNyezxuF3R5fX4+8vu80BII8lUoDA0RfhKFUizIT4JxqCT4RQIKSZokIUvRJAVZmiYZyLI0yUIWpEkQohwtcpDkaZGHpECLAkRFmpQgK9OkDFmFJhXIqjV+Ua9CoZHlJ9kGlJqtLF9lW01oaXe6vX6308aPMRiOxpPJeDQcQMd0Nuer+WwKlcXS4CfG3QKi1Zwm8xUEAYMWRkAZSbvT2qAtYw1bzQ0dbJqws6WjLWzs9nS038HqQMEBFscTBacjzDoUnWF2oegCsytFV7x6ADhgICRy9ELGAAAAAElFTkSuQmCC + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:33 GMT +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=London&sensor=true + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - maps.googleapis.com + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Fri, 27 Dec 2019 03:45:33 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Vary: + - Accept-Language + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '202' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=1 + body: + encoding: ASCII-8BIT + string: | + { + "error_message" : "You must use an API key to authenticate each request to Google Maps Platform APIs. For additional information, please refer to http://g.co/dev/maps-no-account", + "results" : [], + "status" : "REQUEST_DENIED" + } + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:33 GMT +- request: + method: get + uri: https://api.twitter.com/1.1/statuses/show/1205348660982083584.json + body: + encoding: UTF-8 + string: '' + headers: + User-Agent: + - TwitterRubyGem/6.2.0 + Authorization: + - OAuth oauth_consumer_key="", oauth_nonce="fbd8eff18f7b7b63f9cb7a4244a249d9", + oauth_signature="", oauth_signature_method="HMAC-SHA1", + oauth_timestamp="1577418333", oauth_token="", + oauth_version="1.0" + Connection: + - close + 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: + - '2077' + Content-Type: + - application/json;charset=utf-8 + Date: + - Fri, 27 Dec 2019 03:45:34 GMT + Expires: + - Tue, 31 Mar 1981 05:00:00 GMT + Last-Modified: + - Fri, 27 Dec 2019 03:45:33 GMT + Pragma: + - no-cache + Server: + - tsa_m + Set-Cookie: + - guest_id=v1%3A157741833397608077; Max-Age=63072000; Expires=Sun, 26 Dec 2021 + 03:45:33 GMT; Path=/; Domain=.twitter.com + - lang=en; Path=/ + - personalization_id="v1_t/Cj2A8olRHhad2BmhCbcw=="; Max-Age=63072000; Expires=Sun, + 26 Dec 2021 03:45:33 GMT; Path=/; Domain=.twitter.com + Status: + - 200 OK + Strict-Transport-Security: + - max-age=631138519 + X-Access-Level: + - read-write-directmessages + X-Connection-Hash: + - 66ad116b82e9bcba2664bd96d4057e41 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Rate-Limit-Limit: + - '900' + X-Rate-Limit-Remaining: + - '899' + X-Rate-Limit-Reset: + - '1577419233' + X-Response-Time: + - '127' + X-Transaction: + - '0091b54600e475b9' + X-Twitter-Response-Tags: + - BouncerCompliant + X-Xss-Protection: + - '0' + body: + encoding: UTF-8 + string: '{"created_at":"Fri Dec 13 04:48:09 +0000 2019","id":1205348660982083584,"id_str":"1205348660982083584","text":"@ScruffyMcG + This is a test of Twitter API webhooks.","truncated":false,"entities":{"hashtags":[],"symbols":[],"user_mentions":[{"screen_name":"ScruffyMcG","name":"Ryan + Lue","id":2975699229,"id_str":"2975699229","indices":[0,11]}],"urls":[]},"source":"\u003ca + href=\"https:\/\/mobile.twitter.com\" rel=\"nofollow\"\u003eTwitter Web App\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":2975699229,"in_reply_to_user_id_str":"2975699229","in_reply_to_screen_name":"ScruffyMcG","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":8,"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,"can_media_tag":true,"followed_by":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":"en"}' + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:34 GMT +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=London&sensor=true + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - maps.googleapis.com + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Fri, 27 Dec 2019 03:45:34 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Vary: + - Accept-Language + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '202' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=2 + body: + encoding: ASCII-8BIT + string: | + { + "error_message" : "You must use an API key to authenticate each request to Google Maps Platform APIs. For additional information, please refer to http://g.co/dev/maps-no-account", + "results" : [], + "status" : "REQUEST_DENIED" + } + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:34 GMT +recorded_with: VCR 4.0.0 diff --git a/test/data/vcr_cassettes/lib/twitter_sync/that_hasn_t_been_imported_yet_retrieves_the_parent_tweet_via_the_twitter_api.yml b/test/data/vcr_cassettes/lib/twitter_sync/that_hasn_t_been_imported_yet_retrieves_the_parent_tweet_via_the_twitter_api.yml new file mode 100644 index 000000000..6a75d4761 --- /dev/null +++ b/test/data/vcr_cassettes/lib/twitter_sync/that_hasn_t_been_imported_yet_retrieves_the_parent_tweet_via_the_twitter_api.yml @@ -0,0 +1,307 @@ +--- +http_interactions: +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=London&sensor=true + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - maps.googleapis.com + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Fri, 27 Dec 2019 03:45:34 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Vary: + - Accept-Language + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '202' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=2 + body: + encoding: ASCII-8BIT + string: | + { + "error_message" : "You must use an API key to authenticate each request to Google Maps Platform APIs. For additional information, please refer to http://g.co/dev/maps-no-account", + "results" : [], + "status" : "REQUEST_DENIED" + } + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:34 GMT +- request: + method: get + uri: http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - abs.twimg.com + response: + status: + code: 200 + message: OK + headers: + Accept-Ranges: + - bytes + Access-Control-Allow-Origin: + - "*" + Content-Type: + - image/png + Date: + - Fri, 27 Dec 2019 03:45:34 GMT + Etag: + - '"QcuRPIhOAYpzfGh0vMtQAQ=="' + Expires: + - Sat, 26 Dec 2020 03:45:34 GMT + Last-Modified: + - Sat, 22 Dec 2018 15:21:58 GMT + Server: + - ECS (tpe/68A3) + Strict-Transport-Security: + - max-age=631138519 + Surrogate-Key: + - twitter-assets + X-Cache: + - HIT + X-Connection-Hash: + - 97b9e7d1341932a1fcad07625c3f5283 + X-Content-Type-Options: + - nosniff + X-Response-Time: + - '8' + X-Ton-Expected-Size: + - '504' + Content-Length: + - '504' + body: + encoding: ASCII-8BIT + string: !binary |- + iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAwFBMVEXM1t3K1dy5xc2xvca7xs+KmaVrfYtld4agrrhneYhoeoiksbufrbegrbe7xs66xs6Dk5+/ytKdqrWElKCDk6Byg5HG0NjEz9bH0dlqfIprfIuAkJ2ir7nJ09p1hpN0hZO9yNDH0tmbqbTBzNOzv8iqtsCotL6ntL7L1dy3w8yjsLqVpK+Onam8x9CRn6ttf43CzdWMm6dmeIeLmqauusSwvMVxgpCToa29ydFneIeRoKzK1Nttfo2yvseZp7KHl6OOgUPZAAAA80lEQVR4Ae3SA5oDQRgE0Aortm1zNub9T7Xm4O/+uMo7w8O/duNyezxuF3R5fX4+8vu80BII8lUoDA0RfhKFUizIT4JxqCT4RQIKSZokIUvRJAVZmiYZyLI0yUIWpEkQohwtcpDkaZGHpECLAkRFmpQgK9OkDFmFJhXIqjV+Ua9CoZHlJ9kGlJqtLF9lW01oaXe6vX6308aPMRiOxpPJeDQcQMd0Nuer+WwKlcXS4CfG3QKi1Zwm8xUEAYMWRkAZSbvT2qAtYw1bzQ0dbJqws6WjLWzs9nS038HqQMEBFscTBacjzDoUnWF2oegCsytFV7x6ADhgICRy9ELGAAAAAElFTkSuQmCC + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:34 GMT +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=London&sensor=true + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - maps.googleapis.com + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Fri, 27 Dec 2019 03:45:34 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Vary: + - Accept-Language + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '202' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=2 + body: + encoding: ASCII-8BIT + string: | + { + "error_message" : "You must use an API key to authenticate each request to Google Maps Platform APIs. For additional information, please refer to http://g.co/dev/maps-no-account", + "results" : [], + "status" : "REQUEST_DENIED" + } + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:34 GMT +- request: + method: get + uri: https://api.twitter.com/1.1/statuses/show/1205348660982083584.json + body: + encoding: UTF-8 + string: '' + headers: + User-Agent: + - TwitterRubyGem/6.2.0 + Authorization: + - OAuth oauth_consumer_key="", oauth_nonce="4754f26af6a3a66b618d2b6af10f4c6f", + oauth_signature="", oauth_signature_method="HMAC-SHA1", + oauth_timestamp="1577418334", oauth_token="", + oauth_version="1.0" + Connection: + - close + 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: + - '2077' + Content-Type: + - application/json;charset=utf-8 + Date: + - Fri, 27 Dec 2019 03:45:35 GMT + Expires: + - Tue, 31 Mar 1981 05:00:00 GMT + Last-Modified: + - Fri, 27 Dec 2019 03:45:35 GMT + Pragma: + - no-cache + Server: + - tsa_m + Set-Cookie: + - guest_id=v1%3A157741833523952118; Max-Age=63072000; Expires=Sun, 26 Dec 2021 + 03:45:35 GMT; Path=/; Domain=.twitter.com + - lang=en; Path=/ + - personalization_id="v1_i22bdfR7BX3UE1wD7pcLEA=="; Max-Age=63072000; Expires=Sun, + 26 Dec 2021 03:45:35 GMT; Path=/; Domain=.twitter.com + Status: + - 200 OK + Strict-Transport-Security: + - max-age=631138519 + X-Access-Level: + - read-write-directmessages + X-Connection-Hash: + - 61933ead1b9e05a273d158cbcf6e2fcd + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Rate-Limit-Limit: + - '900' + X-Rate-Limit-Remaining: + - '898' + X-Rate-Limit-Reset: + - '1577419233' + X-Response-Time: + - '126' + X-Transaction: + - 0041e652004e06dc + X-Twitter-Response-Tags: + - BouncerCompliant + X-Xss-Protection: + - '0' + body: + encoding: UTF-8 + string: '{"created_at":"Fri Dec 13 04:48:09 +0000 2019","id":1205348660982083584,"id_str":"1205348660982083584","text":"@ScruffyMcG + This is a test of Twitter API webhooks.","truncated":false,"entities":{"hashtags":[],"symbols":[],"user_mentions":[{"screen_name":"ScruffyMcG","name":"Ryan + Lue","id":2975699229,"id_str":"2975699229","indices":[0,11]}],"urls":[]},"source":"\u003ca + href=\"https:\/\/mobile.twitter.com\" rel=\"nofollow\"\u003eTwitter Web App\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":2975699229,"in_reply_to_user_id_str":"2975699229","in_reply_to_screen_name":"ScruffyMcG","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":8,"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,"can_media_tag":true,"followed_by":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":"en"}' + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:35 GMT +- request: + method: get + uri: http://maps.googleapis.com/maps/api/geocode/json?address=London&sensor=true + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - maps.googleapis.com + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Date: + - Fri, 27 Dec 2019 03:45:35 GMT + Pragma: + - no-cache + Expires: + - Fri, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, must-revalidate + Vary: + - Accept-Language + Access-Control-Allow-Origin: + - "*" + Server: + - mafe + Content-Length: + - '202' + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + Server-Timing: + - gfet4t7; dur=2 + body: + encoding: ASCII-8BIT + string: | + { + "error_message" : "You must use an API key to authenticate each request to Google Maps Platform APIs. For additional information, please refer to http://g.co/dev/maps-no-account", + "results" : [], + "status" : "REQUEST_DENIED" + } + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:35 GMT +recorded_with: VCR 4.0.0 diff --git a/test/data/vcr_cassettes/lib/twitter_sync/when_message_contains_a_media_attachment_e_g__jpg_stores_it_as_an_attachment_on_the_article.yml b/test/data/vcr_cassettes/lib/twitter_sync/when_message_contains_a_media_attachment_e_g__jpg_stores_it_as_an_attachment_on_the_article.yml new file mode 100644 index 000000000..aea7f7ad1 --- /dev/null +++ b/test/data/vcr_cassettes/lib/twitter_sync/when_message_contains_a_media_attachment_e_g__jpg_stores_it_as_an_attachment_on_the_article.yml @@ -0,0 +1,117 @@ +--- +http_interactions: +- request: + method: get + uri: http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - abs.twimg.com + response: + status: + code: 200 + message: OK + headers: + Accept-Ranges: + - bytes + Access-Control-Allow-Origin: + - "*" + Content-Type: + - image/png + Date: + - Fri, 27 Dec 2019 03:45:39 GMT + Etag: + - '"QcuRPIhOAYpzfGh0vMtQAQ=="' + Expires: + - Sat, 26 Dec 2020 03:45:39 GMT + Last-Modified: + - Sat, 22 Dec 2018 15:21:58 GMT + Server: + - ECS (tpe/68A3) + Strict-Transport-Security: + - max-age=631138519 + Surrogate-Key: + - twitter-assets + X-Cache: + - HIT + X-Connection-Hash: + - 97b9e7d1341932a1fcad07625c3f5283 + X-Content-Type-Options: + - nosniff + X-Response-Time: + - '8' + X-Ton-Expected-Size: + - '504' + Content-Length: + - '504' + body: + encoding: ASCII-8BIT + string: !binary |- + iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAwFBMVEXM1t3K1dy5xc2xvca7xs+KmaVrfYtld4agrrhneYhoeoiksbufrbegrbe7xs66xs6Dk5+/ytKdqrWElKCDk6Byg5HG0NjEz9bH0dlqfIprfIuAkJ2ir7nJ09p1hpN0hZO9yNDH0tmbqbTBzNOzv8iqtsCotL6ntL7L1dy3w8yjsLqVpK+Onam8x9CRn6ttf43CzdWMm6dmeIeLmqauusSwvMVxgpCToa29ydFneIeRoKzK1Nttfo2yvseZp7KHl6OOgUPZAAAA80lEQVR4Ae3SA5oDQRgE0Aortm1zNub9T7Xm4O/+uMo7w8O/duNyezxuF3R5fX4+8vu80BII8lUoDA0RfhKFUizIT4JxqCT4RQIKSZokIUvRJAVZmiYZyLI0yUIWpEkQohwtcpDkaZGHpECLAkRFmpQgK9OkDFmFJhXIqjV+Ua9CoZHlJ9kGlJqtLF9lW01oaXe6vX6308aPMRiOxpPJeDQcQMd0Nuer+WwKlcXS4CfG3QKi1Zwm8xUEAYMWRkAZSbvT2qAtYw1bzQ0dbJqws6WjLWzs9nS038HqQMEBFscTBacjzDoUnWF2oegCsytFV7x6ADhgICRy9ELGAAAAAElFTkSuQmCC + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:39 GMT +- request: + method: get + uri: https://pbs.twimg.com/tweet_video_thumb/EMeXU-7UYAEm71w.jpg + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Zammad User Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + Host: + - pbs.twimg.com + response: + status: + code: 200 + message: OK + headers: + Accept-Ranges: + - bytes + Access-Control-Allow-Origin: + - "*" + Access-Control-Expose-Headers: + - Content-Length + Cache-Control: + - max-age=604800, must-revalidate + Content-Type: + - image/jpeg + Date: + - Fri, 27 Dec 2019 03:45:39 GMT + Last-Modified: + - Mon, 23 Dec 2019 13:18:15 GMT + Server: + - ECS (tpe/68A0) + Strict-Transport-Security: + - max-age=631138519 + Surrogate-Key: + - tweet_video_thumb tweet_video_thumb/bucket/2 tweet_video_thumb/1209101411717242881 + X-Cache: + - MISS + X-Connection-Hash: + - 2782e3eabbfee48f2029b102037eec33 + X-Content-Type-Options: + - nosniff + X-Response-Time: + - '114' + Content-Length: + - '18674' + body: + encoding: ASCII-8BIT + string: !binary |- + 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 + http_version: + recorded_at: Fri, 27 Dec 2019 03:45:39 GMT +recorded_with: VCR 4.0.0