jekyll-twitter-plugin 1.4.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Binary file
Binary file
Binary file
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+ RSpec.describe TwitterJekyll::ApiRequest do
3
+ subject(:api_request) { described_class.new(url, params) }
4
+ let(:url) { "https://twitter.com/twitter_user/status/12345" }
5
+ let(:params) { {} }
6
+
7
+ describe "#to_uri" do
8
+ subject(:uri) { api_request.to_uri }
9
+
10
+ it "uses correct api" do
11
+ expect(uri.scheme).to eq "https"
12
+ expect(uri.host).to eq "publish.twitter.com"
13
+ expect(uri.path).to eq "/oembed"
14
+ end
15
+
16
+ context "with no params" do
17
+ it "has url encoded query param" do
18
+ expect(URI.decode_www_form(uri.query)).to match_array [["url", url]]
19
+ end
20
+ end
21
+
22
+ context "with params" do
23
+ let(:params) { { align: "right" } }
24
+
25
+ it "has encoded query params" do
26
+ expect(URI.decode_www_form(uri.query)).to match_array [["url", url], %w(align right)]
27
+ end
28
+ end
29
+
30
+ context "with an incorrectly passed url param" do
31
+ let(:params) { { url: "why" } }
32
+
33
+ it "has uses the correct url" do
34
+ expect(URI.decode_www_form(uri.query)).to match_array [["url", url]]
35
+ end
36
+ end
37
+ end
38
+
39
+ describe "#cache_key" do
40
+ let(:url) { ["https://twitter.com/twitter_user/status/12345"] }
41
+
42
+ context "with no params" do
43
+ it "matches on status url" do
44
+ request_one = described_class.new(url, {})
45
+ request_two = described_class.new(url, {})
46
+
47
+ expect(
48
+ request_one.cache_key == request_two.cache_key
49
+ ).to be true
50
+ end
51
+
52
+ it "fails if different" do
53
+ request_one = described_class.new(url, {})
54
+ request_two = described_class.new("https://twitter.com/other_user/status/12345", {})
55
+
56
+ expect(
57
+ request_one.cache_key == request_two.cache_key
58
+ ).to be false
59
+ end
60
+ end
61
+
62
+ context "with params" do
63
+ it "matches on keys and values" do
64
+ request_one = described_class.new(url, align: "left")
65
+ request_two = described_class.new(url, align: "left")
66
+
67
+ expect(
68
+ request_one.cache_key == request_two.cache_key
69
+ ).to be true
70
+ end
71
+
72
+ it "fails if different" do
73
+ request_one = described_class.new(url, align: "left")
74
+ request_two = described_class.new(url, align: "right")
75
+
76
+ expect(
77
+ request_one.cache_key == request_two.cache_key
78
+ ).to be false
79
+ end
80
+ end
81
+ end
82
+ end
@@ -7,13 +7,20 @@ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
7
7
  require_relative "./support/jekyll_template"
8
8
  require "jekyll-twitter-plugin"
9
9
  require "erb"
10
+ require "byebug"
10
11
 
11
12
  OUTPUT_FILENAME = "output_test.html"
12
13
  OPTIONS = [
13
- "oembed https://twitter.com/rubygems/status/518821243320287232",
14
- "oembed https://twitter.com/rubygems/status/518821243320287232 align='right' width='350'",
14
+ "https://twitter.com/jekyllrb maxwidth=500 limit=5",
15
+ "https://twitter.com/rubygems",
16
+ "https://twitter.com/i/moments/650667182356082688 maxwidth=500",
17
+ "https://twitter.com/TwitterDev/timelines/539487832448843776 limit=5 widget_type=grid maxwidth=500",
15
18
  "https://twitter.com/rubygems/status/518821243320287232",
16
- "oembed https://twitter.com/rubygems/status/missing"
19
+ "https://twitter.com/rubygems/status/11",
20
+ "https://twitter.com/rubygems/status/518821243320287232 align=right width=350",
21
+ "https://twitter.com/Ace_Tate/status/225611299009216512",
22
+ "https://twitter.com/FeelsGood2BeMe/status/225456333032398848",
23
+ "oembed https://twitter.com/rubygems/status/518821243320287232",
17
24
  ].freeze
