nytimes-articles 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,73 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{nytimes-articles}
5
+ s.version = "0.4.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Jacob Harris"]
9
+ s.date = %q{2009-07-23}
10
+ s.description = %q{A gem for accessing the New York Times Article Search API}
11
+ s.email = %q{jharris@nytimes.com}
12
+ s.extra_rdoc_files = [
13
+ "LICENSE",
14
+ "README"
15
+ ]
16
+ s.files = [
17
+ ".gitignore",
18
+ "HISTORY",
19
+ "LICENSE",
20
+ "README",
21
+ "Rakefile",
22
+ "VERSION.yml",
23
+ "features/nytimes_articles.feature",
24
+ "features/steps/nytimes_articles_steps.rb",
25
+ "features/support/env.rb",
26
+ "lib/nytimes_articles.rb",
27
+ "lib/nytimes_articles/article.rb",
28
+ "lib/nytimes_articles/base.rb",
29
+ "lib/nytimes_articles/exceptions.rb",
30
+ "lib/nytimes_articles/facet.rb",
31
+ "lib/nytimes_articles/facet_hash.rb",
32
+ "lib/nytimes_articles/query.rb",
33
+ "lib/nytimes_articles/result_set.rb",
34
+ "lib/nytimes_articles/thumbnail.rb",
35
+ "nytimes-articles.gemspec",
36
+ "script/console",
37
+ "test/nytimes/articles/test_article.rb",
38
+ "test/nytimes/articles/test_base.rb",
39
+ "test/nytimes/articles/test_facet.rb",
40
+ "test/nytimes/articles/test_query.rb",
41
+ "test/nytimes/articles/test_result_set.rb",
42
+ "test/nytimes/articles/test_thumbnail.rb",
43
+ "test/test_helper.rb"
44
+ ]
45
+ s.homepage = %q{http://github.com/harrisj/nytimes-articles}
46
+ s.rdoc_options = ["--charset=UTF-8"]
47
+ s.require_paths = ["lib"]
48
+ s.requirements = ["Unicode", "The htmlentities gem"]
49
+ s.rubygems_version = %q{1.3.5}
50
+ s.summary = %q{A gem for accessing the NYTimes Article Search API}
51
+ s.test_files = [
52
+ "test/nytimes/articles/test_article.rb",
53
+ "test/nytimes/articles/test_base.rb",
54
+ "test/nytimes/articles/test_facet.rb",
55
+ "test/nytimes/articles/test_query.rb",
56
+ "test/nytimes/articles/test_result_set.rb",
57
+ "test/nytimes/articles/test_thumbnail.rb",
58
+ "test/test_helper.rb"
59
+ ]
60
+
61
+ if s.respond_to? :specification_version then
62
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
63
+ s.specification_version = 3
64
+
65
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
66
+ s.add_runtime_dependency(%q<htmlentities>, [">= 0"])
67
+ else
68
+ s.add_dependency(%q<htmlentities>, [">= 0"])
69
+ end
70
+ else
71
+ s.add_dependency(%q<htmlentities>, [">= 0"])
72
+ end
73
+ end
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/nytimes_articles.rb'}"
9
+ puts "Loading nytimes_articles gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
@@ -0,0 +1,584 @@
1
+ require File.dirname(__FILE__) + '/../../test_helper.rb'
2
+
3
+ ARTICLE_API_HASH = {"page_facet"=>"8", "lead_paragraph"=>"", "classifiers_facet"=>["Top/News/Business", "Top/Classifieds/Job Market/Job Categories/Banking, Finance and Insurance", "Top/News/Business/Markets"], "title"=>"Wall St. Treads Water as It Waits on Washington", "nytd_title"=>"Wall St. Treads Water as It Waits on Washington", "byline"=>"By JACK HEALY", "body"=>"Wall Street held its breath on Monday as it awaited details on a banking bailout from Washington. Investors had expected to start the week with an announcement from the Treasury Department outlining its latest plans to stabilize the financial system. But the Obama administration delayed releasing the details until at least Tuesday to keep the focus", "material_type_facet"=>["News"], "url"=>"http://www.nytimes.com/2009/02/10/business/10markets.html", "publication_month"=>"02", "date"=>"20090210", "publication_year"=>"2009", "nytd_section_facet"=>["Business"], "source_facet"=>"The New York Times", "desk_facet"=>"Business", "publication_day"=>"10", "des_facet"=>["STOCKS AND BONDS"], "day_of_week_facet"=>"Tuesday"}
4
+
5
+ ARTICLE_API_HASH2 = {"page_facet"=>"29", "lead_paragraph"=>"", "geo_facet"=>["WALL STREET (NYC)"], "small_image_width"=>"75", "classifiers_facet"=>["Top/News/New York and Region", "Top/Classifieds/Job Market/Job Categories/Education", "Top/Features/Travel/Guides/Destinations/North America", "Top/Classifieds/Job Market/Job Categories/Banking, Finance and Insurance", "Top/Features/Travel/Guides/Destinations/North America/United States/New York", "Top/Features/Travel/Guides/Destinations/North America/United States", "Top/News/Education"], "title"=>"OUR TOWNS; As Pipeline to Wall Street Narrows, Princeton Students Adjust Sights", "nytd_title"=>"As Pipeline to Wall Street Narrows, Princeton Students Adjust Sights", "byline"=>"By PETER APPLEBOME", "body"=>"Princeton, N.J. There must be a screenplay in the fabulous Schoppe twins, Christine and Jennifer, Princeton University juniors from Houston. They had the same G.P.A. and SATs in high school, where they became Gold Award Girl Scouts , sort of the female version of Eagle Scouts. They live together and take all the same courses, wear identical necklac", "material_type_facet"=>["News"], "url"=>"http://www.nytimes.com/2009/02/08/nyregion/08towns.html", "publication_month"=>"02", "small_image_height"=>"75", "date"=>"20090208", "column_facet"=>"Our Towns", "small_image"=>"Y", "publication_year"=>"2009", "nytd_section_facet"=>["New York and Region", "Education"], "source_facet"=>"The New York Times", "org_facet"=>["PRINCETON UNIVERSITY"], "desk_facet"=>"New York Region", "publication_day"=>"08", "small_image_url"=>"http://graphics8.nytimes.com/images/2009/02/08/nyregion/08towns.751.jpg", "des_facet"=>["EDUCATION AND SCHOOLS", "BANKS AND BANKING"], "day_of_week_facet"=>"Sunday"}
6
+
7
+ class TestNytimes::TestArticles::TestArticle < Test::Unit::TestCase
8
+ include Nytimes::Articles
9
+
10
+ def setup
11
+ init_test_key
12
+ Article.stubs(:parse_reply)
13
+ end
14
+
15
+ context "Article.search" do
16
+ should "accept a String for the first argument that is passed through to the query in the API" do
17
+ Article.expects(:invoke).with(has_entry("query", "FOO BAR"))
18
+ Article.search "FOO BAR"
19
+ end
20
+
21
+ should "accept a Hash for the first argument" do
22
+ Article.expects(:invoke).with(has_entry("query", "FOO BAR"))
23
+ Article.search :query => 'FOO BAR', :page => 2
24
+ end
25
+
26
+ context "date ranges" do
27
+ should "pass a string argument to begin_date straight through" do
28
+ date = "20081212"
29
+ Article.expects(:invoke).with(has_entry("begin_date", date))
30
+ Article.search :begin_date => date
31
+ end
32
+
33
+ should "convert begin_date from a Date or Time to YYYYMMDD format" do
34
+ time = Time.now
35
+ Article.expects(:invoke).with(has_entry("begin_date", time.strftime("%Y%m%d")))
36
+ Article.search :begin_date => time
37
+ end
38
+
39
+ should "pass a string argument to end_date straight through" do
40
+ date = "20081212"
41
+ Article.expects(:invoke).with(has_entry("end_date", date))
42
+ Article.search :end_date => date
43
+ end
44
+
45
+ should "convert end_date from a Date or Time to YYYYMMDD format" do
46
+ time = Time.now
47
+ Article.expects(:invoke).with(has_entry("end_date", time.strftime("%Y%m%d")))
48
+ Article.search :end_date => time
49
+ end
50
+
51
+ should "raise an ArgumentError if the begin_date is NOT a string and does not respond_to strftime" do
52
+ assert_raise(ArgumentError) { Article.search :begin_date => 23 }
53
+ end
54
+
55
+ should "raise an ArgumentError if the end_date is NOT a string and does not respond_to strftime" do
56
+ assert_raise(ArgumentError) { Article.search :end_date => 23 }
57
+ end
58
+
59
+ context ":before" do
60
+ should "send the :before value through as a end_date" do
61
+ t = Time.now
62
+ Article.expects(:invoke).with(has_entry('end_date', t.strftime("%Y%m%d")))
63
+ Article.search :before => t
64
+ end
65
+
66
+ should "not send through :before as an argument to the API" do
67
+ t = Time.now
68
+ Article.expects(:invoke).with(Not(has_key('before')))
69
+ Article.search :before => t
70
+ end
71
+
72
+ should "raise an ArgumentError if the before_date is NOT a string and does not respond_to strftime" do
73
+ assert_raise(ArgumentError) { Article.search :before => 23 }
74
+ end
75
+
76
+ should "add a begin_date in 1980 if no :since or :begin_date argument is provided" do
77
+ Article.expects(:invoke).with(has_entry('begin_date', Article::EARLIEST_BEGIN_DATE))
78
+ Article.search :before => Time.now
79
+ end
80
+
81
+ should "not automatically add a begin_date is there is a :since argument" do
82
+ since = Time.now - 12000
83
+ Article.expects(:invoke).with(has_entry('begin_date', since.strftime("%Y%m%d")))
84
+ Article.search :before => Time.now, :since => since
85
+ end
86
+
87
+ should "not automatically add a begin_date if there is a :begin_date argument already" do
88
+ since = Time.now - 12000
89
+ Article.expects(:invoke).with(has_entry('begin_date', since.strftime("%Y%m%d")))
90
+ Article.search :before => Time.now, :begin_date => since
91
+ end
92
+
93
+ should "raise an ArgumentError if there is also an :end_date argument" do
94
+ assert_raise(ArgumentError) { Article.search :before => Time.now, :end_date => Time.now }
95
+ end
96
+ end
97
+
98
+ context ":since" do
99
+ should "send the :since value through as a begin_date" do
100
+ t = Time.now - 1200
101
+ Article.expects(:invoke).with(has_entry('begin_date', t.strftime("%Y%m%d")))
102
+ Article.search :since => t
103
+ end
104
+
105
+ should "not send through :since as an argument to the API" do
106
+ t = Time.now
107
+ Article.expects(:invoke).with(Not(has_key('since')))
108
+ Article.search :since => t
109
+ end
110
+
111
+ should "raise an ArgumentError if the before_date is NOT a string and does not respond_to strftime" do
112
+ assert_raise(ArgumentError) { Article.search :since => 23 }
113
+ end
114
+
115
+ # This is to fix an error where the begin and end date are the same
116
+ should "add a end_date of tomorrow if no :before or :end_date argument is provided" do
117
+ Article.expects(:invoke).with(has_entry('end_date', (Date.today + 1).strftime("%Y%m%d")))
118
+ Article.search :since => Date.today
119
+ end
120
+
121
+ should "not automatically add a end_date is there is a :before argument" do
122
+ since = '19990101'
123
+ Article.expects(:invoke).with(has_entry('end_date', '20030101'))
124
+ Article.search :before => '20030101', :since => since
125
+ end
126
+
127
+ should "not automatically add a end_date if there is a :end_date argument already" do
128
+ since = '19990101'
129
+ Article.expects(:invoke).with(has_entry('end_date', '20030101'))
130
+ Article.search :end_date => '20030101', :since => since
131
+ end
132
+
133
+ should "raise an ArgumentError if there is also an :begin_date argument" do
134
+ assert_raise(ArgumentError) { Article.search :since => Time.now, :begin_date => Time.now }
135
+ end
136
+ end
137
+ end
138
+
139
+ context "facets" do
140
+ should "accept a single string" do
141
+ Article.expects(:invoke).with(has_entry("facets", Facet::DATE))
142
+ Article.search "FOO BAR", :facets => Facet::DATE
143
+ end
144
+
145
+ should "accept an array of strings" do
146
+ Article.expects(:invoke).with(has_entry("facets", [Facet::DATE, Facet::GEO].join(',')))
147
+ Article.search "FOO BAR", :facets => [Facet::DATE, Facet::GEO]
148
+ end
149
+ end
150
+
151
+ context "only_facets" do
152
+ should "accept a String" do
153
+ Article.expects(:invoke).with(has_entry("query", "#{Facet::GEO}:[CALIFORNIA]"))
154
+ Article.search :only_facets => "#{Facet::GEO}:[CALIFORNIA]"
155
+ end
156
+
157
+ should "accept a single hash value Facet string to a term" do
158
+ Article.expects(:invoke).with(has_entry("query", "#{Facet::GEO}:[CALIFORNIA]"))
159
+ Article.search :only_facets => {Facet::GEO => 'CALIFORNIA'}
160
+ end
161
+
162
+ should "accept an Facet string hashed to an array terms" do
163
+ Article.expects(:invoke).with(has_entry("query", "#{Facet::GEO}:[CALIFORNIA] #{Facet::GEO}:[GREAT BRITAIN]"))
164
+ Article.search :only_facets => {Facet::GEO => ['CALIFORNIA', 'GREAT BRITAIN']}
165
+ end
166
+
167
+ should "accept a single Facet object" do
168
+ f = Facet.new(Facet::GEO, 'CALIFORNIA', 2394)
169
+ Article.expects(:invoke).with(has_entry("query", "#{Facet::GEO}:[CALIFORNIA]"))
170
+ Article.search :only_facets => f
171
+ end
172
+
173
+ should "accept an array of Facet objects" do
174
+ f = Facet.new(Facet::GEO, 'CALIFORNIA', 2394)
175
+ f2 = Facet.new(Facet::NYTD_ORGANIZATION, 'University Of California', 12)
176
+
177
+ Article.expects(:invoke).with(has_entry("query", "#{Facet::GEO}:[CALIFORNIA] #{Facet::NYTD_ORGANIZATION}:[University Of California]"))
178
+ Article.search :only_facets => [f, f2]
179
+ end
180
+
181
+ should "merge multiple Facets objects in the array of the same type into one array" do
182
+ f = Facet.new(Facet::GEO, 'CALIFORNIA', 2394)
183
+ f2 = Facet.new(Facet::GEO, 'IOWA', 12)
184
+
185
+ Article.expects(:invoke).with(has_entry("query", "#{Facet::GEO}:[CALIFORNIA] #{Facet::GEO}:[IOWA]"))
186
+ Article.search :only_facets => [f, f2]
187
+ end
188
+
189
+ should "not stomp on an existing query string" do
190
+ Article.expects(:invoke).with(has_entry("query", "ice cream #{Facet::GEO}:[CALIFORNIA]"))
191
+ Article.search "ice cream", :only_facets => {Facet::GEO => "CALIFORNIA"}
192
+ end
193
+ end
194
+
195
+ context "except_facets" do
196
+ should "accept a String" do
197
+ Article.expects(:invoke).with(has_entry("query", "-#{Facet::GEO}:[CALIFORNIA]"))
198
+ Article.search :except_facets => "-#{Facet::GEO}:[CALIFORNIA]"
199
+ end
200
+
201
+ should "accept a single hash value Facet string to a term" do
202
+ Article.expects(:invoke).with(has_entry("query", "-#{Facet::GEO}:[CALIFORNIA]"))
203
+ Article.search :except_facets => {Facet::GEO => 'CALIFORNIA'}
204
+ end
205
+
206
+ should "accept an Facet string hashed to an array terms" do
207
+ Article.expects(:invoke).with(has_entry("query", "-#{Facet::GEO}:[CALIFORNIA] -#{Facet::GEO}:[GREAT BRITAIN]"))
208
+ Article.search :except_facets => {Facet::GEO => ['CALIFORNIA', 'GREAT BRITAIN']}
209
+ end
210
+
211
+ should "accept a single Facet object" do
212
+ f = Facet.new(Facet::GEO, 'CALIFORNIA', 2394)
213
+ Article.expects(:invoke).with(has_entry("query", "-#{Facet::GEO}:[CALIFORNIA]"))
214
+ Article.search :except_facets => f
215
+ end
216
+
217
+ should "accept an array of Facet objects" do
218
+ f = Facet.new(Facet::GEO, 'CALIFORNIA', 2394)
219
+ f2 = Facet.new(Facet::NYTD_ORGANIZATION, 'University Of California', 12)
220
+
221
+ Article.expects(:invoke).with(has_entry("query", "-#{Facet::GEO}:[CALIFORNIA] -#{Facet::NYTD_ORGANIZATION}:[University Of California]"))
222
+ Article.search :except_facets => [f, f2]
223
+ end
224
+
225
+ should "merge multiple Facets objects in the array of the same type into one array" do
226
+ f = Facet.new(Facet::GEO, 'CALIFORNIA', 2394)
227
+ f2 = Facet.new(Facet::GEO, 'IOWA', 12)
228
+
229
+ Article.expects(:invoke).with(has_entry("query", "-#{Facet::GEO}:[CALIFORNIA] -#{Facet::GEO}:[IOWA]"))
230
+ Article.search :except_facets => [f, f2]
231
+ end
232
+
233
+ should "not stomp on an existing query string" do
234
+ Article.expects(:invoke).with(has_entry("query", "ice cream -#{Facet::GEO}:[CALIFORNIA]"))
235
+ Article.search "ice cream", :except_facets => {Facet::GEO => "CALIFORNIA"}
236
+ end
237
+ end
238
+
239
+ context ":fee" do
240
+ should "send through as fee:Y if set to true" do
241
+ Article.expects(:invoke).with(has_entry("query", "ice cream fee:Y"))
242
+ Article.search "ice cream", :fee => true
243
+ end
244
+
245
+ should "send through as -fee:Y if set to false" do
246
+ Article.expects(:invoke).with(has_entry("query", "ice cream -fee:Y"))
247
+ Article.search "ice cream", :fee => false
248
+ end
249
+ end
250
+
251
+ context ":fields" do
252
+ context "when not specified at all" do
253
+ should "pass all fields in a comma-delimited list" do
254
+ Article.expects(:invoke).with(has_entry('fields', Article::ALL_FIELDS.join(',')))
255
+ Article.search "FOO BAR", :fields => :all
256
+ end
257
+ end
258
+
259
+ context "for the :all argument" do
260
+ should "pass all fields in a comma-delimited list" do
261
+ Article.expects(:invoke).with(has_entry('fields', Article::ALL_FIELDS.join(',')))
262
+ Article.search "FOO BAR", :fields => :all
263
+ end
264
+ end
265
+
266
+ context "for the :basic argument" do
267
+ should "not send a fields argument to the api" do
268
+ Article.expects(:invoke).with(Not(has_key('fields')))
269
+ Article.search "FOO BAR", :fields => :basic
270
+ end
271
+ end
272
+
273
+ context "for the :none argument" do
274
+ should "request a blank space for the fields argument" do
275
+ Article.expects(:invoke).with(has_entry('fields', ' '))
276
+ Article.search "FOO BAR", :fields => :none
277
+ end
278
+
279
+ should "request the standard :facets if no :facets have been explicitly provided" do
280
+ Article.expects(:invoke).with(has_entry('facets', Facet::DEFAULT_RETURN_FACETS.join(',')))
281
+ Article.search "FOO BAR", :fields => :none
282
+ end
283
+
284
+ should "request the given :facets field if provided" do
285
+ Article.expects(:invoke).with(has_entry('facets', "#{Facet::GEO}"))
286
+ Article.search "FOO BAR", :fields => :none, :facets => Facet::GEO
287
+ end
288
+ end
289
+
290
+ context ":thumbnail" do
291
+ should "accept the symbol version of the argument" do
292
+ Article.expects(:invoke).with(has_entry('fields', Article::IMAGE_FIELDS.join(',')))
293
+ Article.search "FOO BAR", :fields => :thumbnail
294
+ end
295
+
296
+ should "accept the string version of the argument" do
297
+ Article.expects(:invoke).with(has_entry('fields', Article::IMAGE_FIELDS.join(',')))
298
+ Article.search "FOO BAR", :fields => 'thumbnail'
299
+ end
300
+ end
301
+
302
+ context ":multimedia" do
303
+ should "be implemented"
304
+ end
305
+
306
+ should "accept a single string as an argument" do
307
+ Article.expects(:invoke).with(has_entry('fields', 'body'))
308
+ Article.search "FOO BAR", :fields => 'body'
309
+ end
310
+
311
+ should "accept a single symbol as an argument" do
312
+ Article.expects(:invoke).with(has_entry('fields', 'body'))
313
+ Article.search "FOO BAR", :fields => :body
314
+ end
315
+
316
+ should "accept an array of strings and symbols" do
317
+ Article.expects(:invoke).with(has_entry('fields', 'abstract,body'))
318
+ Article.search "FOO BAR", :fields => [:abstract, 'body']
319
+ end
320
+
321
+ should "raise an ArgumentError otherwise" do
322
+ assert_raise(ArgumentError) { Article.search :fields => 12 }
323
+ end
324
+ end
325
+
326
+ context ":has_multimedia" do
327
+ should "send through as related_multimedia:Y if set to true" do
328
+ Article.expects(:invoke).with(has_entry("query", "ice cream related_multimedia:Y"))
329
+ Article.search "ice cream", :has_multimedia => true
330
+ end
331
+
332
+ should "send through as -related_multimedia:Y if set to false" do
333
+ Article.expects(:invoke).with(has_entry("query", "ice cream -related_multimedia:Y"))
334
+ Article.search "ice cream", :has_multimedia => false
335
+ end
336
+ end
337
+
338
+ context ":has_thumbnail" do
339
+ should "send through as small_image:Y if set to true" do
340
+ Article.expects(:invoke).with(has_entry("query", "ice cream small_image:Y"))
341
+ Article.search "ice cream", :has_thumbnail => true
342
+ end
343
+
344
+ should "send through as -small_image:Y if set to false" do
345
+ Article.expects(:invoke).with(has_entry("query", "ice cream -small_image:Y"))
346
+ Article.search "ice cream", :has_thumbnail => false
347
+ end
348
+ end
349
+
350
+ context ":offset" do
351
+ should "pass through an explicit offset parameter if specified" do
352
+ Article.expects(:invoke).with(has_entry("offset", 10))
353
+ Article.search :offset => 10
354
+ end
355
+
356
+ should "raise an ArgumentError if the offset is not an Integer" do
357
+ assert_raise(ArgumentError) { Article.search :offset => 'apple' }
358
+ end
359
+
360
+ should "pass through an offset of page - 1 if :page is used instead" do
361
+ Article.expects(:invoke).with(has_entry("offset", 2))
362
+ Article.search :page => 3
363
+ end
364
+
365
+ should "not pass through a page parameter to the API" do
366
+ Article.expects(:invoke).with(Not(has_key("page")))
367
+ Article.search :page => 3
368
+ end
369
+
370
+ should "raise an ArgumentError if the page is not an Integer" do
371
+ assert_raise(ArgumentError) { Article.search :page => 'orange' }
372
+ end
373
+
374
+ should "raise an ArgumentError if the page is less than 1" do
375
+ assert_raise(ArgumentError) { Article.search :page => 0 }
376
+ end
377
+
378
+ should "use the :offset argument if both an :offset and :page are provided" do
379
+ Article.expects(:invoke).with(has_entry("offset", 2))
380
+ Article.search :offset => 2, :page => 203
381
+ end
382
+ end
383
+
384
+ context "rank" do
385
+ %w(newest oldest closest).each do |rank|
386
+ should "accept #{rank} as the argument to rank" do
387
+ Article.expects(:invoke).with(has_entry("rank", rank))
388
+ Article.search :rank => rank.to_sym
389
+ end
390
+ end
391
+
392
+ should "raise an ArgumentError if rank is something else" do
393
+ assert_raise(ArgumentError) { Article.search :rank => :clockwise }
394
+ end
395
+ end
396
+
397
+ Article::TEXT_FIELDS.each do |tf|
398
+ context ":#{tf} parameter" do
399
+ should "prefix each non-quoted term with the #{tf}: field identifier in the query to the API" do
400
+ Article.expects(:invoke).with(has_entry("query", "#{tf}:ice #{tf}:cream"))
401
+ Article.search tf.to_sym => 'ice cream'
402
+ end
403
+
404
+ should "prefix -terms (excluded terms) with -#{tf}:" do
405
+ Article.expects(:invoke).with(has_entry("query", "#{tf}:ice -#{tf}:cream"))
406
+ Article.search tf.to_sym => 'ice -cream'
407
+ end
408
+
409
+ should "put quoted terms behind the field spec" do
410
+ Article.expects(:invoke).with(has_entry("query", "#{tf}:\"ice cream\" #{tf}:cone"))
411
+ Article.search tf.to_sym => '"ice cream" cone'
412
+ end
413
+
414
+ should "handle complicated combinations of expressions" do
415
+ Article.expects(:invoke).with(has_entry("query", "#{tf}:\"ice cream\" -#{tf}:cone #{tf}:\"waffle\""))
416
+ Article.search tf.to_sym => '"ice cream" -cone "waffle"'
417
+ end
418
+ end
419
+ end
420
+
421
+ # context "query parameters" do
422
+ # context "abstract" do
423
+ # should "be prefixed with the abstract: field identifier in the query"
424
+ # should "cast the argument to a string (will figure out processing later)"
425
+ # end
426
+ #
427
+ # context "author" do
428
+ # should "be prefixed with the author: field identifier in the query"
429
+ # should "cast the argument to a string (will figure out processing later)"
430
+ # end
431
+ #
432
+ # context "body" do
433
+ # should "be prefixed with the body: field identifier in the query"
434
+ # should "cast the argument to a string (will figure out processing later)"
435
+ # end
436
+ #
437
+ # context "byline" do
438
+ # should "be prefixed with the body: field identifier in the query"
439
+ # should "cast the argument to a string (will figure out processing later)"
440
+ # end
441
+ # end
442
+ end
443
+
444
+ context "Article.init_from_api" do
445
+ setup do
446
+ @article = Article.init_from_api(ARTICLE_API_HASH2)
447
+ end
448
+
449
+ Article::TEXT_FIELDS.each do |tf|
450
+ context "@#{tf}" do
451
+ should "read the value from the hash input" do
452
+ hash = {}
453
+ hash[tf] = "TEST TEXT"
454
+ article = Article.init_from_api(hash)
455
+ assert_equal "TEST TEXT", article.send(tf)
456
+ end
457
+
458
+ should "properly translate HTML entities back into characters" do
459
+ article = Article.init_from_api(tf => '&#8220;Money for Nothing&#8221;')
460
+ assert_equal "“Money for Nothing”", article.send(tf), article.inspect
461
+ end
462
+
463
+ should "only provide read-only access to the field" do
464
+ article = Article.init_from_api(tf => "TEST TEXT")
465
+ assert !article.respond_to?("#{tf}=")
466
+ end
467
+
468
+ should "return nil if the value is not provided in the hash" do
469
+ article = Article.init_from_api({"foo" => "bar"})
470
+ assert_nil article.send(tf)
471
+ end
472
+ end
473
+ end
474
+
475
+ Article::NUMERIC_FIELDS.each do |tf|
476
+ context "@#{tf}" do
477
+ should "read and coerce the string value from the hash input" do
478
+ article = Article.init_from_api(tf => "23")
479
+ assert_equal 23, article.send(tf)
480
+ end
481
+
482
+ should "only provide read-only access to the field" do
483
+ article = Article.init_from_api(tf => "23")
484
+ assert !article.respond_to?("#{tf}=")
485
+ end
486
+
487
+ should "return nil if the value is not provided in the hash" do
488
+ article = Article.init_from_api({"foo" => "bar"})
489
+ assert_nil article.send(tf)
490
+ end
491
+ end
492
+ end
493
+
494
+ # all the rest
495
+ context "@fee" do
496
+ setup do
497
+ @article = Article.init_from_api(ARTICLE_API_HASH)
498
+ end
499
+
500
+ should "be true if returned as true from the API" do
501
+ article = Article.init_from_api('fee' => true)
502
+ assert_equal true, article.fee?
503
+ assert_equal false, article.free?
504
+ end
505
+
506
+ should "be true if returned as Y from the API" do
507
+ article = Article.init_from_api('fee' => 'Y')
508
+ assert_equal true, article.fee?
509
+ assert_equal false, article.free?
510
+ end
511
+
512
+ should "default to false if not specified in the hash" do
513
+ assert_equal false, @article.fee?
514
+ assert_equal true, @article.free?
515
+ end
516
+
517
+ should "default to false if returned as N from the API" do
518
+ article = Article.init_from_api('fee' => 'N')
519
+ assert_equal false, article.fee?
520
+ assert_equal true, article.free?
521
+ end
522
+ end
523
+
524
+ context "@url" do
525
+ setup do
526
+ @article = Article.init_from_api(ARTICLE_API_HASH)
527
+ end
528
+
529
+ should "read the value from the hash" do
530
+ assert_equal ARTICLE_API_HASH['url'], @article.url
531
+ end
532
+
533
+ should "return a String" do
534
+ assert_kind_of(String, @article.url)
535
+ end
536
+
537
+ should "only provide read-only access to the field" do
538
+ assert !@article.respond_to?("url=")
539
+ end
540
+
541
+ should "return nil if the value is not provided in the hash" do
542
+ article = Article.init_from_api({"foo" => "bar"})
543
+ assert_nil article.url
544
+ end
545
+ end
546
+
547
+ context "@page" do
548
+ should "read the value from the page_facet field" do
549
+ assert_equal ARTICLE_API_HASH2['page_facet'].to_i, @article.page
550
+ end
551
+
552
+ should "only provide read-only access to the field" do
553
+ article = Article.new
554
+ assert !article.respond_to?("page=")
555
+ end
556
+
557
+ should "return nil if the value is not provided in the hash" do
558
+ article = Article.init_from_api({"foo" => "bar"})
559
+ assert_nil article.page
560
+ end
561
+ end
562
+
563
+ context "@thumbnail" do
564
+ should "assign nil to thumbnail otherwise" do
565
+ article = Article.init_from_api({"foo" => "bar"})
566
+ assert_nil article.thumbnail
567
+ end
568
+
569
+ should "create a thumbnail object if a small_image_url is part of the return hash" do
570
+ article = Article.init_from_api(ARTICLE_API_HASH2)
571
+ thumbnail = article.thumbnail
572
+ assert_not_nil thumbnail
573
+ assert_kind_of Thumbnail, thumbnail
574
+ assert_equal ARTICLE_API_HASH2['small_image_url'], thumbnail.url
575
+ assert_equal ARTICLE_API_HASH2['small_image_width'].to_i, thumbnail.width
576
+ assert_equal ARTICLE_API_HASH2['small_image_height'].to_i, thumbnail.height
577
+ end
578
+ end
579
+
580
+ context "array facets" do
581
+ should "have some tests for array facets"
582
+ end
583
+ end
584
+ end