feedjira 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG.md +162 -0
  6. data/Gemfile +17 -0
  7. data/Guardfile +5 -0
  8. data/README.md +242 -0
  9. data/Rakefile +6 -0
  10. data/benchmarks/README.md +90 -0
  11. data/benchmarks/basic.rb +31 -0
  12. data/benchmarks/feed_list.txt +10 -0
  13. data/benchmarks/feed_xml/apple.xml +149 -0
  14. data/benchmarks/feed_xml/cnn.xml +278 -0
  15. data/benchmarks/feed_xml/daring_fireball.xml +1697 -0
  16. data/benchmarks/feed_xml/engadget.xml +604 -0
  17. data/benchmarks/feed_xml/feedjira_commits.xml +370 -0
  18. data/benchmarks/feed_xml/gizmodo.xml +2 -0
  19. data/benchmarks/feed_xml/loop.xml +441 -0
  20. data/benchmarks/feed_xml/rails.xml +1938 -0
  21. data/benchmarks/feed_xml/white_house.xml +951 -0
  22. data/benchmarks/feed_xml/xkcd.xml +2 -0
  23. data/benchmarks/fetching_systems.rb +23 -0
  24. data/benchmarks/other_libraries.rb +73 -0
  25. data/feedjira.gemspec +27 -0
  26. data/lib/feedjira.rb +16 -0
  27. data/lib/feedjira/core_ext.rb +3 -0
  28. data/lib/feedjira/core_ext/date.rb +19 -0
  29. data/lib/feedjira/core_ext/string.rb +9 -0
  30. data/lib/feedjira/core_ext/time.rb +31 -0
  31. data/lib/feedjira/feed.rb +459 -0
  32. data/lib/feedjira/feed_entry_utilities.rb +66 -0
  33. data/lib/feedjira/feed_utilities.rb +103 -0
  34. data/lib/feedjira/parser.rb +20 -0
  35. data/lib/feedjira/parser/atom.rb +61 -0
  36. data/lib/feedjira/parser/atom_entry.rb +34 -0
  37. data/lib/feedjira/parser/atom_feed_burner.rb +22 -0
  38. data/lib/feedjira/parser/atom_feed_burner_entry.rb +35 -0
  39. data/lib/feedjira/parser/google_docs_atom.rb +28 -0
  40. data/lib/feedjira/parser/google_docs_atom_entry.rb +29 -0
  41. data/lib/feedjira/parser/itunes_rss.rb +50 -0
  42. data/lib/feedjira/parser/itunes_rss_item.rb +41 -0
  43. data/lib/feedjira/parser/itunes_rss_owner.rb +12 -0
  44. data/lib/feedjira/parser/rss.rb +24 -0
  45. data/lib/feedjira/parser/rss_entry.rb +37 -0
  46. data/lib/feedjira/parser/rss_feed_burner.rb +23 -0
  47. data/lib/feedjira/parser/rss_feed_burner_entry.rb +43 -0
  48. data/lib/feedjira/version.rb +3 -0
  49. data/spec/feedjira/feed_entry_utilities_spec.rb +62 -0
  50. data/spec/feedjira/feed_spec.rb +762 -0
  51. data/spec/feedjira/feed_utilities_spec.rb +273 -0
  52. data/spec/feedjira/parser/atom_entry_spec.rb +86 -0
  53. data/spec/feedjira/parser/atom_feed_burner_entry_spec.rb +47 -0
  54. data/spec/feedjira/parser/atom_feed_burner_spec.rb +56 -0
  55. data/spec/feedjira/parser/atom_spec.rb +76 -0
  56. data/spec/feedjira/parser/google_docs_atom_entry_spec.rb +22 -0
  57. data/spec/feedjira/parser/google_docs_atom_spec.rb +31 -0
  58. data/spec/feedjira/parser/itunes_rss_item_spec.rb +63 -0
  59. data/spec/feedjira/parser/itunes_rss_owner_spec.rb +18 -0
  60. data/spec/feedjira/parser/itunes_rss_spec.rb +58 -0
  61. data/spec/feedjira/parser/rss_entry_spec.rb +85 -0
  62. data/spec/feedjira/parser/rss_feed_burner_entry_spec.rb +85 -0
  63. data/spec/feedjira/parser/rss_feed_burner_spec.rb +57 -0
  64. data/spec/feedjira/parser/rss_spec.rb +57 -0
  65. data/spec/sample_feeds/AmazonWebServicesBlog.xml +797 -0
  66. data/spec/sample_feeds/AmazonWebServicesBlogFirstEntryContent.xml +63 -0
  67. data/spec/sample_feeds/AtomFeedWithSpacesAroundEquals.xml +61 -0
  68. data/spec/sample_feeds/FeedBurnerUrlNoAlternate.xml +28 -0
  69. data/spec/sample_feeds/GoogleDocsList.xml +188 -0
  70. data/spec/sample_feeds/HREFConsideredHarmful.xml +314 -0
  71. data/spec/sample_feeds/HREFConsideredHarmfulFirstEntry.xml +22 -0
  72. data/spec/sample_feeds/ITunesWithSpacesInAttributes.xml +63 -0
  73. data/spec/sample_feeds/PaulDixExplainsNothing.xml +175 -0
  74. data/spec/sample_feeds/PaulDixExplainsNothingAlternate.xml +175 -0
  75. data/spec/sample_feeds/PaulDixExplainsNothingFirstEntryContent.xml +19 -0
  76. data/spec/sample_feeds/PaulDixExplainsNothingWFW.xml +174 -0
  77. data/spec/sample_feeds/SamRuby.xml +583 -0
  78. data/spec/sample_feeds/TechCrunch.xml +1515 -0
  79. data/spec/sample_feeds/TechCrunchFirstEntry.xml +9 -0
  80. data/spec/sample_feeds/TechCrunchFirstEntryDescription.xml +3 -0
  81. data/spec/sample_feeds/TenderLovemaking.xml +516 -0
  82. data/spec/sample_feeds/TenderLovemakingFirstEntry.xml +66 -0
  83. data/spec/sample_feeds/TrotterCashionHome.xml +611 -0
  84. data/spec/sample_feeds/TypePadNews.xml +368 -0
  85. data/spec/sample_feeds/atom_with_link_tag_for_url_unmarked.xml +31 -0
  86. data/spec/sample_feeds/itunes.xml +67 -0
  87. data/spec/sample_feeds/pet_atom.xml +497 -0
  88. data/spec/spec_helper.rb +88 -0
  89. metadata +229 -0
