[deploy] Decouple Twitter authentication from fetching tweets (#7920)

* Add TwitterClient::Client

* Use TwitterClient::Client for Tweet

* Test retweets

* Test assignment to user

* Fix TweetTag spec

* Remove TwitterBot

* Fix some specs

* Fix RSS Reader specs
This commit is contained in:
rhymes 2020-05-20 00:05:41 +02:00 committed by GitHub
parent 59765d339f
commit 300ba1b33f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 6414 additions and 172 deletions

10
Envfile
View file

@ -46,15 +46,15 @@ variable :FORCE_SSL_IN_RAILS, :String, default: "not applicable in dev"
# It's best if you have the following configured
# for more development experience
# Github for github related access
# GitHub
# (https://developer.github.com/v3/guides/basics-of-authentication/)
variable :GITHUB_KEY, :String, default: ""
variable :GITHUB_SECRET, :String, default: ""
# Twitter for normal twitter access
# (https://developer.twitter.com/en/docs/basics/authentication/guides/access-tokens)
variable :TWITTER_KEY, :String, default: "Optional"
variable :TWITTER_SECRET, :String, default: "Optional"
# Twitter
# (https://developer.twitter.com/en/docs/basics/authentication/oauth-2-0/application-only)
variable :TWITTER_KEY, :String, default: ""
variable :TWITTER_SECRET, :String, default: ""
################################################
######### Optional 3rd Party Services ##########

View file

@ -1,10 +0,0 @@
class TwitterBot
def self.client(token:, secret:)
Twitter::REST::Client.new do |config|
config.consumer_key = ApplicationConfig["TWITTER_KEY"]
config.consumer_secret = ApplicationConfig["TWITTER_SECRET"]
config.access_token = token
config.access_token_secret = secret
end
end
end

View file

@ -3,32 +3,15 @@ class Tweet < ApplicationRecord
belongs_to :user, optional: true
serialize :mentioned_usernames_serialized
serialize :hashtags_serialized
serialize :urls_serialized
serialize :media_serialized
serialize :extended_entities_serialized
serialize :full_fetched_object_serialized
serialize :hashtags_serialized
serialize :media_serialized
serialize :mentioned_usernames_serialized
serialize :urls_serialized
validates :twitter_id_code, presence: true
validates :full_fetched_object_serialized, presence: true
def self.find_or_fetch(twitter_id_code)
find_by(twitter_id_code: twitter_id_code) || fetch(twitter_id_code)
end
def self.fetch(twitter_id_code)
tries = 0
tweet = nil
until tries > 4 || tweet
begin
return tweet = try_to_get_tweet(twitter_id_code)
rescue StandardError => e
Rails.logger.error(e)
tries += 1
end
end
end
validates :twitter_id_code, presence: true
def processed_text
urls_serialized.each do |url|
@ -55,66 +38,83 @@ class Tweet < ApplicationRecord
end
class << self
def try_to_get_tweet(twitter_id_code)
client = TwitterBot.client(random_identity)
tweet = client.status(twitter_id_code, tweet_mode: "extended")
make_tweet_from_api_object(tweet)
def find_or_fetch(status_id)
find_by(twitter_id_code: status_id) || fetch(status_id)
end
private
def make_tweet_from_api_object(tweeted)
tweeted = if tweeted.attrs[:retweeted_status]
TwitterBot.client(random_identity).status(tweeted.attrs[:retweeted_status][:id_str])
else
tweeted
end
def fetch(status_id)
retrieve_and_save_tweet(status_id)
rescue TwitterClient::Errors::NotFound => e
raise e, "Tweet not found"
end
tweet = Tweet.where(twitter_id_code: tweeted.attrs[:id_str]).first_or_initialize
def retrieve_and_save_tweet(status_id)
status = TwitterClient::Client.status(status_id, tweet_mode: "extended")
create_tweet_from_api_status(status)
end
def create_tweet_from_api_status(status)
status = if status.retweeted_status.present?
TwitterClient::Client.status(status.retweeted_status.id.to_s)
else
status
end
params = { twitter_id_code: status.id.to_s }
tweet = Tweet.find_by(params) || new(params)
tweet.text = status.full_text
# matching the retrieved tweet to the DB user if there is one
tweet.user_id = User.find_by(twitter_username: status.user.screen_name)&.id
tweet = extract_metadata_attributes(tweet, status)
tweet = extract_serializable_attributes(tweet, status)
tweet = extract_user_attributes(tweet, status)
tweet.twitter_uid = tweeted.user.id.to_s
tweet.twitter_username = tweeted.user.screen_name.downcase
tweet.user_id = User.find_by(twitter_username: tweeted.user.screen_name)&.id
tweet.favorite_count = tweeted.favorite_count
tweet.retweet_count = tweeted.retweet_count
tweet.twitter_user_following_count = tweeted.user.friends_count
tweet.twitter_user_followers_count = tweeted.user.followers_count
tweet.in_reply_to_username = tweeted.in_reply_to_screen_name
tweet.source = tweeted.source
tweet.twitter_name = tweeted.user.name
tweet.mentioned_usernames_serialized = tweeted.user_mentions.as_json
tweet.remote_profile_image_url = tweeted.user.profile_image_url
tweet.last_fetched_at = Time.current
tweet.user_is_verified = tweeted.user.verified?
tweet = handle_tweeted_attrs(tweet, tweeted)
tweet.save!
tweet
end
def handle_tweeted_attrs(tweet, tweeted)
tweet.in_reply_to_user_id_code = tweeted.attrs[:in_reply_to_user_id_str]
tweet.in_reply_to_status_id_code = tweeted.attrs[:in_reply_to_status_id_str]
tweet.twitter_id_code = tweeted.attrs[:id_str]
tweet.quoted_tweet_id_code = tweeted.attrs[:quoted_status_id_str]
tweet.text = tweeted.attrs[:full_text]
tweet.hashtags_serialized = tweeted.attrs[:entities][:hashtags]
tweet.urls_serialized = tweeted.attrs[:entities][:urls]
tweet.media_serialized = tweeted.attrs[:media]
tweet.extended_entities_serialized = tweeted.attrs[:extended_entities]
tweet.full_fetched_object_serialized = tweeted.attrs
tweet.tweeted_at = tweeted.attrs[:created_at]
tweet.is_quote_status = tweeted.attrs[:is_quote_status]
def extract_metadata_attributes(tweet, status)
tweet.favorite_count = status.favorite_count
tweet.in_reply_to_status_id_code = status.in_reply_to_status_id.to_s
tweet.in_reply_to_user_id_code = status.in_reply_to_user_id.to_s
tweet.in_reply_to_username = status.in_reply_to_screen_name.to_s
tweet.is_quote_status = status.attrs[:is_quote_status]
tweet.quoted_tweet_id_code = status.attrs[:quoted_status_id_str]
tweet.retweet_count = status.retweet_count
tweet.source = status.source
tweet.tweeted_at = status.created_at
tweet
end
def random_identity
iden = Identity.where(provider: "twitter").last(250).sample
{
token: iden&.token || ApplicationConfig["TWITTER_KEY"],
secret: iden&.secret || ApplicationConfig["TWITTER_SECRET"]
}
def extract_serializable_attributes(tweet, status)
tweet.extended_entities_serialized = status.attrs[:extended_entities]
tweet.full_fetched_object_serialized = status.attrs
tweet.hashtags_serialized = status.hashtags
tweet.media_serialized = status.attrs.dig(:entities, :media)
tweet.mentioned_usernames_serialized = status.user_mentions.as_json
tweet.urls_serialized = status.urls
tweet
end
def extract_user_attributes(tweet, status)
tweet.remote_profile_image_url = status.user.profile_image_url
tweet.twitter_name = status.user.name
tweet.twitter_uid = status.user.id.to_s
tweet.twitter_user_followers_count = status.user.followers_count
tweet.twitter_user_following_count = status.user.friends_count
tweet.twitter_username = status.user.screen_name.downcase
tweet.user_is_verified = status.user.verified?
tweet
end
end
end

View file

@ -4,12 +4,17 @@ class RssReader
end
def get_all_articles(force = true)
articles = []
User.where.not(feed_url: [nil, ""]).find_each do |user|
# unless forced, fetch sparingly
next if force == false && (rand(2) == 1 || user.feed_fetched_at > 15.minutes.ago)
create_articles_for_user(user)
user_articles = create_articles_for_user(user)
articles.concat(user_articles) if user_articles
end
articles
end
def fetch_user(user)
@ -28,8 +33,11 @@ class RssReader
user.update_column(:feed_fetched_at, Time.current)
feed = fetch_rss(user.feed_url.strip)
articles = []
feed.entries.reverse_each do |item|
make_from_rss_item(item, user, feed)
article = make_from_rss_item(item, user, feed)
articles.append(article)
rescue StandardError => e
log_error(
"RssReaderError: occurred while creating article",
@ -41,6 +49,8 @@ class RssReader
},
)
end
articles
rescue StandardError => e
log_error(
"RssReaderError: occurred while fetching feed",
@ -80,6 +90,8 @@ class RssReader
)
Slack::Messengers::ArticleFetchedFeed.call(article: article)
article
end
def get_host_without_www(url)