18
25
 
19
26
  COLOUR_MAP = {
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
3
+ require "webmock/rspec"
3
4
  require "support/jekyll_template"
4
5
  require "support/shared_contexts"
5
6
  require "jekyll-twitter-plugin"
@@ -11,11 +11,12 @@ RSpec.shared_context "without cached response" do
11
11
  end
12
12
  end
13
13
 
14
- RSpec.shared_context "with any oembed request and response" do
15
- let(:options) { "oembed https://twitter.com/twitter_user/status/12345" }
16
- let(:response) { OpenStruct.new(html: "<p>tweet html</p>") }
14
+ RSpec.shared_context "with a normal request and response" do
15
+ let(:arguments) { "https://twitter.com/twitter_user/status/12345" }
16
+ let(:response) { { html: "<p>tweet html</p>" } }
17
17
 
18
18
  before do
19
- allow(api_client).to receive(:oembed).and_return(response)
19
+ allow(api_client).to receive(:fetch).and_return(response)
20
+ allow(TwitterJekyll::ApiClient).to receive(:new).and_return(api_client)
20
21
  end
21
22
  end
@@ -1,26 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
  RSpec.describe TwitterJekyll::TwitterTag do
3
- let(:context) { double.as_null_object }
4
- let(:options) { "" }
5
- subject { described_class.new(nil, options, nil) }
3
+ let(:context) { empty_jekyll_context }
4
+ let(:arguments) { "" }
5
+ let(:api_response_hash) do
6
+ {
7
+ "url" => "https://twitter.com/twitter_user/status/12345",
8
+ "author_name" => "twitter user",
9
+ "author_url" => "https://twitter.com/twitter_user",
10
+ "html" => "<p>tweet html</p>",
11
+ "width" => 550,
12
+ "height" => nil,
13
+ "type" => "rich",
14
+ "cache_age" => "3153600000",
15
+ "provider_name" => "Twitter",
16
+ "provider_url" => "https://twitter.com",
17
+ "version" => "1.0"
18
+ }
19
+ end
20
+ subject { described_class.new(nil, arguments, nil) }
6
21
 
7
22
  describe "output from oembed request" do
8
- let(:api_response_hash) do
9
- {
10
- "url" => "https://twitter.com/twitter_user/status/12345",
11
- "author_name" => "twitter user",
12
- "author_url" => "https://twitter.com/twitter_user",
13
- "html" => "<p>tweet html</p>",
14
- "width" => 550,
15
- "height" => nil,
16
- "type" => "rich",
17
- "cache_age" => "3153600000",
18
- "provider_name" => "Twitter",
19
- "provider_url" => "https://twitter.com",
20
- "version" => "1.0"
21
- }
22
- end
23
- let(:options) { "oembed https://twitter.com/twitter_user/status/12345" }
23
+ let(:arguments) { "https://twitter.com/twitter_user/status/12345" }
24
24
 
25
25
  context "with cached response" do
26
26
  let(:cache) { double("TwitterJekyll::FileCache") }
@@ -29,7 +29,6 @@ RSpec.describe TwitterJekyll::TwitterTag do
29
29
  end
30
30
 
31
31
  it "renders response from cache" do
32
- allow(Twitter::REST::Client).to receive(:new).and_return(double.as_null_object)
33
32
  expect(cache).to receive(:read).with(an_instance_of(String)).and_return(api_response_hash)
34
33
 
35
34
  output = subject.render(context)
@@ -39,19 +38,18 @@ RSpec.describe TwitterJekyll::TwitterTag do
39
38
 
40
39
  context "without cached response" do
41
40
  let(:cache) { double("TwitterJekyll::FileCache") }
42
- let(:response) { build_response_object(api_response_hash) }
43
41
  before do
44
42
  subject.cache = cache
45
43
  allow(cache).to receive(:read).with(an_instance_of(String)).and_return(nil)
46
44
  end
47
45
 
48
46
  context "with successful api request" do
49
- it "renders response from api and writes to cache" do
50
- api_client = double("Twitter::REST::Client", status: double)
51
- allow(Twitter::REST::Client).to receive(:new).and_return(api_client)
47
+ before do
48
+ stub_api_request(status: 200, body: api_response_hash.to_json, headers: {})
49
+ end
52
50
 
53
- expect(api_client).to receive(:oembed).and_return(response)
54
- expect(cache).to receive(:write).with(an_instance_of(String), response)
51
+ it "renders response from api and writes to cache" do
52
+ expect(cache).to receive(:write).with(an_instance_of(String), api_response_hash)
55
53
 
56
54
  output = subject.render(context)
57
55
  expect_output_to_match_tag_content(output, api_response_hash.fetch("html"))
@@ -59,107 +57,106 @@ RSpec.describe TwitterJekyll::TwitterTag do
59
57
  end
60
58
 
61
59
  context "with a status not found api request" do
62
- it "renders response from api and does not write to cache" do
63
- api_client = double("Twitter::REST::Client", status: double)
64
- allow(Twitter::REST::Client).to receive(:new).and_return(api_client)
60
+ before do
61
+ stub_api_request(status: [404, "Not Found"], body: "", headers: {})
62
+ end
65
63
 
66
- expect(api_client).to receive(:status).and_raise(Twitter::Error::NotFound)
67
- expect(cache).not_to receive(:write).with(an_instance_of(String), response)
64
+ it "renders error response and writes to cache" do
65
+ expect(cache).to receive(:write).with(an_instance_of(String), an_instance_of(Hash))
68
66
 
69
67
  output = subject.render(context)
70
- expect_output_to_have_error(output, Twitter::Error::NotFound)
68
+ expect_output_to_have_error(output, "Not Found")
71
69
  end
72
70
  end
73
71
 
74
72
  context "with a status request not permitted api request" do
75
- it "renders response from api and does not write to cache" do
76
- api_client = double("Twitter::REST::Client", status: double)
77
- allow(Twitter::REST::Client).to receive(:new).and_return(api_client)
73
+ before do
74
+ stub_api_request(status: [403, "Forbidden"], body: "", headers: {})
75
+ end
78
76
 
79
- expect(api_client).to receive(:status).and_raise(Twitter::Error::Forbidden)
80
- expect(cache).not_to receive(:write).with(an_instance_of(String), response)
77
+ it "renders error response and writes to cache" do
78
+ expect(cache).to receive(:write).with(an_instance_of(String), an_instance_of(Hash))
81
79
 
82
80
  output = subject.render(context)
83
- expect_output_to_have_error(output, Twitter::Error::Forbidden)
81
+ expect_output_to_have_error(output, "Forbidden")
84
82
  end
85
83
  end
86
- end
87
- end
88
84
 
89
- describe "With an invalid request type" do
90
- context "without any arguments" do
91
- let(:options) { "" }
85
+ context "with a server error api request" do
86
+ before do
87
+ stub_api_request(status: [500, "Internal Server Error"], body: "", headers: {})
88
+ end
92
89
 
93
- it "raises an exception" do
94
- expect_to_raise_invalid_args_error(options) do
95
- tag = described_class.new(nil, options, nil)
96
- tag.render(context)
90
+ it "renders error response and writes to cache" do
91
+ expect(cache).to receive(:write).with(an_instance_of(String), an_instance_of(Hash))
92
+
93
+ output = subject.render(context)
94
+ expect_output_to_have_error(output, "Internal Server Error")
97
95
  end
98
96
  end
99
- end
100
97
 
101
- context "with an api request type not supported" do
102
- let(:options) { "unsupported https://twitter.com/twitter_user/status/12345" }
98
+ context "with api request that times out" do
99
+ before do
100
+ stub_api.to_timeout
101
+ end
103
102
 
104
- it "raises an exception" do
105
- expect_to_raise_invalid_args_error(options) do
106
- tag = described_class.new(nil, options, nil)
107
- tag.render(context)
103
+ it "renders error response and writes to cache" do
104
+ expect(cache).to receive(:write).with(an_instance_of(String), an_instance_of(Hash))
105
+
106
+ output = subject.render(context)
107
+ expect_output_to_have_error(output, "Timeout::Error")
108
108
  end
109
109
  end
110
- end
111
110
 
112
- context "without an api request type and no valid status url" do
113
- let(:options) { "https://anything.com/twitter_user/status/12345" }
111
+ context "with the oembed api type as the first argument" do
112
+ let(:arguments) { "oembed https://twitter.com/twitter_user/status/12345" }
113
+ before do
114
+ stub_api_request(status: 200, body: api_response_hash.to_json, headers: {})
115
+ end
114
116
 
115
- it "raises an exception" do
116
- expect_to_raise_invalid_args_error(options) do
117
- tag = described_class.new(nil, options, nil)
118
- tag.render(context)
117
+ it "renders response from api and writes to cache" do
118
+ expect(cache).to receive(:write).with(an_instance_of(String), api_response_hash)
119
+
120
+ output = subject.render(context)
121
+ expect_output_to_match_tag_content(output, api_response_hash.fetch("html"))
119
122
  end
120
123
  end
121
124
  end
122
125
  end
123
126
 
124
- describe "parsing api request type" do
125
- include_context "without cached response"
126
- let(:response) { OpenStruct.new(html: "anything") }
127
- let(:status) { double }
128
-
129
- context "with oembed requested" do
130
- let(:options) { "oembed https://twitter.com/twitter_user/status/12345" }
131
-
132
- it "uses the oembed api" do
133
- api_client = double("Twitter::REST::Client", status: status)
134
- allow(Twitter::REST::Client).to receive(:new).and_return(api_client)
127
+ describe "parsing arguments" do
128
+ context "without any arguments" do
129
+ let(:arguments) { "" }
135
130
 
136
- expect(api_client).to receive(:oembed).with(status, {}).and_return(response)
137
- subject.render(context)
131
+ it "raises an exception" do
132
+ expect_to_raise_invalid_args_error(arguments) do
133
+ tag = described_class.new(nil, arguments, nil)
134
+ tag.render(context)
135
+ end
138
136
  end
139
137
  end
140
138
 
141
- context "without an api request type" do
142
- let(:options) { "https://twitter.com/twitter_user/status/12345" }
139
+ context "with the oembed api type as the first argument" do
140
+ let(:arguments) { "oembed https://twitter.com/twitter_user/status/12345" }
143
141
 
144
- it "uses the default oembed api type" do
145
- api_client = double("Twitter::REST::Client", status: status)
146
- allow(Twitter::REST::Client).to receive(:new).and_return(api_client)
142
+ it "uses correct twitter url and warns of deprecation" do
143
+ api_client = api_client_double
144
+ allow(api_client).to receive(:fetch).and_return({})
145
+ allow(TwitterJekyll::ApiClient).to receive(:new).and_return(api_client)
146
+ expect(TwitterJekyll::ApiRequest).to receive(:new).with("https://twitter.com/twitter_user/status/12345", {}).and_call_original
147
147
 
148
- expect(api_client).to receive(:oembed).with(status, {}).and_return(response)
149
- subject.render(context)
148
+ expect do
149
+ tag = described_class.new(nil, arguments, nil)
150
+ tag.render(context)
151
+ end.to output(/Passing 'oembed' as the first argument is not required anymore/).to_stderr
150
152
  end
151
153
  end
152
154
  end
153
155
 
154
156
  describe "parsing api secrets" do
155
157
  include_context "without cached response"
156
- include_context "with any oembed request and response"
157
- let(:api_client) { double("Twitter::REST::Client", status: double) }
158
- let(:config_builder) { double }
159
-
160
- before do
161
- allow(Twitter::REST::Client).to receive(:new).and_yield(config_builder).and_return(api_client)
162
- end
158
+ include_context "with a normal request and response"
159
+ let(:api_client) { api_client_double }
163
160
 
164
161
  context "with api secrets provided by ENV" do
165
162
  let(:context) { double("context", registers: { site: double(config: {}) }) }
@@ -170,13 +167,11 @@ RSpec.describe TwitterJekyll::TwitterTag do
170
167
  "TWITTER_ACCESS_TOKEN_SECRET" => "access_token_secret")
