jekyll-twitter-plugin 1.4.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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