View file

@ -0,0 +1,72 @@
module TwitterClient
# Twitter client (users twitter gem as a backend)
class Client
class << self
# adapted from https://api.rubyonrails.org/classes/Module.html#method-i-delegate_missing_to
def method_missing(method, *args, &block)
return super unless target.respond_to?(method, false)
request do
target.public_send(method, *args, &block)
end
end
# adapted from https://api.rubyonrails.org/classes/Module.html#method-i-delegate_missing_to
def respond_to_missing?(method, _include_all = false)
target.respond_to?(method, false) || super
end
private
def request
Honeycomb.add_field("name", "twitter.client")
yield
rescue Twitter::Error => e
record_error(e)
handle_error(e)
end
def record_error(exception)
class_name = exception.class.name.demodulize
Honeycomb.add_field("twitter.result", "error")
Honeycomb.add_field("twitter.error", class_name)
DatadogStatsClient.increment(
"twitter.errors",
tags: ["error:#{class_name}", "message:#{exception.message}"],
)
end
def handle_error(exception)
class_name = exception.class.name.demodulize
# raise specific error if known, generic one if unknown
error_class = "::TwitterClient::Errors::#{class_name}".safe_constantize
raise error_class, exception.message if error_class
error_class = if exception.class < Twitter::Error::ClientError
TwitterClient::Errors::ClientError
elsif exception.class < Twitter::Error::ServerError
TwitterClient::Errors::ServerError
else
TwitterClient::Errors::Error
end
raise error_class, exception.message
end
def target
Twitter::REST::Client.new(
consumer_key: ApplicationConfig["TWITTER_KEY"],
consumer_secret: ApplicationConfig["TWITTER_SECRET"],
user_agent: "TwitterRubyGem/#{Twitter::Version} (#{URL.url})",
timeouts: {
connect: 5,
read: 5,
write: 5
},
)
end
end
end
end

View file

@ -0,0 +1,15 @@
module TwitterClient
module Errors
class Error < StandardError
end
class ClientError < Error
end
class ServerError < Error
end
class NotFound < ClientError
end
end
end

View file

@ -389,7 +389,7 @@ Rails.application.routes.draw do
get "/rails/mailers/*path" => "rails/mailers#preview"
end
get "/embed/:embeddable" => "liquid_embeds#show"
get "/embed/:embeddable", to: "liquid_embeds#show", as: "liquid_embed"
# serviceworkers
get "/serviceworker" => "service_worker#index"

View file

@ -34,7 +34,7 @@ FactoryBot.define do
---
#{Faker::Hipster.paragraph(sentence_count: 2)}
#{'{% tweet 1018911886862057472%}' if with_tweet_tag}
#{'{% tweet 1018911886862057472 %}' if with_tweet_tag}
#{Faker::Hipster.paragraph(sentence_count: 1)}
#{"\n\n---\n\n something \n\n---\n funky in the code? \n---\n That's nice" if with_hr_issue}
HEREDOC

View file