171
168
  end
172
169
 
173
- it "creates api client correctly" do
174
- expect(config_builder).to receive(:consumer_key=).with("consumer_key")
175
- expect(config_builder).to receive(:consumer_secret=).with("consumer_secret")
176
- expect(config_builder).to receive(:access_token=).with("access_token")
177
- expect(config_builder).to receive(:access_token_secret=).with("access_token_secret")
178
-
179
- subject.render(context)
170
+ it "warns of deprecation" do
171
+ expect do
172
+ tag = described_class.new(nil, arguments, nil)
173
+ tag.render(context)
174
+ end.to output(/Found Twitter API keys in ENV, this library does not require these keys anymore/).to_stderr
180
175
  end
181
176
  end
182
177
 
@@ -191,40 +186,55 @@ RSpec.describe TwitterJekyll::TwitterTag do
191
186
  stub_const("ENV", {})
192
187
  end
193
188
 
194
- it "creates api client correctly" do
195
- expect(config_builder).to receive(:consumer_key=).with("consumer_key")
196
- expect(config_builder).to receive(:consumer_secret=).with("consumer_secret")
197
- expect(config_builder).to receive(:access_token=).with("access_token")
198
- expect(config_builder).to receive(:access_token_secret=).with("access_token_secret")
199
-
200
- subject.render(context)
189
+ it "warns of deprecation" do
190
+ expect do
191
+ tag = described_class.new(nil, arguments, nil)
192
+ tag.render(context)
193
+ end.to output(/Found Twitter API keys in Jekyll _config.yml, this library does not require these keys anymore/).to_stderr
201
194
  end