@@ -0,0 +1,41 @@
1
+ module Feedjira
2
+
3
+ module Parser
4
+ # iTunes extensions to the standard RSS2.0 item
5
+ # Source: http://www.apple.com/itunes/whatson/podcasts/specs.html
6
+ class ITunesRSSItem
7
+ include SAXMachine
8
+ include FeedEntryUtilities
9
+
10
+ element :author
11
+ element :guid, :as => :entry_id
12
+ element :title
13
+ element :link, :as => :url
14
+ element :description, :as => :summary
15
+ element :"content:encoded", :as => :content
16
+ element :pubDate, :as => :published
17
+
18
+ # If author is not present use author tag on the item
19
+ element :"itunes:author", :as => :itunes_author
20
+ element :"itunes:block", :as => :itunes_block
21
+ element :"itunes:duration", :as => :itunes_duration
22
+ element :"itunes:explicit", :as => :itunes_explicit
23
+ element :"itunes:keywords", :as => :itunes_keywords
24
+ element :"itunes:subtitle", :as => :itunes_subtitle
25
+ element :"itunes:image", :value => :href, :as => :itunes_image
26
+ element :"itunes:isClosedCaptioned", :as => :itunes_closed_captioned
27
+ element :"itunes:order", :as => :itunes_order
28
+ # If summary is not present, use the description tag
29
+ element :"itunes:summary", :as => :itunes_summary
30
+ element :enclosure, :value => :length, :as => :enclosure_length
31
+ element :enclosure, :value => :type, :as => :enclosure_type
32
+ element :enclosure, :value => :url, :as => :enclosure_url
33
+
34
+ def guid
35
+ warn "Feedjira: ITunesRSSItem.guid is deprecated, please use `id` or `entry_id` instead. This will be removed in version 1.0"
36
+ id
37
+ end
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,12 @@
1
+ module Feedjira
2
+
3
+ module Parser
4
+ class ITunesRSSOwner
5
+ include SAXMachine
6
+ include FeedUtilities
7
+ element :"itunes:name", :as => :name
8
+ element :"itunes:email", :as => :email
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,24 @@
1
+ module Feedjira
2
+
3
+ module Parser
4
+ # Parser for dealing with RSS feeds.
5
+ class RSS
6
+ include SAXMachine
7
+ include FeedUtilities
8
+ element :rss, as: :version, value: :version
9
+ element :title
10
+ element :description
11
+ element :link, :as => :url
12
+ elements :item, :as => :entries, :class => RSSEntry
13
+
14
+ attr_accessor :feed_url
15
+ attr_accessor :hubs
16
+
17
+ def self.able_to_parse?(xml) #:nodoc:
18
+ (/\<rss|\<rdf/ =~ xml) && !(/feedburner/ =~ xml)
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,37 @@
1
+ module Feedjira
2
+
3
+ module Parser
4
+ # Parser for dealing with RDF feed entries.
5
+ class RSSEntry
6
+ include SAXMachine
7
+ include FeedEntryUtilities
8
+
9
+ element :title
10
+ element :link, :as => :url
11
+
12
+ element :"dc:creator", :as => :author
13
+ element :author, :as => :author
14
+ element :"content:encoded", :as => :content
15
+ element :description, :as => :summary
16
+
17
+ element :"media:content", :as => :image, :value => :url
18
+ element :enclosure, :as => :image, :value => :url
19
+
20
+ element :pubDate, :as => :published
21
+ element :pubdate, :as => :published
22
+ element :"dc:date", :as => :published
23
+ element :"dc:Date", :as => :published
24
+ element :"dcterms:created", :as => :published
25
+
26
+
27
+ element :"dcterms:modified", :as => :updated
28
+ element :issued, :as => :published
29
+ elements :category, :as => :categories
30
+
31
+ element :guid, :as => :entry_id
32
+ element :"dc:identifier", :as => :entry_id
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,23 @@
1
+ module Feedjira
2
+
3
+ module Parser
4
+ # Parser for dealing with RSS feeds.
5
+ class RSSFeedBurner
6
+ include SAXMachine
7
+ include FeedUtilities
8
+ element :title
9
+ element :description
10
+ element :link, :as => :url
11
+ elements :"atom10:link", :as => :hubs, :value => :href, :with => {:rel => "hub"}
12
+ elements :item, :as => :entries, :class => RSSFeedBurnerEntry
13
+
14
+ attr_accessor :feed_url
15
+
16
+ def self.able_to_parse?(xml) #:nodoc:
17
+ (/\<rss|\<rdf/ =~ xml) && (/feedburner/ =~ xml)
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,43 @@
1
+ module Feedjira
2
+
3
+ module Parser
4
+ # Parser for dealing with RDF feed entries.
5
+ class RSSFeedBurnerEntry
6
+ include SAXMachine
7
+ include FeedEntryUtilities
8
+
9
+ element :title
10
+
11
+ element :"feedburner:origLink", :as => :url
12
+ element :link, :as => :url
13
+
14
+ element :"dc:creator", :as => :author
15
+ element :author, :as => :author
16
+ element :"content:encoded", :as => :content
17
+ element :description, :as => :summary
18
+
19
+ element :"media:content", :as => :image, :value => :url
20
+ element :enclosure, :as => :image, :value => :url
21
+
22
+ element :pubDate, :as => :published
23
+ element :pubdate, :as => :published
24
+ element :"dc:date", :as => :published
25
+ element :"dc:Date", :as => :published
26
+ element :"dcterms:created", :as => :published
27
+
28
+
29
+ element :"dcterms:modified", :as => :updated
30
+ element :issued, :as => :published
31
+ elements :category, :as => :categories
32
+
33
+ element :guid, :as => :entry_id
34
+
35
+ def url
36
+ @url || @link
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,3 @@
1
+ module Feedjira
2
+ VERSION = '0.9.0'
3
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe Feedjira::FeedUtilities do
4
+ before(:each) do
5
+ @klass = Class.new do
6
+ include Feedjira::FeedEntryUtilities
7
+ end
8
+ end
9
+
10
+ describe "handling dates" do
11
+ it "should parse an ISO 8601 formatted datetime into Time" do
12
+ time = @klass.new.parse_datetime("2008-02-20T8:05:00-010:00")
13
+ time.class.should == Time
14
+ time.should == Time.parse_safely("Wed Feb 20 18:05:00 UTC 2008")
15
+ end
16
+
17
+ it "should parse a ISO 8601 with milliseconds into Time" do
18
+ time = @klass.new.parse_datetime("2013-09-17T08:20:13.931-04:00")
19
+ time.class.should == Time
20
+ time.should == Time.parse_safely("Tue Sep 17 12:20:13 UTC 2013")
21
+ end
22
+ end
23
+
24
+ describe "sanitizing" do
25
+ before(:each) do
26
+ @feed = Feedjira::Feed.parse(sample_atom_feed)
27
+ @entry = @feed.entries.first
28
+ end
29
+
30
+ it "doesn't fail when no elements are defined on includer" do
31
+ expect { @klass.new.sanitize! }.to_not raise_error
32
+ end
33
+
34
+ it "should provide a sanitized title" do
35
+ new_title = "<script>this is not safe</script>" + @entry.title
36
+ @entry.title = new_title
37
+ @entry.title.sanitize.should == Loofah.scrub_fragment(new_title, :prune).to_s
38
+ end
39
+
40
+ it "should sanitize content in place" do
41
+ new_content = "<script>" + @entry.content
42
+ @entry.content = new_content.dup
43
+ @entry.content.sanitize!.should == Loofah.scrub_fragment(new_content, :prune).to_s
44
+ @entry.content.should == Loofah.scrub_fragment(new_content, :prune).to_s
45
+ end
46
+
47
+ it "should sanitize things in place" do
48
+ @entry.title += "<script>"
49
+ @entry.author += "<script>"
50
+ @entry.content += "<script>"
51
+
52
+ cleaned_title = Loofah.scrub_fragment(@entry.title, :prune).to_s
53
+ cleaned_author = Loofah.scrub_fragment(@entry.author, :prune).to_s
54
+ cleaned_content = Loofah.scrub_fragment(@entry.content, :prune).to_s
55
+
56
+ @entry.sanitize!
57
+ @entry.title.should == cleaned_title
58
+ @entry.author.should == cleaned_author
59
+ @entry.content.should == cleaned_content
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,762 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ class Hell < StandardError; end
4
+
5
+ class FailParser
6
+ def self.parse(_, &on_failure)
7
+ on_failure.call 'this parser always fails.'
8
+ end
9
+ end
10
+
11
+ describe Feedjira::Feed do
12
+
13
+ describe "#add_common_feed_element" do
14
+ before(:all) do
15
+ Feedjira::Feed.add_common_feed_element("generator")
16
+ end
17
+
18
+ it "should parse the added element out of Atom feeds" do
19
+ Feedjira::Feed.parse(sample_wfw_feed).generator.should == "TypePad"
20
+ end
21
+
22
+ it "should parse the added element out of Atom Feedburner feeds" do
23
+ Feedjira::Parser::Atom.new.should respond_to(:generator)
24
+ end
25
+
26
+ it "should parse the added element out of RSS feeds" do
27
+ Feedjira::Parser::RSS.new.should respond_to(:generator)
28
+ end
29
+ end
30
+
31
+ describe "#add_common_feed_entry_element" do
32
+ before(:all) do
33
+ Feedjira::Feed.add_common_feed_entry_element("wfw:commentRss", :as => :comment_rss)
34
+ end
35
+
36
+ it "should parse the added element out of Atom feeds entries" do
37
+ Feedjira::Feed.parse(sample_wfw_feed).entries.first.comment_rss.should == "this is the new val"
38
+ end
39
+
40
+ it "should parse the added element out of Atom Feedburner feeds entries" do
41
+ Feedjira::Parser::AtomEntry.new.should respond_to(:comment_rss)
42
+ end
43
+
44
+ it "should parse the added element out of RSS feeds entries" do
45
+ Feedjira::Parser::RSSEntry.new.should respond_to(:comment_rss)
46
+ end
47
+ end
48
+
49
+ describe '#parse_with' do
50
+ let(:xml) { '<xml></xml>' }
51
+
52
+ it 'invokes the parser and passes the xml' do
53
+ parser = double 'Parser', parse: nil
54
+ parser.should_receive(:parse).with xml
55
+ Feedjira::Feed.parse_with parser, xml
56
+ end
57
+
58
+ context 'with a callback block' do
59
+ it 'passes the callback to the parser' do
60
+ callback = ->(*) { raise Hell }
61
+
62
+ expect do
63
+ Feedjira::Feed.parse_with FailParser, xml, &callback
64
+ end.to raise_error Hell
65
+ end
66
+ end
67
+ end
68
+
69
+ describe "#parse" do # many of these tests are redundant with the specific feed type tests, but I put them here for completeness
70
+ context "when there's an available parser" do
71
+ it "should parse an rdf feed" do
72
+ feed = Feedjira::Feed.parse(sample_rdf_feed)
73
+ feed.title.should == "HREF Considered Harmful"
74
+ feed.entries.first.published.should == Time.parse_safely("Tue Sep 02 19:50:07 UTC 2008")
75
+ feed.entries.size.should == 10
76
+ end
77
+
78
+ it "should parse an rss feed" do
79
+ feed = Feedjira::Feed.parse(sample_rss_feed)
80
+ feed.title.should == "Tender Lovemaking"
81
+ feed.entries.first.published.should == Time.parse_safely("Thu Dec 04 17:17:49 UTC 2008")
82
+ feed.entries.size.should == 10
83
+ end
84
+
85
+ it "should parse an atom feed" do
86
+ feed = Feedjira::Feed.parse(sample_atom_feed)
87
+ feed.title.should == "Amazon Web Services Blog"
88
+ feed.entries.first.published.should == Time.parse_safely("Fri Jan 16 18:21:00 UTC 2009")
89
+ feed.entries.size.should == 10
90
+ end
91
+
92
+ it "should parse an feedburner atom feed" do
93
+ feed = Feedjira::Feed.parse(sample_feedburner_atom_feed)
94
+ feed.title.should == "Paul Dix Explains Nothing"
95
+ feed.entries.first.published.should == Time.parse_safely("Thu Jan 22 15:50:22 UTC 2009")
96
+ feed.entries.size.should == 5
97
+ end
98
+
99
+ it "should parse an itunes feed" do
100
+ feed = Feedjira::Feed.parse(sample_itunes_feed)
101
+ feed.title.should == "All About Everything"
102
+ feed.entries.first.published.should == Time.parse_safely("Wed, 15 Jun 2005 19:00:00 GMT")
103
+ feed.entries.size.should == 3
104
+ end
105
+ end
106
+
107
+ context "when there's no available parser" do
108
+ it "raises Feedjira::NoParserAvailable" do
109
+ proc {
110
+ Feedjira::Feed.parse("I'm an invalid feed")
111
+ }.should raise_error(Feedjira::NoParserAvailable)
112
+ end
113
+ end
114
+
115
+ it "should parse an feedburner rss feed" do
116
+ feed = Feedjira::Feed.parse(sample_rss_feed_burner_feed)
117
+ feed.title.should == "TechCrunch"
118
+ feed.entries.first.published.should == Time.parse_safely("Wed Nov 02 17:25:27 UTC 2011")
119
+ feed.entries.size.should == 20
120
+ end
121
+ end
122
+
123
+ describe "#determine_feed_parser_for_xml" do
124
+ it 'should return the Feedjira::Parser::GoogleDocsAtom calss for a Google Docs atom feed' do
125
+ Feedjira::Feed.determine_feed_parser_for_xml(sample_google_docs_list_feed).should == Feedjira::Parser::GoogleDocsAtom
126
+ end
127
+
128
+ it "should return the Feedjira::Parser::Atom class for an atom feed" do
129
+ Feedjira::Feed.determine_feed_parser_for_xml(sample_atom_feed).should == Feedjira::Parser::Atom
130
+ end
131
+
132
+ it "should return the Feedjira::Parser::AtomFeedBurner class for an atom feedburner feed" do
133
+ Feedjira::Feed.determine_feed_parser_for_xml(sample_feedburner_atom_feed).should == Feedjira::Parser::AtomFeedBurner
134
+ end
135
+
136
+ it "should return the Feedjira::Parser::RSS class for an rdf/rss 1.0 feed" do
137
+ Feedjira::Feed.determine_feed_parser_for_xml(sample_rdf_feed).should == Feedjira::Parser::RSS
138
+ end
139
+
140
+ it "should return the Feedjira::Parser::RSSFeedBurner class for an rss feedburner feed" do
141
+ Feedjira::Feed.determine_feed_parser_for_xml(sample_rss_feed_burner_feed).should == Feedjira::Parser::RSSFeedBurner
142
+ end
143
+
144
+ it "should return the Feedjira::Parser::RSS object for an rss 2.0 feed" do
145
+ Feedjira::Feed.determine_feed_parser_for_xml(sample_rss_feed).should == Feedjira::Parser::RSS
146
+ end
147
+
148
+ it "should return a Feedjira::Parser::RSS object for an itunes feed" do
149
+ Feedjira::Feed.determine_feed_parser_for_xml(sample_itunes_feed).should == Feedjira::Parser::ITunesRSS
150
+ end
151
+
152
+ end
153
+
154
+ describe "#setup_easy" do
155
+ class MockCurl
156
+ attr_accessor :follow_location, :userpwd, :proxy_url, :proxy_port, :max_redirects, :timeout, :ssl_verify_host, :ssl_verify_peer, :ssl_version, :enable_cookies, :cookiefile, :cookies
157
+
158
+ def headers
159
+ @headers ||= {}
160
+ end
161
+ end
162
+
163
+ let(:curl) { MockCurl.new }
164
+
165
+ it "sets defaults on curl" do
166
+ Feedjira::Feed.setup_easy curl
167
+
168
+ curl.headers["User-Agent"].should eq Feedjira::Feed::USER_AGENT
169
+ curl.follow_location.should eq true
170
+ end
171
+
172
+ it "allows user agent over-ride" do
173
+ Feedjira::Feed.setup_easy(curl, user_agent: '007')
174
+ curl.headers["User-Agent"].should eq '007'
175
+ end
176
+
177
+ it "enables compression" do
178
+ Feedjira::Feed.setup_easy(curl, compress: true)
179
+ curl.headers["Accept-encoding"].should eq 'gzip, deflate'
180
+ end
181
+
182
+ it "enables compression even when you act like you don't want it" do
183
+ Feedjira::Feed.setup_easy(curl, compress: false)
184
+ curl.headers["Accept-encoding"].should eq 'gzip, deflate'
185
+ end
186
+
187
+ it "sets up http auth" do
188
+ Feedjira::Feed.setup_easy(curl, http_authentication: ['user', 'pass'])
189
+ curl.userpwd.should eq 'user:pass'
190
+ end
191
+
192
+ it "passes known options to curl" do
193
+ known_options = {
194
+ enable_cookies: true,
195
+ cookiefile: 'cookies.txt',
196
+ cookies: 'asdf',
197
+ proxy_url: 'http://proxy.url.com',
198
+ proxy_port: '1234',
199
+ max_redirects: 2,
200
+ timeout: 500,
201
+ ssl_verify_host: true,
202
+ ssl_verify_peer: true,
203
+ ssl_version: :omg
204
+ }
205
+
206
+ Feedjira::Feed.setup_easy curl, known_options
207
+
208
+ known_options.each do |option|
209
+ key, value = option
210
+ curl.send(key).should eq value
211
+ end
212
+ end
213
+
214
+ it "ignores unknown options" do
215
+ expect { Feedjira::Feed.setup_easy curl, foo: :bar }.to_not raise_error
216
+ end
217
+ end
218
+
219
+ describe "when adding feed types" do
220
+ it "should prioritize added types over the built in ones" do
221
+ feed_text = "Atom asdf"
222
+ Feedjira::Parser::Atom.stub(:able_to_parse?).and_return(true)
223
+ new_feed_type = Class.new do
224
+ def self.able_to_parse?(val)
225
+ true
226
+ end
227
+ end
228
+
229
+ new_feed_type.should be_able_to_parse(feed_text)
230
+ Feedjira::Feed.add_feed_class(new_feed_type)
231
+ Feedjira::Feed.determine_feed_parser_for_xml(feed_text).should == new_feed_type
232
+
233
+ # this is a hack so that this doesn't break the rest of the tests
234
+ Feedjira::Feed.feed_classes.reject! {|o| o == new_feed_type }
235
+ end
236
+ end
237
+
238
+ describe '#etag_from_header' do
239
+ before(:each) do
240
+ @header = "HTTP/1.0 200 OK\r\nDate: Thu, 29 Jan 2009 03:55:24 GMT\r\nServer: Apache\r\nX-FB-Host: chi-write6\r\nLast-Modified: Wed, 28 Jan 2009 04:10:32 GMT\r\nETag: ziEyTl4q9GH04BR4jgkImd0GvSE\r\nP3P: CP=\"ALL DSP COR NID CUR OUR NOR\"\r\nConnection: close\r\nContent-Type: text/xml;charset=utf-8\r\n\r\n"
241
+ end
242
+
243
+ it "should return the etag from the header if it exists" do
244
+ Feedjira::Feed.etag_from_header(@header).should == "ziEyTl4q9GH04BR4jgkImd0GvSE"
245
+ end
246
+
247
+ it "should return nil if there is no etag in the header" do
248
+ Feedjira::Feed.etag_from_header("foo").should be_nil
249
+ end
250
+
251
+ end
252
+
253
+ describe '#last_modified_from_header' do
254
+ before(:each) do
255
+ @header = "HTTP/1.0 200 OK\r\nDate: Thu, 29 Jan 2009 03:55:24 GMT\r\nServer: Apache\r\nX-FB-Host: chi-write6\r\nLast-Modified: Wed, 28 Jan 2009 04:10:32 GMT\r\nETag: ziEyTl4q9GH04BR4jgkImd0GvSE\r\nP3P: CP=\"ALL DSP COR NID CUR OUR NOR\"\r\nConnection: close\r\nContent-Type: text/xml;charset=utf-8\r\n\r\n"
256
+ end
257
+
258
+ it "should return the last modified date from the header if it exists" do
259
+ Feedjira::Feed.last_modified_from_header(@header).should == Time.parse_safely("Wed, 28 Jan 2009 04:10:32 GMT")
260
+ end
261
+
262
+ it "should return nil if there is no last modified date in the header" do
263
+ Feedjira::Feed.last_modified_from_header("foo").should be_nil
264
+ end
265
+ end
266
+
267
+ describe "fetching feeds" do
268
+ before(:each) do
269
+ @paul_feed = { :xml => load_sample("PaulDixExplainsNothing.xml"), :url => "http://feeds.feedburner.com/PaulDixExplainsNothing" }
270
+ @trotter_feed = { :xml => load_sample("TrotterCashionHome.xml"), :url => "http://feeds2.feedburner.com/trottercashion" }
271
+ @invalid_feed = { :xml => 'This feed is invalid', :url => "http://feeds.feedburner.com/InvalidFeed" }
272
+ end
273
+
274
+ describe "#fetch_raw" do
275
+ before(:each) do
276
+ @cmock = double('cmock', :header_str => '', :body_str => @paul_feed[:xml] )
277
+ @multi = double('curl_multi', :add => true, :perform => true)
278
+ @curl_easy = double('curl_easy')
279
+ @curl = double('curl', :headers => {}, :follow_location= => true, :on_failure => true, :on_complete => true)
280
+ @curl.stub(:on_success).and_yield(@cmock)
281
+
282
+ Curl::Multi.stub(:new).and_return(@multi)
283
+ Curl::Easy.stub(:new).and_yield(@curl).and_return(@curl_easy)
284
+ end
285
+
286
+ it "should set user agent if it's passed as an option" do
287
+ Feedjira::Feed.fetch_raw(@paul_feed[:url], :user_agent => 'Custom Useragent')
288
+ @curl.headers['User-Agent'].should == 'Custom Useragent'
289
+ end
290
+
291
+ it "should set user agent to default if it's not passed as an option" do
292
+ Feedjira::Feed.fetch_raw(@paul_feed[:url])
293
+ @curl.headers['User-Agent'].should == Feedjira::Feed::USER_AGENT
294
+ end
295
+
296
+ it "should set if modified since as an option if passed" do
297
+ Feedjira::Feed.fetch_raw(@paul_feed[:url], :if_modified_since => Time.parse_safely("Wed, 28 Jan 2009 04:10:32 GMT"))
298
+ @curl.headers["If-Modified-Since"].should == 'Wed, 28 Jan 2009 04:10:32 GMT'
299
+ end
300
+
301
+ it "should set if none match as an option if passed" do
302
+ Feedjira::Feed.fetch_raw(@paul_feed[:url], :if_none_match => 'ziEyTl4q9GH04BR4jgkImd0GvSE')
303
+ @curl.headers["If-None-Match"].should == 'ziEyTl4q9GH04BR4jgkImd0GvSE'
304
+ end
305
+
306
+ it 'should set userpwd for http basic authentication if :http_authentication is passed' do
307
+ @curl.should_receive(:userpwd=).with('username:password')
308
+ Feedjira::Feed.fetch_raw(@paul_feed[:url], :http_authentication => ['username', 'password'])
309
+ end
310
+
311
+ it 'should set accepted encodings' do
312
+ Feedjira::Feed.fetch_raw(@paul_feed[:url], :compress => true)
313
+ @curl.headers["Accept-encoding"].should == 'gzip, deflate'
314
+ end
315
+
316
+ it "should return raw xml" do
317
+ Feedjira::Feed.fetch_raw(@paul_feed[:url]).should =~ /^#{Regexp.escape('<?xml version="1.0" encoding="UTF-8"?>')}/
318
+ end
319
+
320
+ it "should take multiple feed urls and return a hash of urls and response xml" do
321
+ multi = double('curl_multi', :add => true, :perform => true)
322
+ Curl::Multi.stub(:new).and_return(multi)
323
+
324
+ paul_response = double('paul_response', :header_str => '', :body_str => @paul_feed[:xml] )
325
+ trotter_response = double('trotter_response', :header_str => '', :body_str => @trotter_feed[:xml] )
326
+
327
+ paul_curl = double('paul_curl', :headers => {}, :follow_location= => true, :on_failure => true, :on_complete => true)
328
+ paul_curl.stub(:on_success).and_yield(paul_response)
329
+
330
+ trotter_curl = double('trotter_curl', :headers => {}, :follow_location= => true, :on_failure => true, :on_complete => true)
331
+ trotter_curl.stub(:on_success).and_yield(trotter_response)
332
+
333
+ Curl::Easy.should_receive(:new).with(@paul_feed[:url]).ordered.and_yield(paul_curl)
334
+ Curl::Easy.should_receive(:new).with(@trotter_feed[:url]).ordered.and_yield(trotter_curl)
335
+
336
+ results = Feedjira::Feed.fetch_raw([@paul_feed[:url], @trotter_feed[:url]])
337
+ results.keys.should include(@paul_feed[:url])
338
+ results.keys.should include(@trotter_feed[:url])
339
+ results[@paul_feed[:url]].should =~ /Paul Dix/
340
+ results[@trotter_feed[:url]].should =~ /Trotter Cashion/
341
+ end
342
+
343
+ it "should always return a hash when passed an array" do
344
+ results = Feedjira::Feed.fetch_raw([@paul_feed[:url]])
345
+ results.class.should == Hash
346
+ end
347
+ end
348
+
349
+ describe "#add_url_to_multi" do
350
+ before(:each) do
351
+ allow_message_expectations_on_nil
352
+ @multi = Curl::Multi.get([@paul_feed[:url]], {:follow_location => true}, {:pipeline => true})
353
+ @multi.stub(:add)
354
+ @easy_curl = Curl::Easy.new(@paul_feed[:url])
355
+
356
+ Curl::Easy.should_receive(:new).and_yield(@easy_curl)
357
+ end
358
+
359
+ it "should set user agent if it's passed as an option" do
360
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, :user_agent => 'My cool application')
361
+ @easy_curl.headers["User-Agent"].should == 'My cool application'
362
+ end
363
+
364
+ it "should set user agent to default if it's not passed as an option" do
365
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, {})
366
+ @easy_curl.headers["User-Agent"].should == Feedjira::Feed::USER_AGENT
367
+ end
368
+
369
+ it "should set if modified since as an option if passed" do
370
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, :if_modified_since => Time.parse_safely("Jan 25 2009 04:10:32 GMT"))
371
+ @easy_curl.headers["If-Modified-Since"].should == 'Sun, 25 Jan 2009 04:10:32 GMT'
372
+ end
373
+
374
+ it 'should set follow location to true' do
375
+ @easy_curl.should_receive(:follow_location=).with(true)
376
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, {})
377
+ end
378
+
379
+ it 'should set userpwd for http basic authentication if :http_authentication is passed' do
380
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, :http_authentication => ['myusername', 'mypassword'])
381
+ @easy_curl.userpwd.should == 'myusername:mypassword'
382
+ end
383
+
384
+ it 'should set accepted encodings' do
385
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, {:compress => true})
386
+ @easy_curl.headers["Accept-encoding"].should == 'gzip, deflate'
387
+ end
388
+
389
+ it "should set if_none_match as an option if passed" do
390
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, :if_none_match => 'ziEyTl4q9GH04BR4jgkImd0GvSE')
391
+ @easy_curl.headers["If-None-Match"].should == 'ziEyTl4q9GH04BR4jgkImd0GvSE'
392
+ end
393
+
394
+ describe 'on success' do
395
+ before(:each) do
396
+ @feed = double('feed', :feed_url= => true, :etag= => true, :last_modified= => true)
397
+ Feedjira::Feed.stub(:decode_content).and_return(@paul_feed[:xml])
398
+ Feedjira::Feed.stub(:determine_feed_parser_for_xml).and_return(Feedjira::Parser::AtomFeedBurner)
399
+ Feedjira::Parser::AtomFeedBurner.stub(:parse).and_return(@feed)
400
+ Feedjira::Feed.stub(:etag_from_header).and_return('ziEyTl4q9GH04BR4jgkImd0GvSE')
401
+ Feedjira::Feed.stub(:last_modified_from_header).and_return('Wed, 28 Jan 2009 04:10:32 GMT')
402
+ end
403
+
404
+ it 'should decode the response body' do
405
+ Feedjira::Feed.should_receive(:decode_content).with(@easy_curl).and_return(@paul_feed[:xml])
406
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, {})
407
+ @easy_curl.on_success.call(@easy_curl)
408
+ end
409
+
410
+ it 'should determine the xml parser class' do
411
+ Feedjira::Feed.should_receive(:determine_feed_parser_for_xml).with(@paul_feed[:xml]).and_return(Feedjira::Parser::AtomFeedBurner)
412
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, {})
413
+ @easy_curl.on_success.call(@easy_curl)
414
+ end
415
+
416
+ it 'should parse the xml' do
417
+ Feedjira::Parser::AtomFeedBurner.should_receive(:parse).
418
+ with(@paul_feed[:xml]).and_return(@feed)
419
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, {})
420
+ @easy_curl.on_success.call(@easy_curl)
421
+ end
422
+
423
+ describe 'when a compatible xml parser class is found' do
424
+ it 'should set the last effective url to the feed url' do
425
+ @easy_curl.should_receive(:last_effective_url).and_return(@paul_feed[:url])
426
+ @feed.should_receive(:feed_url=).with(@paul_feed[:url])
427
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, {})
428
+ @easy_curl.on_success.call(@easy_curl)
429
+ end
430
+
431
+ it 'should set the etags on the feed' do
432
+ @feed.should_receive(:etag=).with('ziEyTl4q9GH04BR4jgkImd0GvSE')
433
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, {})
434
+ @easy_curl.on_success.call(@easy_curl)
435
+ end
436
+
437
+ it 'should set the last modified on the feed' do
438
+ @feed.should_receive(:last_modified=).with('Wed, 28 Jan 2009 04:10:32 GMT')
439
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, {})
440
+ @easy_curl.on_success.call(@easy_curl)
441
+ end
442
+
443
+ it 'should add the feed to the responses' do
444
+ responses = {}
445
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], responses, {})
446
+ @easy_curl.on_success.call(@easy_curl)
447
+
448
+ responses.length.should == 1
449
+ responses['http://feeds.feedburner.com/PaulDixExplainsNothing'].should == @feed
450
+ end
451
+
452
+ it 'should call proc if :on_success option is passed' do
453
+ success = lambda { |url, feed| }
454
+ success.should_receive(:call).with(@paul_feed[:url], @feed)
455
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, { :on_success => success })
456
+ @easy_curl.on_success.call(@easy_curl)
457
+ end
458
+
459
+ describe 'when the parser raises an exception' do
460
+ it 'invokes the on_failure callback with that exception' do
461
+ failure = double 'Failure callback', arity: 2
462
+ failure.should_receive(:call).with(@easy_curl, an_instance_of(Hell))
463
+
464
+ Feedjira::Parser::AtomFeedBurner.should_receive(:parse).and_raise Hell
465
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, { on_failure: failure })
466
+
467
+ @easy_curl.on_success.call(@easy_curl)
468
+ end
469
+ end
470
+
471
+ describe 'when the parser invokes its on_failure callback' do
472
+ before(:each) do
473
+ Feedjira::Feed.stub(:determine_feed_parser_for_xml).and_return FailParser
474
+ end
475
+
476
+ it 'invokes the on_failure callback' do
477
+ failure = double 'Failure callback', arity: 2
478
+ failure.should_receive(:call).with(@easy_curl, an_instance_of(RuntimeError))
479
+
480
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, { on_failure: failure })
481
+ @easy_curl.on_success.call(@easy_curl)
482
+ end
483
+ end
484
+ end
485
+
486
+ describe 'when no compatible xml parser class is found' do
487
+ it 'invokes the on_failure callback' do
488
+ failure = double 'Failure callback', arity: 2
489
+ failure.should_receive(:call).with(@easy_curl, "Can't determine a parser")
490
+
491
+ Feedjira::Feed.should_receive(:determine_feed_parser_for_xml).and_return nil
492
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, { on_failure: failure })
493
+
494
+ @easy_curl.on_success.call(@easy_curl)
495
+ end
496
+ end
497
+ end
498
+
499
+ describe 'on failure' do
500
+ before(:each) do
501
+ @headers = "HTTP/1.0 500 Something Bad\r\nDate: Thu, 29 Jan 2009 03:55:24 GMT\r\nServer: Apache\r\nX-FB-Host: chi-write6\r\nLast-Modified: Wed, 28 Jan 2009 04:10:32 GMT\r\n"
502
+ @body = 'Sorry, something broke'
503
+
504
+ @easy_curl.stub(:response_code).and_return(500)
505
+ @easy_curl.stub(:header_str).and_return(@headers)
506
+ @easy_curl.stub(:body_str).and_return(@body)
507
+ end
508
+
509
+ it 'should call proc if :on_failure option is passed' do
510
+ failure = double 'Failure callback', arity: 2
511
+ failure.should_receive(:call).with(@easy_curl, nil)
512
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, { :on_failure => failure })
513
+ @easy_curl.on_failure.call(@easy_curl)
514
+ end
515
+
516
+ it 'should return the http code in the responses' do
517
+ responses = {}
518
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], responses, {})
519
+ @easy_curl.on_failure.call(@easy_curl)
520
+
521
+ responses.length.should == 1
522
+ responses[@paul_feed[:url]].should == 500
523
+ end
524
+ end
525
+
526
+ describe 'on complete for 404s' do
527
+ before(:each) do
528
+ @headers = "HTTP/1.0 404 Not Found\r\nDate: Thu, 29 Jan 2009 03:55:24 GMT\r\nServer: Apache\r\nX-FB-Host: chi-write6\r\nLast-Modified: Wed, 28 Jan 2009 04:10:32 GMT\r\n"
529
+ @body = 'Page could not be found.'
530
+
531
+ @easy_curl.stub(:response_code).and_return(404)
532
+ @easy_curl.stub(:header_str).and_return(@headers)
533
+ @easy_curl.stub(:body_str).and_return(@body)
534
+ end
535
+
536
+ it 'should call proc if :on_failure option is passed' do
537
+ complete = double 'Failure callback', arity: 2
538
+ complete.should_receive(:call).with(@easy_curl, 'Server returned a 404')
539
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], {}, { :on_failure => complete })
540
+ @easy_curl.on_missing.call(@easy_curl)
541
+ end
542
+
543
+ it 'should return the http code in the responses' do
544
+ responses = {}
545
+ Feedjira::Feed.add_url_to_multi(@multi, @paul_feed[:url], [], responses, {})
546
+ @easy_curl.on_complete.call(@easy_curl)
547
+
548
+ responses.length.should == 1
549
+ responses[@paul_feed[:url]].should == 404
550
+ end
551
+ end
552
+ end
553
+
554
+ describe "#add_feed_to_multi" do
555
+ before(:each) do
556
+ allow_message_expectations_on_nil
557
+ @multi = Curl::Multi.get([@paul_feed[:url]], {:follow_location => true}, {:pipeline => true})
558
+ @multi.stub(:add)
559
+ @easy_curl = Curl::Easy.new(@paul_feed[:url])
560
+ @feed = Feedjira::Feed.parse(sample_feedburner_atom_feed)
561
+
562
+ Curl::Easy.should_receive(:new).and_yield(@easy_curl)
563
+ end
564
+
565
+ it "should set user agent if it's passed as an option" do
566
+ Feedjira::Feed.add_feed_to_multi(@multi, @feed, [], {}, :user_agent => 'My cool application')
567
+ @easy_curl.headers["User-Agent"].should == 'My cool application'
568
+ end
569
+
570
+ it "should set user agent to default if it's not passed as an option" do
571
+ Feedjira::Feed.add_feed_to_multi(@multi, @feed, [], {}, {})
572
+ @easy_curl.headers["User-Agent"].should == Feedjira::Feed::USER_AGENT
573
+ end
574
+
575
+ it "should set if modified since as an option if passed" do
576
+ modified_time = Time.parse_safely("Wed, 28 Jan 2009 04:10:32 GMT")
577
+ Feedjira::Feed.add_feed_to_multi(@multi, @feed, [], {}, {:if_modified_since => modified_time})
578
+ modified_time.should be > @feed.last_modified
579
+
580
+ @easy_curl.headers["If-Modified-Since"].should == modified_time
581
+ end
582
+
583
+ it 'should set follow location to true' do
584
+ @easy_curl.should_receive(:follow_location=).with(true)
585
+ Feedjira::Feed.add_feed_to_multi(@multi, @feed, [], {}, {})
586
+ end
587
+
588
+ it 'should set userpwd for http basic authentication if :http_authentication is passed' do
589
+ Feedjira::Feed.add_feed_to_multi(@multi, @feed, [], {}, :http_authentication => ['myusername', 'mypassword'])
590
+ @easy_curl.userpwd.should == 'myusername:mypassword'
591
+ end
592
+
593
+ it "should set if_none_match as an option if passed" do
594
+ @feed.etag = 'ziEyTl4q9GH04BR4jgkImd0GvSE'
595
+ Feedjira::Feed.add_feed_to_multi(@multi, @feed, [], {}, {})
596
+ @easy_curl.headers["If-None-Match"].should == 'ziEyTl4q9GH04BR4jgkImd0GvSE'
597
+ end
598
+
599
+ describe 'on success' do
600
+ before(:each) do
601
+ @new_feed = @feed.clone
602
+ @feed.stub(:update_from_feed)
603
+ Feedjira::Feed.stub(:decode_content).and_return(@paul_feed[:xml])
604
+ Feedjira::Feed.stub(:determine_feed_parser_for_xml).and_return(Feedjira::Parser::AtomFeedBurner)
605
+ Feedjira::Parser::AtomFeedBurner.stub(:parse).and_return(@new_feed)
606
+ Feedjira::Feed.stub(:etag_from_header).and_return('ziEyTl4q9GH04BR4jgkImd0GvSE')
607
+ Feedjira::Feed.stub(:last_modified_from_header).and_return('Wed, 28 Jan 2009 04:10:32 GMT')
608
+ end
609
+
610
+ it 'should parse the updated feed' do
611
+ Feedjira::Parser::AtomFeedBurner.should_receive(:parse).and_return(@new_feed)
612
+ Feedjira::Feed.add_feed_to_multi(@multi, @feed, [], {}, {})
613
+ @easy_curl.on_success.call(@easy_curl)
614
+ end
615
+
616
+ it 'should set the last effective url to the feed url' do
617
+ @easy_curl.should_receive(:last_effective_url).and_return(@paul_feed[:url])
618
+ @new_feed.should_receive(:feed_url=).with(@paul_feed[:url])
619
+ Feedjira::Feed.add_feed_to_multi(@multi, @feed, [], {}, {})
620
+ @easy_curl.on_success.call(@easy_curl)
621
+ end
622
+
623
+ it 'should set the etags on the feed' do
624
+ @new_feed.should_receive(:etag=).with('ziEyTl4q9GH04BR4jgkImd0GvSE')
625
+ Feedjira::Feed.add_feed_to_multi(@multi, @feed, [], {}, {})
626
+ @easy_curl.on_success.call(@easy_curl)
627
+ end
628
+
629
+ it 'should set the last modified on the feed' do
630
+ @new_feed.should_receive(:last_modified=).with('Wed, 28 Jan 2009 04:10:32 GMT')
631
+ Feedjira::Feed.add_feed_to_multi(@multi, @feed, [], {}, {})
632
+ @easy_curl.on_success.call(@easy_curl)
633
+ end
634
+
635
+ it 'should add the feed to the responses' do
636
+ responses = {}
637
+ Feedjira::Feed.add_feed_to_multi(@multi, @feed, [], responses, {})
638
+ @easy_curl.on_success.call(@easy_curl)
639
+
640
+ responses.length.should == 1
641
+ responses['http://feeds.feedburner.com/PaulDixExplainsNothing'].should == @feed
642
+ end
643
+
644
+ it 'should call proc if :on_success option is passed' do
645
+ success = lambda { |feed| }
646
+ success.should_receive(:call).with(@feed)
647
+ Feedjira::Feed.add_feed_to_multi(@multi, @feed, [], {}, { :on_success => success })
648
+ @easy_curl.on_success.call(@easy_curl)
649
+ end
650
+
651
+ it 'should call update from feed on the old feed with the updated feed' do
652
+ @feed.should_receive(:update_from_feed).with(@new_feed)
653
+ Feedjira::Feed.add_feed_to_multi(@multi, @feed, [], {}, {})
654
+ @easy_curl.on_success.call(@easy_curl)
655
+ end
656
+
657
+ describe 'when the parser invokes its on_failure callback' do
658
+ before(:each) do
659
+ Feedjira::Feed.stub(:determine_feed_parser_for_xml).and_return FailParser
660
+ end
661
+
662
+ it 'invokes the on_failure callback' do
663
+ failure = double 'Failure callback', arity: 2
664
+ failure.should_receive(:call)
665
+
666
+ Feedjira::Feed.add_feed_to_multi(@multi, @feed, [], {}, { on_failure: failure })
667
+ @easy_curl.on_success.call(@easy_curl)
668
+ end
669
+ end
670
+ end
671
+
672
+ describe 'on failure' do
673
+ before(:each) do
674
+ @headers = "HTTP/1.0 404 Not Found\r\nDate: Thu, 29 Jan 2009 03:55:24 GMT\r\nServer: Apache\r\nX-FB-Host: chi-write6\r\nLast-Modified: Wed, 28 Jan 2009 04:10:32 GMT\r\n"
675
+ @body = 'Page could not be found.'
676
+
677
+ @easy_curl.stub(:response_code).and_return(404)
678
+ @easy_curl.stub(:header_str).and_return(@headers)
679
+ @easy_curl.stub(:body_str).and_return(@body)
680
+ end
681
+
682
+ it 'should call on success callback if the response code is 304' do
683
+ success = lambda { |feed| }
684
+ success.should_receive(:call).with(@feed)
685
+ @easy_curl.should_receive(:response_code).and_return(304)
686
+ Feedjira::Feed.add_feed_to_multi(@multi, @feed, [], {}, { :on_success => success })
687
+ @easy_curl.on_redirect.call(@easy_curl)
688
+ end
689
+
690
+ it 'should return the http code in the responses' do
691
+ responses = {}
692
+ Feedjira::Feed.add_feed_to_multi(@multi, @feed, [], responses, {})
693
+ @easy_curl.on_failure.call(@easy_curl)
694
+
695
+ responses.length.should == 1
696
+ responses[@paul_feed[:url]].should == 404
697
+ end
698
+ end
699
+ end
700
+
701
+ describe "#fetch_and_parse" do
702
+ it "passes options to multicurl" do
703
+ options = { user_agent: '007' }
704
+
705
+ Feedjira::Feed.should_receive(:add_url_to_multi).
706
+ with(anything, anything, anything, anything, options)
707
+
708
+ Feedjira::Feed.fetch_and_parse(sample_rss_feed, options)
709
+ end
710
+ end
711
+
712
+ describe "#decode_content" do
713
+ before(:each) do
714
+ @curl_easy = double('curl_easy', :body_str => '<xml></xml>')
715
+ end
716
+
717
+ it 'should decode the response body using gzip if the Content-Encoding: is gzip' do
718
+ @curl_easy.stub(:header_str).and_return('Content-Encoding: gzip')
719
+ string_io = double('stringio', :read => @curl_easy.body_str, :close => true)
720
+ StringIO.should_receive(:new).and_return(string_io)
721
+ Zlib::GzipReader.should_receive(:new).with(string_io).and_return(string_io)
722
+ Feedjira::Feed.decode_content(@curl_easy)
723
+ end
724
+
725
+ it 'should decode the response body using gzip if the Content-Encoding: is gzip even when the case is wrong' do
726
+ @curl_easy.stub(:header_str).and_return('content-encoding: gzip')
727
+ string_io = double('stringio', :read => @curl_easy.body_str, :close => true)
728
+ StringIO.should_receive(:new).and_return(string_io)
729
+ Zlib::GzipReader.should_receive(:new).with(string_io).and_return(string_io)
730
+ Feedjira::Feed.decode_content(@curl_easy)
731
+ end
732
+
733
+ it 'should deflate the response body using inflate if the Content-Encoding: is deflate' do
734
+ @curl_easy.stub(:header_str).and_return('Content-Encoding: deflate')
735
+ Zlib::Inflate.should_receive(:inflate).with(@curl_easy.body_str)
736
+ Feedjira::Feed.decode_content(@curl_easy)
737
+ end
738
+
739
+ it 'should deflate the response body using inflate if the Content-Encoding: is deflate event if the case is wrong' do
740
+ @curl_easy.stub(:header_str).and_return('content-encoding: deflate')
741
+ Zlib::Inflate.should_receive(:inflate).with(@curl_easy.body_str)
742
+ Feedjira::Feed.decode_content(@curl_easy)
743
+ end
744
+
745
+ it 'should return the response body if it is not encoded' do
746
+ @curl_easy.stub(:header_str).and_return('')
747
+ Feedjira::Feed.decode_content(@curl_easy).should == '<xml></xml>'
748
+ end
749
+ end
750
+
751
+ describe "#update" do
752
+ it "passes options to multicurl" do
753
+ options = { user_agent: '007' }
754
+
755
+ Feedjira::Feed.should_receive(:add_feed_to_multi).
756
+ with(anything, anything, anything, anything, options)
757
+
758
+ Feedjira::Feed.update(sample_rss_feed, options)
759
+ end
760
+ end
761
+ end
762
+ end