@ -21,5 +21,11 @@ Github gist here
code block
```
testsetsetsetsetsetset
```
testsetsetsetsetsetset lets introduce some {{ chaos }}
Here's more {{ what }}
```
some more code and `{{ VARIABLE }}` and **_`{{ HTML }}`_**
### `{{ how about this }}`

View file

@ -3,7 +3,7 @@ require "rails_helper"
RSpec.describe TweetTag, type: :liquid_tag do
let(:twitter_id) { "1018911886862057472" }
let(:handle) { "thepracticaldev" }
let(:name) { "The Practical Dev" }
let(:name) { "DEV Community 👩‍💻👨‍💻" }
let(:body) { "When GitHub goes down" }
setup { Liquid::Template.register_tag("tweet", described_class) }
@ -13,16 +13,18 @@ RSpec.describe TweetTag, type: :liquid_tag do
end
it "accepts valid tweet id", :vcr do
VCR.use_cassette("twitter_fetch_status") do
VCR.use_cassette("twitter_client_status_extended") do
liquid = generate_tweet_liquid_tag(twitter_id)
expect(liquid.render).to include(handle)
expect(liquid.render).to include(name)
expect(liquid.render).to include(body)
body = liquid.render
expect(body).to include(handle)
expect(body).to include(name)
expect(body).to include(body)
end
end
xit "render properly", :vcr do
VCR.use_cassette("twitter_fetch_status") do
VCR.use_cassette("twitter_client_status_extended") do
Time.use_zone("Asia/Tokyo") do
rendered = generate_tweet_liquid_tag(twitter_id).render
Approvals.verify(rendered, name: "liquid_tweet_tag_spec", format: :html)

View file

@ -178,7 +178,7 @@ RSpec.describe Article, type: :model do
end
it "is valid with valid liquid tags", :vcr do
VCR.use_cassette("twitter_fetch_status") do
VCR.use_cassette("twitter_client_status_extended") do
article = build_and_validate_article(with_tweet_tag: true)
expect(article).to be_valid
end

View file

@ -3,33 +3,138 @@ require "rails_helper"
RSpec.describe Tweet, type: :model, vcr: true do
let(:tweet_id) { "1018911886862057472" }
let(:tweet_reply_id) { "1242938461784608770" }
let(:retweet_id) { "1262395854469677058" }
it "fetches a tweet" do
VCR.use_cassette("twitter_fetch_status") do
tweet = described_class.fetch(tweet_id)
expect(tweet).to be_a(described_class)
end
end
it { is_expected.to validate_presence_of(:twitter_id_code) }
it { is_expected.to validate_presence_of(:full_fetched_object_serialized) }
it "renders processed text" do
VCR.use_cassette("twitter_fetch_status") do
tweet = described_class.fetch(tweet_id)
expect(tweet.processed_text).not_to be_nil
end
end
describe ".find_or_fetch" do
context "when retrieving a tweet", vcr: { cassette_name: "twitter_client_status_extended" } do
it "saves a new tweet" do
expect do
described_class.find_or_fetch(tweet_id)
end.to change(described_class, :count).by(1)
end
describe "reply ids" do
it "correctly saves in_reply_to_user_id_code" do
VCR.use_cassette("twitter_fetch_reply") do
tweet = described_class.fetch(tweet_reply_id)
expect(tweet.in_reply_to_user_id_code).to eq("45042829")
it "retrieves an existing tweet" do
created_tweet = described_class.find_or_fetch(tweet_id)
expect do
found_tweet = described_class.find_or_fetch(tweet_id)
expect(found_tweet.id).to eq(created_tweet.id)
end.not_to change(described_class, :count)
end
it "saves the proper status ID and text" do
tweet = described_class.find_or_fetch(tweet_id)
status = tweet.full_fetched_object_serialized
expect(tweet.text).to eq(status[:full_text])
expect(tweet.twitter_id_code).to eq(status[:id_str])
end
it "saves the proper metadata attributes", :aggregate_failures do
tweet = described_class.find_or_fetch(tweet_id)
status = tweet.full_fetched_object_serialized
expect(tweet.favorite_count).to eq(status[:favorite_count])
expect(tweet.in_reply_to_status_id_code).to be_empty
expect(tweet.in_reply_to_user_id_code).to be_empty
expect(tweet.in_reply_to_username).to be_empty
expect(tweet.is_quote_status).to be(false)
expect(tweet.quoted_tweet_id_code).to eq(status[:quoted_status_id_str])
expect(tweet.retweet_count).to eq(status[:retweet_count])
expect(tweet.source).to eq(status[:source])
expect(tweet.tweeted_at.to_i).to eq(Time.zone.parse(status[:created_at]).to_i)
end
it "saves the proper serializable attributes", :aggregate_failures do
tweet = described_class.find_or_fetch(tweet_id)
status = tweet.full_fetched_object_serialized
expect(status).to be_present
expect(tweet.extended_entities_serialized).to eq(status[:extended_entities])
expect(tweet.hashtags_serialized).to eq(status[:entities][:hashtags])
expect(tweet.media_serialized).to eq(status[:entities][:media])
expect(tweet.mentioned_usernames_serialized).to be_empty
expect(tweet.urls_serialized).to eq(status[:entities][:urls])
end
it "saves the proper user attributes", :aggregate_failures do
tweet = described_class.find_or_fetch(tweet_id)
status = tweet.full_fetched_object_serialized
status_user = status[:user]
expect(tweet.remote_profile_image_url.to_s).to eq(status_user[:profile_image_url])
expect(tweet.twitter_name).to eq(status_user[:name])
expect(tweet.twitter_uid).to eq(status_user[:id_str])
expect(tweet.twitter_user_followers_count).to eq(status_user[:followers_count])
expect(tweet.twitter_user_following_count).to eq(status_user[:friends_count])
expect(tweet.twitter_username).to eq(status_user[:screen_name].downcase)
expect(tweet.user_is_verified).to be(status_user[:verified])
end
it "sets #last_fetched_at" do
tweet = described_class.find_or_fetch(tweet_id)
expect(tweet.last_fetched_at).to be_present
end
it "is assignes to the existing user if the screen name corresponds" do
user = create(:user, twitter_username: "ThePracticalDev")
tweet = described_class.find_or_fetch(tweet_id)
expect(tweet.user_id).to eq(user.id)
end
end
it "correctly saves in_reply_to_status_id_code" do
VCR.use_cassette("twitter_fetch_reply") do
tweet = described_class.fetch(tweet_reply_id)
expect(tweet.in_reply_to_status_id_code).to eq("1242837746315669505")
context "when retrieving a non existent tweet", vcr: { cassette_name: "twitter_client_status_not_found_extended" } do
it "raises an error if the tweet does not exist" do
expect { described_class.find_or_fetch("0") }.to raise_error(TwitterClient::Errors::NotFound)
end
end
context "when retrieving a reply tweet", vcr: { cassette_name: "twitter_client_status_reply_extended" } do
it "saves a new tweet" do
expect do
described_class.find_or_fetch(tweet_reply_id)
end.to change(described_class, :count).by(1)
end
it "retrieves an existing tweet" do
created_tweet = described_class.find_or_fetch(tweet_reply_id)
expect do
found_tweet = described_class.find_or_fetch(tweet_reply_id)
expect(found_tweet.id).to eq(created_tweet.id)
end.not_to change(described_class, :count)
end
it "saves the proper reply fields" do
tweet = described_class.find_or_fetch(tweet_reply_id)
status = tweet.full_fetched_object_serialized
expect(tweet.in_reply_to_status_id_code).to eq(status[:in_reply_to_status_id_str])
expect(tweet.in_reply_to_user_id_code).to eq(status[:in_reply_to_user_id_str])
expect(tweet.in_reply_to_username).to eq(status[:in_reply_to_screen_name])
end
end
context "when retrieving a reply tweet", vcr: { cassette_name: "twitter_client_status_retweet_extended" } do
it "saves a new tweet" do
expect do
described_class.find_or_fetch(retweet_id)
end.to change(described_class, :count).by(1)
end
it "saves the proper status ID" do
tweet = described_class.find_or_fetch(retweet_id)
expect(tweet.twitter_id_code).not_to eq(retweet_id) # we fetch the original tweet
end
end
end

View file

@ -1,25 +1,27 @@
require "rails_helper"
RSpec.describe "LiquidEmbeds", type: :request, vcr: VCR_OPTIONS[:twitter_fetch_status] do
RSpec.describe "LiquidEmbeds", type: :request, vcr: { cassette_name: "twitter_client_status_extended" } do
describe "get /embeds" do
let(:path) { liquid_embed_path("tweet", args: 1_018_911_886_862_057_472) }
it "renders proper tweet" do
get "/embed/tweet?args=1018911886862057472"
get path
expect(response.body).to include("ltag__twitter-tweet")
end
it "renders proper css" do
get "/embed/tweet?args=1018911886862057472"
it "renders proper CSS" do
get path
expect(response.body).to include("blockquote.ltag__twitter-tweet")
end
it "renders 404 if improper tweet" do
expect do
get "/embed/tweet?args=improper"
get liquid_embed_path("tweet", args: "improper")
end.to raise_error(ActionView::Template::Error)
end
it "contains base target parent" do
get "/embed/tweet?args=1018911886862057472"
get path
expect(response.body).to include('<base target="_parent">')
end
end

View file

@ -3,7 +3,7 @@ require "rss"
default_logger = Rails.logger
RSpec.describe RssReader, type: :service, vcr: VCR_OPTIONS[:rss_feeds] do
RSpec.describe RssReader, type: :service, vcr: true do
let(:link) { "https://medium.com/feed/@vaidehijoshi" }
let(:nonmedium_link) { "https://circleci.com/blog/feed.xml" }
let(:nonpermanent_link) { "https://medium.com/feed/@macsiri/" }
@ -25,21 +25,21 @@ RSpec.describe RssReader, type: :service, vcr: VCR_OPTIONS[:rss_feeds] do
end
end
it "fetch only articles from an feed_url" do
rss_reader.get_all_articles
it "fetch only articles from a feed_url", vcr: { cassette_name: "rss_reader_fetch_articles" } do
articles = rss_reader.get_all_articles
# the result within the approval file depends on the feed
# not fetching comments is baked into this
verify(format: :txt) { Article.count }
verify(format: :txt) { articles.length }
end
it "does not re-create article if it already exist" do
it "does not recreate articles if they already exist", vcr: { cassette_name: "rss_reader_fetch_articles_twice" } do
rss_reader.get_all_articles
expect { rss_reader.get_all_articles }.not_to change(Article, :count)
end
it "parses correctly" do
it "parses correctly", vcr: { cassette_name: "rss_reader_fetch_articles" } do
rss_reader.get_all_articles
verify format: :txt do
@ -47,7 +47,7 @@ RSpec.describe RssReader, type: :service, vcr: VCR_OPTIONS[:rss_feeds] do
end
end
it "sets feed_fetched_at to the current time" do
it "sets feed_fetched_at to the current time", vcr: { cassette_name: "rss_reader_fetch_articles" } do
Timecop.freeze(Time.current) do
rss_reader.get_all_articles
@ -57,7 +57,7 @@ RSpec.describe RssReader, type: :service, vcr: VCR_OPTIONS[:rss_feeds] do
end
end
it "does refetch same user over and over by default" do
it "does refetch same user over and over by default", vcr: { cassette_name: "rss_reader_fetch_multiple_times" } do
user = User.find_by(feed_url: nonpermanent_link)
Timecop.freeze(Time.current) do
@ -94,10 +94,10 @@ RSpec.describe RssReader, type: :service, vcr: VCR_OPTIONS[:rss_feeds] do
expect(Rails.logger).to have_received(:error).at_least(:once)
end
it "queues as many slack messages as there are articles" do
expect do
rss_reader.get_all_articles
end.to change(Slack::Messengers::Worker.jobs, :count).by(12)
it "queues as many slack messages as there are articles", vcr: { cassette_name: "rss_reader_fetch_articles" } do
old_count = Slack::Messengers::Worker.jobs.count
articles = rss_reader.get_all_articles
expect(Slack::Messengers::Worker.jobs.count).to eq(old_count + articles.length)
end
end
@ -115,7 +115,7 @@ RSpec.describe RssReader, type: :service, vcr: VCR_OPTIONS[:rss_feeds] do
end
end
describe "#fetch_user" do
describe "#fetch_user", vcr: { cassette_name: "rss_reader_fetch_medium_feed" } do
before do
[link, nonmedium_link, nonpermanent_link].each do |feed_url|
create(:user, feed_url: feed_url)
@ -123,10 +123,10 @@ RSpec.describe RssReader, type: :service, vcr: VCR_OPTIONS[:rss_feeds] do
end
it "gets articles for user" do
rss_reader.fetch_user(User.find_by(feed_url: link))
articles = rss_reader.fetch_user(User.find_by(feed_url: link))
# the result within the approval file depends on the feed
verify(format: :txt) { Article.count }
verify(format: :txt) { articles.length }
end
it "does not set featured_number" do
@ -155,9 +155,9 @@ RSpec.describe RssReader, type: :service, vcr: VCR_OPTIONS[:rss_feeds] do
end
it "queues as many slack messages as there are user articles" do
expect do
rss_reader.fetch_user(User.find_by(feed_url: link))
end.to change(Slack::Messengers::Worker.jobs, :count).by(1)
old_count = Slack::Messengers::Worker.jobs.count
articles = rss_reader.fetch_user(User.find_by(feed_url: link))
expect(Slack::Messengers::Worker.jobs.count).to eq(old_count + articles.length)
end
end

View file

@ -0,0 +1,22 @@
require "rails_helper"
RSpec.describe TwitterClient::Client, type: :service, vcr: true do
let(:tweet_id) { "1018911886862057472" }
describe ".status" do
it "returns a status" do
VCR.use_cassette("twitter_client_status") do
tweet = described_class.status(tweet_id)
expect(tweet.text).to be_present
end
end
it "raises NotFound if the status does not exist" do
VCR.use_cassette("twitter_client_status_not_found") do
expect do
described_class.status(0)
end.to raise_error(TwitterClient::Errors::NotFound)
end
end
end
end

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,145 @@
---
http_interactions:
- request:
method: post
uri: https://api.twitter.com/oauth2/token
body:
encoding: UTF-8
string: grant_type=client_credentials
headers:
User-Agent:
- TwitterRubyGem/7.0.0 (http://localhost:3000)
Accept:
- "*/*"
Connection:
- close
Content-Type:
- application/x-www-form-urlencoded
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:
- '153'
Content-Type:
- application/json;charset=utf-8
Date:
- Mon, 18 May 2020 12:41:17 GMT
Expires:
- Tue, 31 Mar 1981 05:00:00 GMT
Last-Modified:
- Mon, 18 May 2020 12:41:16 GMT
Ml:
- A
Pragma:
- no-cache
Server:
- tsa_o
Status:
- 200 OK
Strict-Transport-Security:
- max-age=631138519
X-Connection-Hash:
- b808ab37862bae0eb94f753a5cd96dd5
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Response-Time:
- '107'
X-Transaction:
- '00494008004581f6'
X-Tsa-Request-Body-Time:
- '1'
X-Twitter-Response-Tags:
- BouncerCompliant
X-Ua-Compatible:
- IE=edge,chrome=1
X-Xss-Protection:
- '0'
body:
encoding: UTF-8
string: '{"token_type":"bearer","access_token":"ACCESS_TOKEN"}'
http_version: null
recorded_at: Mon, 18 May 2020 12:41:17 GMT
- request:
method: get
uri: https://api.twitter.com/1.1/statuses/show/1018911886862057472.json
body:
encoding: UTF-8
string: ''
headers:
User-Agent:
- TwitterRubyGem/7.0.0 (http://localhost:3000)
Connection:
- close
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:
- '4181'
Content-Type:
- application/json;charset=utf-8
Date:
- Mon, 18 May 2020 12:41:17 GMT
Expires:
- Tue, 31 Mar 1981 05:00:00 GMT
Last-Modified:
- Mon, 18 May 2020 12:41:17 GMT
Pragma:
- no-cache
Server:
- tsa_o
Status:
- 200 OK
Strict-Transport-Security:
- max-age=631138519
X-Access-Level:
- read
X-Connection-Hash:
- a3e8c4da447fc5bcef97874cbd59a994
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- SAMEORIGIN
X-Rate-Limit-Limit:
- '900'
X-Rate-Limit-Remaining:
- '896'
X-Rate-Limit-Reset:
- '1589805893'
X-Response-Time:
- '131'
X-Transaction:
- '0094e40000aabfb7'
X-Twitter-Response-Tags:
- BouncerCompliant
X-Xss-Protection:
- '0'
body:
encoding: UTF-8
string: '{"created_at":"Mon Jul 16 17:34:58 +0000 2018","id":1018911886862057472,"id_str":"1018911886862057472","text":"When
GitHub goes down https:\/\/t.co\/5Laf1GtUSS","truncated":false,"entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[],"media":[{"id":1018911875919212546,"id_str":"1018911875919212546","indices":[22,45],"media_url":"http:\/\/pbs.twimg.com\/tweet_video_thumb\/DiPm8-WXcAI3_qS.jpg","media_url_https":"https:\/\/pbs.twimg.com\/tweet_video_thumb\/DiPm8-WXcAI3_qS.jpg","url":"https:\/\/t.co\/5Laf1GtUSS","display_url":"pic.twitter.com\/5Laf1GtUSS","expanded_url":"https:\/\/twitter.com\/ThePracticalDev\/status\/1018911886862057472\/photo\/1","type":"photo","sizes":{"thumb":{"w":150,"h":150,"resize":"crop"},"medium":{"w":400,"h":312,"resize":"fit"},"large":{"w":400,"h":312,"resize":"fit"},"small":{"w":400,"h":312,"resize":"fit"}}}]},"extended_entities":{"media":[{"id":1018911875919212546,"id_str":"1018911875919212546","indices":[22,45],"media_url":"http:\/\/pbs.twimg.com\/tweet_video_thumb\/DiPm8-WXcAI3_qS.jpg","media_url_https":"https:\/\/pbs.twimg.com\/tweet_video_thumb\/DiPm8-WXcAI3_qS.jpg","url":"https:\/\/t.co\/5Laf1GtUSS","display_url":"pic.twitter.com\/5Laf1GtUSS","expanded_url":"https:\/\/twitter.com\/ThePracticalDev\/status\/1018911886862057472\/photo\/1","type":"animated_gif","sizes":{"thumb":{"w":150,"h":150,"resize":"crop"},"medium":{"w":400,"h":312,"resize":"fit"},"large":{"w":400,"h":312,"resize":"fit"},"small":{"w":400,"h":312,"resize":"fit"}},"video_info":{"aspect_ratio":[50,39],"variants":[{"bitrate":0,"content_type":"video\/mp4","url":"https:\/\/video.twimg.com\/tweet_video\/DiPm8-WXcAI3_qS.mp4"}]}}]},"source":"\u003ca
href=\"http:\/\/twitter.com\" rel=\"nofollow\"\u003eTwitter Web Client\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":2735246778,"id_str":"2735246778","name":"DEV
Community \ud83d\udc69\u200d\ud83d\udcbb\ud83d\udc68\u200d\ud83d\udcbb","screen_name":"ThePracticalDev","location":"","description":"Great
posts from the amazing https:\/\/t.co\/xHvFQQ9jeO community, with some opinion
and humor mixed in. Created by @bendhalpern. Join https:\/\/t.co\/xHvFQQ9jeO!","url":"https:\/\/t.co\/lhcCPP1ReQ","entities":{"url":{"urls":[{"url":"https:\/\/t.co\/lhcCPP1ReQ","expanded_url":"http:\/\/dev.to","display_url":"dev.to","indices":[0,23]}]},"description":{"urls":[{"url":"https:\/\/t.co\/xHvFQQ9jeO","expanded_url":"https:\/\/dev.to","display_url":"dev.to","indices":[29,52]},{"url":"https:\/\/t.co\/xHvFQQ9jeO","expanded_url":"https:\/\/dev.to","display_url":"dev.to","indices":[132,155]}]}},"protected":false,"followers_count":191639,"friends_count":2311,"listed_count":3793,"created_at":"Fri
Aug 15 19:11:17 +0000 2014","favourites_count":48516,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":true,"statuses_count":38686,"lang":null,"contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"131516","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_image_url":"http:\/\/pbs.twimg.com\/profile_images\/1253165670935773185\/SkSoEQL3_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/1253165670935773185\/SkSoEQL3_normal.jpg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/2735246778\/1557714134","profile_link_color":"14BD7B","profile_sidebar_border_color":"000000","profile_sidebar_fill_color":"000000","profile_text_color":"000000","profile_use_background_image":true,"has_extended_profile":false,"default_profile":false,"default_profile_image":false,"following":null,"follow_request_sent":null,"notifications":null,"translator_type":"none"},"geo":null,"coordinates":null,"place":null,"contributors":null,"is_quote_status":false,"retweet_count":82,"favorite_count":263,"favorited":false,"retweeted":false,"possibly_sensitive":false,"possibly_sensitive_appealable":false,"lang":"en"}'
http_version: null
recorded_at: Mon, 18 May 2020 12:41:17 GMT
recorded_with: VCR 5.1.0

View file

@ -1,5 +1,74 @@
---
http_interactions:
- request:
method: post
uri: https://api.twitter.com/oauth2/token
body:
encoding: UTF-8
string: grant_type=client_credentials
headers:
User-Agent:
- TwitterRubyGem/7.0.0 (http://localhost:3000)
Accept:
- "*/*"
Connection:
- close
Content-Type:
- application/x-www-form-urlencoded
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:
- '153'
Content-Type:
- application/json;charset=utf-8
Date:
- Mon, 18 May 2020 12:48:44 GMT
Expires:
- Tue, 31 Mar 1981 05:00:00 GMT
Last-Modified:
- Mon, 18 May 2020 12:48:44 GMT
Ml:
- A
Pragma:
- no-cache
Server:
- tsa_o
Status:
- 200 OK
Strict-Transport-Security:
- max-age=631138519
X-Connection-Hash:
- 4a0eff9d35d94b917c6a394d78e0c178
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Response-Time:
- '114'
X-Transaction:
- 004bebdc006e667c
X-Tsa-Request-Body-Time:
- '0'
X-Twitter-Response-Tags:
- BouncerCompliant
X-Ua-Compatible:
- IE=edge,chrome=1
X-Xss-Protection:
- '0'
body:
encoding: UTF-8
string: '{"token_type":"bearer","access_token":"ACCESS_TOKEN"}'
http_version: null
recorded_at: Mon, 18 May 2020 12:48:44 GMT
- request:
method: get
uri: https://api.twitter.com/1.1/statuses/show/1018911886862057472.json?tweet_mode=extended
@ -8,7 +77,7 @@ http_interactions:
string: ''
headers:
User-Agent:
- TwitterRubyGem/6.2.0
- TwitterRubyGem/7.0.0 (http://localhost:3000)
Connection:
- close
response:
@ -23,27 +92,27 @@ http_interactions:
Content-Disposition:
- attachment; filename=json.json
Content-Length:
- '4159'
- '4214'
Content-Type:
- application/json;charset=utf-8
Date:
- Tue, 17 Jul 2018 20:09:47 GMT
- Mon, 18 May 2020 12:48:44 GMT
Expires:
- Tue, 31 Mar 1981 05:00:00 GMT
Last-Modified:
- Tue, 17 Jul 2018 20:09:47 GMT
- Mon, 18 May 2020 12:48:44 GMT
Pragma:
- no-cache
Server:
- tsa_b
- tsa_o
Status:
- 200 OK
Strict-Transport-Security:
- max-age=631138519
X-Access-Level:
- read-write
- read
X-Connection-Hash:
- fc498ba11baf16a5fd6822ed64fb207d
- 50e8c947e1620a984e9ec9ab6d860012
X-Content-Type-Options:
- nosniff
X-Frame-Options:
@ -51,26 +120,26 @@ http_interactions:
X-Rate-Limit-Limit:
- '900'
X-Rate-Limit-Remaining:
- '893'
- '899'
X-Rate-Limit-Reset:
- '1531858493'
- '1589807024'
X-Response-Time:
- '66'
- '127'
X-Transaction:
- '00208a050094194c'
- '0071193c00ff8788'
X-Twitter-Response-Tags:
- BouncerCompliant
X-Xss-Protection:
- 1; mode=block; report=https://twitter.com/i/xss_report
- '0'
body:
encoding: UTF-8
string: '{"created_at":"Mon Jul 16 17:34:58 +0000 2018","id":1018911886862057472,"id_str":"1018911886862057472","full_text":"When
GitHub goes down https:\/\/t.co\/5Laf1GtUSS","truncated":false,"display_text_range":[0,21],"entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[],"media":[{"id":1018911875919212546,"id_str":"1018911875919212546","indices":[22,45],"media_url":"http:\/\/pbs.twimg.com\/tweet_video_thumb\/DiPm8-WXcAI3_qS.jpg","media_url_https":"https:\/\/pbs.twimg.com\/tweet_video_thumb\/DiPm8-WXcAI3_qS.jpg","url":"https:\/\/t.co\/5Laf1GtUSS","display_url":"pic.twitter.com\/5Laf1GtUSS","expanded_url":"https:\/\/twitter.com\/ThePracticalDev\/status\/1018911886862057472\/photo\/1","type":"photo","sizes":{"thumb":{"w":150,"h":150,"resize":"crop"},"medium":{"w":400,"h":312,"resize":"fit"},"large":{"w":400,"h":312,"resize":"fit"},"small":{"w":400,"h":312,"resize":"fit"}}}]},"extended_entities":{"media":[{"id":1018911875919212546,"id_str":"1018911875919212546","indices":[22,45],"media_url":"http:\/\/pbs.twimg.com\/tweet_video_thumb\/DiPm8-WXcAI3_qS.jpg","media_url_https":"https:\/\/pbs.twimg.com\/tweet_video_thumb\/DiPm8-WXcAI3_qS.jpg","url":"https:\/\/t.co\/5Laf1GtUSS","display_url":"pic.twitter.com\/5Laf1GtUSS","expanded_url":"https:\/\/twitter.com\/ThePracticalDev\/status\/1018911886862057472\/photo\/1","type":"animated_gif","sizes":{"thumb":{"w":150,"h":150,"resize":"crop"},"medium":{"w":400,"h":312,"resize":"fit"},"large":{"w":400,"h":312,"resize":"fit"},"small":{"w":400,"h":312,"resize":"fit"}},"video_info":{"aspect_ratio":[50,39],"variants":[{"bitrate":0,"content_type":"video\/mp4","url":"https:\/\/video.twimg.com\/tweet_video\/DiPm8-WXcAI3_qS.mp4"}]}}]},"source":"\u003ca
href=\"http:\/\/twitter.com\" rel=\"nofollow\"\u003eTwitter Web Client\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":2735246778,"id_str":"2735246778","name":"The
Practical Dev","screen_name":"ThePracticalDev","location":"","description":"Great
href=\"http:\/\/twitter.com\" rel=\"nofollow\"\u003eTwitter Web Client\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":2735246778,"id_str":"2735246778","name":"DEV
Community \ud83d\udc69\u200d\ud83d\udcbb\ud83d\udc68\u200d\ud83d\udcbb","screen_name":"ThePracticalDev","location":"","description":"Great
posts from the amazing https:\/\/t.co\/xHvFQQ9jeO community, with some opinion
and humor mixed in. Created by @bendhalpern. Join https:\/\/t.co\/xHvFQQ9jeO!","url":"https:\/\/t.co\/lhcCPP1ReQ","entities":{"url":{"urls":[{"url":"https:\/\/t.co\/lhcCPP1ReQ","expanded_url":"http:\/\/dev.to","display_url":"dev.to","indices":[0,23]}]},"description":{"urls":[{"url":"https:\/\/t.co\/xHvFQQ9jeO","expanded_url":"https:\/\/dev.to","display_url":"dev.to","indices":[29,52]},{"url":"https:\/\/t.co\/xHvFQQ9jeO","expanded_url":"https:\/\/dev.to","display_url":"dev.to","indices":[132,155]}]}},"protected":false,"followers_count":147060,"friends_count":2258,"listed_count":3145,"created_at":"Fri
Aug 15 19:11:17 +0000 2014","favourites_count":42288,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":false,"statuses_count":18387,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"131516","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_image_url":"http:\/\/pbs.twimg.com\/profile_images\/1002604104194056192\/IEoNsLNM_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/1002604104194056192\/IEoNsLNM_normal.jpg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/2735246778\/1492833420","profile_link_color":"14BD7B","profile_sidebar_border_color":"000000","profile_sidebar_fill_color":"000000","profile_text_color":"000000","profile_use_background_image":true,"has_extended_profile":false,"default_profile":false,"default_profile_image":false,"following":true,"follow_request_sent":false,"notifications":false,"translator_type":"none"},"geo":null,"coordinates":null,"place":null,"contributors":null,"is_quote_status":false,"retweet_count":91,"favorite_count":276,"favorited":true,"retweeted":false,"possibly_sensitive":false,"possibly_sensitive_appealable":false,"lang":"en"}'
http_version:
recorded_at: Tue, 17 Jul 2018 20:09:47 GMT
recorded_with: VCR 4.0.0
and humor mixed in. Created by @bendhalpern. Join https:\/\/t.co\/xHvFQQ9jeO!","url":"https:\/\/t.co\/lhcCPP1ReQ","entities":{"url":{"urls":[{"url":"https:\/\/t.co\/lhcCPP1ReQ","expanded_url":"http:\/\/dev.to","display_url":"dev.to","indices":[0,23]}]},"description":{"urls":[{"url":"https:\/\/t.co\/xHvFQQ9jeO","expanded_url":"https:\/\/dev.to","display_url":"dev.to","indices":[29,52]},{"url":"https:\/\/t.co\/xHvFQQ9jeO","expanded_url":"https:\/\/dev.to","display_url":"dev.to","indices":[132,155]}]}},"protected":false,"followers_count":191641,"friends_count":2311,"listed_count":3793,"created_at":"Fri
Aug 15 19:11:17 +0000 2014","favourites_count":48516,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":true,"statuses_count":38686,"lang":null,"contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"131516","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_image_url":"http:\/\/pbs.twimg.com\/profile_images\/1253165670935773185\/SkSoEQL3_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/1253165670935773185\/SkSoEQL3_normal.jpg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/2735246778\/1557714134","profile_link_color":"14BD7B","profile_sidebar_border_color":"000000","profile_sidebar_fill_color":"000000","profile_text_color":"000000","profile_use_background_image":true,"has_extended_profile":false,"default_profile":false,"default_profile_image":false,"following":null,"follow_request_sent":null,"notifications":null,"translator_type":"none"},"geo":null,"coordinates":null,"place":null,"contributors":null,"is_quote_status":false,"retweet_count":82,"favorite_count":263,"favorited":false,"retweeted":false,"possibly_sensitive":false,"possibly_sensitive_appealable":false,"lang":"en"}'
http_version: null
recorded_at: Mon, 18 May 2020 12:48:44 GMT
recorded_with: VCR 5.1.0

View file

@ -0,0 +1,137 @@
---
http_interactions:
- request:
method: post
uri: https://api.twitter.com/oauth2/token
body:
encoding: UTF-8
string: grant_type=client_credentials
headers:
User-Agent:
- TwitterRubyGem/7.0.0 (http://localhost:3000)
Accept:
- "*/*"
Connection:
- close
Content-Type:
- application/x-www-form-urlencoded
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:
- '153'
Content-Type:
- application/json;charset=utf-8
Date:
- Mon, 18 May 2020 16:09:44 GMT
Expires:
- Tue, 31 Mar 1981 05:00:00 GMT
Last-Modified:
- Mon, 18 May 2020 16:09:44 GMT
Ml:
- A
Pragma:
- no-cache
Server:
- tsa_o
Status:
- 200 OK
Strict-Transport-Security:
- max-age=631138519
X-Connection-Hash:
- c6b4eb440837c9010b219c99effa8141
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Response-Time:
- '116'
X-Transaction:
- 00a3fa16001000f5
X-Tsa-Request-Body-Time:
- '1'
X-Twitter-Response-Tags:
- BouncerCompliant
X-Ua-Compatible:
- IE=edge,chrome=1
X-Xss-Protection:
- '0'
body:
encoding: UTF-8
string: '{"token_type":"bearer","access_token":"ACCESS_TOKEN"}'
http_version: null
recorded_at: Mon, 18 May 2020 16:09:44 GMT
- request:
method: get
uri: https://api.twitter.com/1.1/statuses/show/0.json
body:
encoding: UTF-8
string: ''
headers:
User-Agent:
- TwitterRubyGem/7.0.0 (http://localhost:3000)
Connection:
- close
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:
- '71'
Content-Type:
- application/json; charset=utf-8
Date:
- Mon, 18 May 2020 16:09:44 GMT
Expires:
- Tue, 31 Mar 1981 05:00:00 GMT
Last-Modified:
- Mon, 18 May 2020 16:09:44 GMT
Pragma:
- no-cache
Server:
- tsa_o
Status:
- 404 Not Found
Strict-Transport-Security:
- max-age=631138519
X-Connection-Hash:
- a16f087a18e4315ecbc158f3e8947aa7
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- SAMEORIGIN
X-Rate-Limit-Limit:
- '900'
X-Rate-Limit-Remaining:
- '899'
X-Rate-Limit-Reset:
- '1589819084'
X-Response-Time:
- '114'
X-Transaction:
- 0035566e00d64ce7
X-Twitter-Response-Tags:
- BouncerCompliant
X-Xss-Protection:
- '0'
body:
encoding: UTF-8
string: '{"errors":[{"code":8,"message":"No data available for specified ID."}]}'
http_version: null
recorded_at: Mon, 18 May 2020 16:09:44 GMT
recorded_with: VCR 5.1.0

View file

@ -0,0 +1,137 @@
---
http_interactions:
- request:
method: post
uri: https://api.twitter.com/oauth2/token
body:
encoding: UTF-8
string: grant_type=client_credentials
headers:
User-Agent:
- TwitterRubyGem/7.0.0 (http://localhost:3000)
Accept:
- "*/*"
Connection:
- close
Content-Type:
- application/x-www-form-urlencoded
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:
- '153'
Content-Type:
- application/json;charset=utf-8
Date:
- Mon, 18 May 2020 14:46:21 GMT
Expires:
- Tue, 31 Mar 1981 05:00:00 GMT
Last-Modified:
- Mon, 18 May 2020 14:46:21 GMT
Ml:
- A
Pragma:
- no-cache
Server:
- tsa_o
Status:
- 200 OK
Strict-Transport-Security:
- max-age=631138519
X-Connection-Hash:
- c745e01b2e126fa33f799e05ae0c848b
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Response-Time:
- '116'
X-Transaction:
- 00d4dfbe006f4d83
X-Tsa-Request-Body-Time:
- '0'
X-Twitter-Response-Tags:
- BouncerCompliant
X-Ua-Compatible:
- IE=edge,chrome=1
X-Xss-Protection:
- '0'
body:
encoding: UTF-8
string: '{"token_type":"bearer","access_token":"ACCESS_TOKEN"}'
http_version: null
recorded_at: Mon, 18 May 2020 14:46:21 GMT
- request:
method: get
uri: https://api.twitter.com/1.1/statuses/show/0.json?tweet_mode=extended
body:
encoding: UTF-8
string: ''
headers:
User-Agent:
- TwitterRubyGem/7.0.0 (http://localhost:3000)
Connection:
- close
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:
- '71'
Content-Type:
- application/json; charset=utf-8
Date:
- Mon, 18 May 2020 14:46:21 GMT
Expires:
- Tue, 31 Mar 1981 05:00:00 GMT
Last-Modified:
- Mon, 18 May 2020 14:46:21 GMT
Pragma:
- no-cache
Server:
- tsa_o
Status:
- 404 Not Found
Strict-Transport-Security:
- max-age=631138519
X-Connection-Hash:
- aa96b1523d91f21bc57cc858a291326c
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- SAMEORIGIN
X-Rate-Limit-Limit:
- '900'
X-Rate-Limit-Remaining:
- '898'
X-Rate-Limit-Reset:
- '1589814001'
X-Response-Time:
- '116'
X-Transaction:
- 00d8302700668f57
X-Twitter-Response-Tags:
- BouncerCompliant
X-Xss-Protection:
- '0'
body:
encoding: UTF-8
string: '{"errors":[{"code":8,"message":"No data available for specified ID."}]}'
http_version: null
recorded_at: Mon, 18 May 2020 14:46:21 GMT
recorded_with: VCR 5.1.0

View file

@ -0,0 +1,144 @@
---
http_interactions:
- request:
method: post
uri: https://api.twitter.com/oauth2/token
body:
encoding: UTF-8
string: grant_type=client_credentials
headers:
User-Agent:
- TwitterRubyGem/7.0.0 (http://localhost:3000)
Accept:
- "*/*"
Connection:
- close
Content-Type:
- application/x-www-form-urlencoded
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:
- '153'
Content-Type:
- application/json;charset=utf-8
Date:
- Mon, 18 May 2020 14:25:50 GMT
Expires:
- Tue, 31 Mar 1981 05:00:00 GMT
Last-Modified:
- Mon, 18 May 2020 14:25:50 GMT
Ml:
- A
Pragma:
- no-cache
Server:
- tsa_o
Status:
- 200 OK
Strict-Transport-Security:
- max-age=631138519
X-Connection-Hash:
- 3ac1eba3289eeef0ab2b6cc87580e474
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Response-Time:
- '110'
X-Transaction:
- 003aeaa5005d5e26
X-Tsa-Request-Body-Time:
- '0'
X-Twitter-Response-Tags:
- BouncerCompliant
X-Ua-Compatible:
- IE=edge,chrome=1
X-Xss-Protection:
- '0'
body:
encoding: UTF-8
string: '{"token_type":"bearer","access_token":"ACCESS_TOKEN"}'
http_version: null
recorded_at: Mon, 18 May 2020 14:25:50 GMT
- request:
method: get
uri: https://api.twitter.com/1.1/statuses/show/1242938461784608770.json?tweet_mode=extended
body:
encoding: UTF-8
string: ''
headers:
User-Agent:
- TwitterRubyGem/7.0.0 (http://localhost:3000)
Connection:
- close
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:
- '2533'
Content-Type:
- application/json;charset=utf-8
Date:
- Mon, 18 May 2020 14:25:51 GMT
Expires:
- Tue, 31 Mar 1981 05:00:00 GMT
Last-Modified:
- Mon, 18 May 2020 14:25:51 GMT
Pragma:
- no-cache
Server:
- tsa_o
Status:
- 200 OK
Strict-Transport-Security:
- max-age=631138519
X-Access-Level:
- read
X-Connection-Hash:
- 988e2ce230c1c018731301f04e0f8b73
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- SAMEORIGIN
X-Rate-Limit-Limit:
- '900'
X-Rate-Limit-Remaining:
- '899'
X-Rate-Limit-Reset:
- '1589812851'
X-Response-Time:
- '134'
X-Transaction:
- 001c06cc00a36c5e
X-Twitter-Response-Tags:
- BouncerCompliant
X-Xss-Protection:
- '0'
body:
encoding: UTF-8
string: '{"created_at":"Wed Mar 25 22:16:36 +0000 2020","id":1242938461784608770,"id_str":"1242938461784608770","full_text":"@MattDomm_
Yes! :D","truncated":false,"display_text_range":[11,18],"entities":{"hashtags":[],"symbols":[],"user_mentions":[{"screen_name":"MattDomm_","name":"Matt
Domm","id":45042829,"id_str":"45042829","indices":[0,10]}],"urls":[]},"source":"\u003ca
href=\"https:\/\/mobile.twitter.com\" rel=\"nofollow\"\u003eTwitter Web App\u003c\/a\u003e","in_reply_to_status_id":1242837746315669505,"in_reply_to_status_id_str":"1242837746315669505","in_reply_to_user_id":45042829,"in_reply_to_user_id_str":"45042829","in_reply_to_screen_name":"MattDomm_","user":{"id":1105243879589195777,"id_str":"1105243879589195777","name":"Codeland","screen_name":"codelandconf","location":"NYC","description":"Produced
by @ThePracticalDev @CodeNewbies. The conference for new and growing developers.","url":"https:\/\/t.co\/Yu6ibinH6I","entities":{"url":{"urls":[{"url":"https:\/\/t.co\/Yu6ibinH6I","expanded_url":"https:\/\/codelandconf.com","display_url":"codelandconf.com","indices":[0,23]}]},"description":{"urls":[]}},"protected":false,"followers_count":2414,"friends_count":15,"listed_count":33,"created_at":"Mon
Mar 11 23:07:30 +0000 2019","favourites_count":2851,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":false,"statuses_count":2542,"lang":null,"contributors_enabled":false,"is_translator":false,"is_translation_enabled":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_image_url":"http:\/\/pbs.twimg.com\/profile_images\/1233079056150736896\/qkSuQpiX_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/1233079056150736896\/qkSuQpiX_normal.jpg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/1105243879589195777\/1585092823","profile_link_color":"FF01FF","profile_sidebar_border_color":"000000","profile_sidebar_fill_color":"000000","profile_text_color":"000000","profile_use_background_image":false,"has_extended_profile":false,"default_profile":false,"default_profile_image":false,"following":null,"follow_request_sent":null,"notifications":null,"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: null
recorded_at: Mon, 18 May 2020 14:25:51 GMT
recorded_with: VCR 5.1.0

View file

@ -0,0 +1,298 @@
---
http_interactions:
- request:
method: post
uri: https://api.twitter.com/oauth2/token
body:
encoding: UTF-8
string: grant_type=client_credentials
headers:
User-Agent:
- TwitterRubyGem/7.0.0 (http://localhost:3000)
Accept:
- "*/*"
Connection:
- close
Content-Type:
- application/x-www-form-urlencoded
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:
- '153'
Content-Type:
- application/json;charset=utf-8
Date:
- Mon, 18 May 2020 14:59:16 GMT
Expires:
- Tue, 31 Mar 1981 05:00:00 GMT
Last-Modified:
- Mon, 18 May 2020 14:59:16 GMT
Ml:
- A
Pragma:
- no-cache
Server:
- tsa_o
Status:
- 200 OK
Strict-Transport-Security:
- max-age=631138519
X-Connection-Hash:
- 0ed066348bd2a09f1a4ab67364222291
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Response-Time:
- '124'
X-Transaction:
- 00bc6963002453ac
X-Tsa-Request-Body-Time:
- '0'
X-Twitter-Response-Tags:
- BouncerCompliant
X-Ua-Compatible:
- IE=edge,chrome=1
X-Xss-Protection:
- '0'
body:
encoding: UTF-8
string: '{"token_type":"bearer","access_token":"ACCESS_TOKEN"}'
http_version: null
recorded_at: Mon, 18 May 2020 14:59:16 GMT
- request:
method: get
uri: https://api.twitter.com/1.1/statuses/show/1262395854469677058.json?tweet_mode=extended
body:
encoding: UTF-8
string: ''
headers:
User-Agent:
- TwitterRubyGem/7.0.0 (http://localhost:3000)
Connection:
- close
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:
- '5843'
Content-Type:
- application/json;charset=utf-8
Date:
- Mon, 18 May 2020 14:59:16 GMT
Expires:
- Tue, 31 Mar 1981 05:00:00 GMT
Last-Modified:
- Mon, 18 May 2020 14:59:16 GMT
Pragma:
- no-cache
Server:
- tsa_o
Status:
- 200 OK
Strict-Transport-Security:
- max-age=631138519
X-Access-Level:
- read
X-Connection-Hash:
- f049dc981bb59177959f2e672d20ab06
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- SAMEORIGIN
X-Rate-Limit-Limit:
- '900'
X-Rate-Limit-Remaining:
- '896'
X-Rate-Limit-Reset:
- '1589814001'
X-Response-Time:
- '142'
X-Transaction:
- '009e83bf00c3d0db'
X-Twitter-Response-Tags:
- BouncerCompliant
X-Xss-Protection:
- '0'
body:
encoding: UTF-8
string: '{"created_at":"Mon May 18 14:53:20 +0000 2020","id":1262395854469677058,"id_str":"1262395854469677058","full_text":"RT
@ThePracticalDev: \"The goal of this guide is to provide enough knowledge
to be conversational, as well as to give a strong jumping off p\u2026","truncated":false,"display_text_range":[0,140],"entities":{"hashtags":[],"symbols":[],"user_mentions":[{"screen_name":"ThePracticalDev","name":"DEV
Community \ud83d\udc69\u200d\ud83d\udcbb\ud83d\udc68\u200d\ud83d\udcbb","id":2735246778,"id_str":"2735246778","indices":[3,19]}],"urls":[]},"source":"\u003ca
href=\"http:\/\/twitter.com\/download\/iphone\" rel=\"nofollow\"\u003eTwitter
for iPhone\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":815325836,"id_str":"815325836","name":"Great","screen_name":"01Blows","location":"","description":"","url":null,"entities":{"description":{"urls":[]}},"protected":false,"followers_count":28,"friends_count":106,"listed_count":14,"created_at":"Mon
Sep 10 14:20:14 +0000 2012","favourites_count":62,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":false,"statuses_count":2686,"lang":null,"contributors_enabled":false,"is_translator":false,"is_translation_enabled":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_image_url":"http:\/\/pbs.twimg.com\/profile_images\/2970316093\/4b4015e8e6fe419e375d1e22d3349889_normal.jpeg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/2970316093\/4b4015e8e6fe419e375d1e22d3349889_normal.jpeg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/815325836\/1355402666","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":false,"following":null,"follow_request_sent":null,"notifications":null,"translator_type":"none"},"geo":null,"coordinates":null,"place":null,"contributors":null,"retweeted_status":{"created_at":"Mon
May 18 14:32:00 +0000 2020","id":1262390485399805953,"id_str":"1262390485399805953","full_text":"\"The
goal of this guide is to provide enough knowledge to be conversational, as
well as to give a strong jumping off point if you were to start learning TypeScript
on the job.\"\n\n\u007b author: @shane__lonergan \u007d #DEVCommunity https:\/\/t.co\/K5V4s91cJy","truncated":false,"display_text_range":[0,244],"entities":{"hashtags":[{"text":"DEVCommunity","indices":[207,220]}],"symbols":[],"user_mentions":[{"screen_name":"shane__lonergan","name":"Shane
Lonergan \ud83c\udfa4\u2328\ufe0f","id":1164944561304690688,"id_str":"1164944561304690688","indices":[188,204]}],"urls":[{"url":"https:\/\/t.co\/K5V4s91cJy","expanded_url":"https:\/\/dev.to\/shane__lonergan\/typescript-in-a-weekend-a-crash-course-2171","display_url":"dev.to\/shane__lonerga\u2026","indices":[221,244]}]},"source":"\u003ca
href=\"https:\/\/buffer.com\" rel=\"nofollow\"\u003eBuffer\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":2735246778,"id_str":"2735246778","name":"DEV
Community \ud83d\udc69\u200d\ud83d\udcbb\ud83d\udc68\u200d\ud83d\udcbb","screen_name":"ThePracticalDev","location":"","description":"Great
posts from the amazing https:\/\/t.co\/xHvFQQ9jeO community, with some opinion
and humor mixed in. Created by @bendhalpern. Join https:\/\/t.co\/xHvFQQ9jeO!","url":"https:\/\/t.co\/lhcCPP1ReQ","entities":{"url":{"urls":[{"url":"https:\/\/t.co\/lhcCPP1ReQ","expanded_url":"http:\/\/dev.to","display_url":"dev.to","indices":[0,23]}]},"description":{"urls":[{"url":"https:\/\/t.co\/xHvFQQ9jeO","expanded_url":"https:\/\/dev.to","display_url":"dev.to","indices":[29,52]},{"url":"https:\/\/t.co\/xHvFQQ9jeO","expanded_url":"https:\/\/dev.to","display_url":"dev.to","indices":[132,155]}]}},"protected":false,"followers_count":191666,"friends_count":2311,"listed_count":3793,"created_at":"Fri
Aug 15 19:11:17 +0000 2014","favourites_count":48516,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":true,"statuses_count":38690,"lang":null,"contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"131516","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_image_url":"http:\/\/pbs.twimg.com\/profile_images\/1253165670935773185\/SkSoEQL3_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/1253165670935773185\/SkSoEQL3_normal.jpg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/2735246778\/1557714134","profile_link_color":"14BD7B","profile_sidebar_border_color":"000000","profile_sidebar_fill_color":"000000","profile_text_color":"000000","profile_use_background_image":true,"has_extended_profile":false,"default_profile":false,"default_profile_image":false,"following":null,"follow_request_sent":null,"notifications":null,"translator_type":"none"},"geo":null,"coordinates":null,"place":null,"contributors":null,"is_quote_status":false,"retweet_count":9,"favorite_count":19,"favorited":false,"retweeted":false,"possibly_sensitive":false,"possibly_sensitive_appealable":false,"lang":"en"},"is_quote_status":false,"retweet_count":9,"favorite_count":0,"favorited":false,"retweeted":false,"lang":"en"}'
http_version: null
recorded_at: Mon, 18 May 2020 14:59:16 GMT
- request:
method: post
uri: https://api.twitter.com/oauth2/token
body:
encoding: UTF-8
string: grant_type=client_credentials
headers:
User-Agent:
- TwitterRubyGem/7.0.0 (http://localhost:3000)
Accept:
- "*/*"
Connection:
- close
Content-Type:
- application/x-www-form-urlencoded
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:
- '153'
Content-Type:
- application/json;charset=utf-8
Date:
- Mon, 18 May 2020 14:59:17 GMT
Expires:
- Tue, 31 Mar 1981 05:00:00 GMT
Last-Modified:
- Mon, 18 May 2020 14:59:17 GMT
Ml:
- A
Pragma:
- no-cache
Server:
- tsa_o
Status:
- 200 OK
Strict-Transport-Security:
- max-age=631138519
X-Connection-Hash:
- eeef3e85c1fa03a5d4e20d85d95cc05e
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Response-Time:
- '115'
X-Transaction:
- 00d5cdaa005fc247
X-Tsa-Request-Body-Time:
- '1'
X-Twitter-Response-Tags:
- BouncerCompliant
X-Ua-Compatible:
- IE=edge,chrome=1
X-Xss-Protection:
- '0'
body:
encoding: UTF-8
string: '{"token_type":"bearer","access_token":"ACCESS_TOKEN"}'
http_version: null
recorded_at: Mon, 18 May 2020 14:59:17 GMT
- request:
method: get
uri: https://api.twitter.com/1.1/statuses/show/1262390485399805953.json
body:
encoding: UTF-8
string: ''
headers:
User-Agent:
- TwitterRubyGem/7.0.0 (http://localhost:3000)
Connection:
- close
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:
- '3038'
Content-Type:
- application/json;charset=utf-8
Date:
- Mon, 18 May 2020 14:59:17 GMT
Expires:
- Tue, 31 Mar 1981 05:00:00 GMT
Last-Modified:
- Mon, 18 May 2020 14:59:17 GMT
Pragma:
- no-cache
Server:
- tsa_o
Status:
- 200 OK
Strict-Transport-Security:
- max-age=631138519
X-Access-Level:
- read
X-Connection-Hash:
- fd489dd89ded9e04dc9df080d7cbc52d
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- SAMEORIGIN
X-Rate-Limit-Limit:
- '900'
X-Rate-Limit-Remaining:
- '895'
X-Rate-Limit-Reset:
- '1589814001'
X-Response-Time:
- '130'
X-Transaction:
- 00dc05fb002b905b
X-Twitter-Response-Tags:
- BouncerCompliant
X-Xss-Protection:
- '0'
body:
encoding: UTF-8
string: '{"created_at":"Mon May 18 14:32:00 +0000 2020","id":1262390485399805953,"id_str":"1262390485399805953","text":"\"The
goal of this guide is to provide enough knowledge to be conversational, as
well as to give a strong jumping of\u2026 https:\/\/t.co\/firzrhsCY8","truncated":true,"entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[{"url":"https:\/\/t.co\/firzrhsCY8","expanded_url":"https:\/\/twitter.com\/i\/web\/status\/1262390485399805953","display_url":"twitter.com\/i\/web\/status\/1\u2026","indices":[117,140]}]},"source":"\u003ca
href=\"https:\/\/buffer.com\" rel=\"nofollow\"\u003eBuffer\u003c\/a\u003e","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":2735246778,"id_str":"2735246778","name":"DEV
Community \ud83d\udc69\u200d\ud83d\udcbb\ud83d\udc68\u200d\ud83d\udcbb","screen_name":"ThePracticalDev","location":"","description":"Great
posts from the amazing https:\/\/t.co\/xHvFQQ9jeO community, with some opinion
and humor mixed in. Created by @bendhalpern. Join https:\/\/t.co\/xHvFQQ9jeO!","url":"https:\/\/t.co\/lhcCPP1ReQ","entities":{"url":{"urls":[{"url":"https:\/\/t.co\/lhcCPP1ReQ","expanded_url":"http:\/\/dev.to","display_url":"dev.to","indices":[0,23]}]},"description":{"urls":[{"url":"https:\/\/t.co\/xHvFQQ9jeO","expanded_url":"https:\/\/dev.to","display_url":"dev.to","indices":[29,52]},{"url":"https:\/\/t.co\/xHvFQQ9jeO","expanded_url":"https:\/\/dev.to","display_url":"dev.to","indices":[132,155]}]}},"protected":false,"followers_count":191666,"friends_count":2311,"listed_count":3793,"created_at":"Fri
Aug 15 19:11:17 +0000 2014","favourites_count":48516,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":true,"statuses_count":38690,"lang":null,"contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"131516","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_image_url":"http:\/\/pbs.twimg.com\/profile_images\/1253165670935773185\/SkSoEQL3_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/1253165670935773185\/SkSoEQL3_normal.jpg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/2735246778\/1557714134","profile_link_color":"14BD7B","profile_sidebar_border_color":"000000","profile_sidebar_fill_color":"000000","profile_text_color":"000000","profile_use_background_image":true,"has_extended_profile":false,"default_profile":false,"default_profile_image":false,"following":null,"follow_request_sent":null,"notifications":null,"translator_type":"none"},"geo":null,"coordinates":null,"place":null,"contributors":null,"is_quote_status":false,"retweet_count":9,"favorite_count":19,"favorited":false,"retweeted":false,"possibly_sensitive":false,"possibly_sensitive_appealable":false,"lang":"en"}'
http_version: null
recorded_at: Mon, 18 May 2020 14:59:17 GMT
recorded_with: VCR 5.1.0

View file

@ -17,6 +17,13 @@ VCR.configure do |config|
# Removes all private data (Basic Auth, Set-Cookie headers...)
config.before_record do |i|
# Twitter embeds the Bearer access token in the JSON HTTP response
if i.request.uri.include?("api.twitter.com/oauth2/token")
data = JSON.parse(i.response.body)
data["access_token"] = "ACCESS_TOKEN"
i.response.body = data.to_json
end
i.response.headers.delete("Set-Cookie")
i.request.headers.delete("Authorization")
@ -24,14 +31,3 @@ VCR.configure do |config|
i.request.uri.sub!(/:\/\/.*#{Regexp.escape(u.host)}/, "://#{u.host}")
end
end
VCR_OPTIONS = {
twitter_fetch_status: {
cassette_name: "twitter_fetch_status",
allow_playback_repeats: true
},
rss_feeds: {
cassette_name: "rss_feeds",
allow_playback_repeats: true
}
}.freeze