202
195
  end
203
196
 
204
197
  context "with no api secrets provided" do
205
- let(:context) { double("context", registers: { site: double(config: {}) }) }
198
+ let(:context) { empty_jekyll_context }
206
199
  before do
207
200
  stub_const("ENV", {})
208
201
  end
209
202
 
210
- it "raises an exception" do
203
+ it "does not warn" do
211
204
  expect do
212
205
  subject.render(context)
213
- end.to raise_error(TwitterJekyll::MissingApiKeyError)
206
+ end.to_not output.to_stderr
214
207
  end
215
208
  end
216
209
  end
217
210
 
218
211
  private
219
212
 
213
+ def stub_api_request(response)
214
+ stub_api
215
+ .to_return(response)
216
+ end
217
+
218
+ def stub_api
219
+ stub_request(:get, /publish.twitter.com/)
220
+ end
221
+
222
+ def empty_jekyll_context
223
+ double("context", registers: { site: double(config: {}) })
224
+ end
225
+
226
+ def api_client_double
227
+ double("TwitterJekyll::ApiClient")
228
+ end
229
+
220
230
  def expect_output_to_match_tag_content(actual, content)
221
231
  expect(actual).to eq(
222
- "<div class='embed twitter'>#{content}</div>"
232
+ "<div class='jekyll-twitter-plugin'>#{content}</div>"
223
233
  )
224
234
  end
225
235
 
226
236
  def expect_output_to_have_error(actual, error, tweet_url = "https://twitter.com/twitter_user/status/12345")
227
- expect_output_to_match_tag_content(actual, "<p>There was a '#{error}' error fetching Tweet '#{tweet_url}'</p>")
237
+ expect_output_to_match_tag_content(actual, "<p>There was a '#{error}' error fetching URL: '#{tweet_url}'</p>")
228
238
  end
229
239
 
230
240
  def expect_to_raise_invalid_args_error(options)
@@ -235,9 +245,4 @@ RSpec.describe TwitterJekyll::TwitterTag do
235
245
  yield
236
246
  end.to raise_error(ArgumentError, message)
237
247
  end
238
-
239
- # The twitter gem responds with a struct like object so we do too.
240
- def build_response_object(response)
241
- OpenStruct.new(response)
242
- end
243
248
  end