simple-rss 2.0.0 → 2.2.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.
@@ -0,0 +1,140 @@
1
+ require "test_helper"
2
+
3
+ class FeedMergingAndDiffingTest < Test::Unit::TestCase
4
+ def setup
5
+ @feed_one = SimpleRSS.parse <<~XML
6
+ <?xml version="1.0" encoding="UTF-8"?>
7
+ <rss version="2.0">
8
+ <channel>
9
+ <title>Feed One</title>
10
+ <item>
11
+ <guid>shared-guid</guid>
12
+ <title>Shared (older)</title>
13
+ <pubDate>Mon, 01 Jan 2024 10:00:00 UTC</pubDate>
14
+ </item>
15
+ <item>
16
+ <guid>one-guid</guid>
17
+ <title>Only One</title>
18
+ <pubDate>Mon, 01 Jan 2024 11:00:00 UTC</pubDate>
19
+ </item>
20
+ <item>
21
+ <title>Unidentified One</title>
22
+ </item>
23
+ </channel>
24
+ </rss>
25
+ XML
26
+
27
+ @feed_two = SimpleRSS.parse <<~XML
28
+ <?xml version="1.0" encoding="UTF-8"?>
29
+ <rss version="2.0">
30
+ <channel>
31
+ <title>Feed Two</title>
32
+ <item>
33
+ <guid>shared-guid</guid>
34
+ <title>Shared (newer)</title>
35
+ <pubDate>Mon, 01 Jan 2024 12:00:00 UTC</pubDate>
36
+ </item>
37
+ <item>
38
+ <link>https://example.com/two-only</link>
39
+ <title>Only Two</title>
40
+ <pubDate>Mon, 01 Jan 2024 13:00:00 UTC</pubDate>
41
+ </item>
42
+ <item>
43
+ <title>Unidentified Two</title>
44
+ </item>
45
+ </channel>
46
+ </rss>
47
+ XML
48
+ end
49
+
50
+ def test_merge_dedupes_and_sorts_newest_first
51
+ merged = @feed_one.merge(@feed_two)
52
+ titles = merged.map { |item| item[:title] }
53
+
54
+ assert_equal [
55
+ "Only Two",
56
+ "Shared (newer)",
57
+ "Only One",
58
+ "Unidentified One",
59
+ "Unidentified Two"
60
+ ], titles
61
+ end
62
+
63
+ def test_class_merge_combines_multiple_feeds
64
+ merged = SimpleRSS.merge(@feed_one, @feed_two)
65
+
66
+ assert_equal 5, merged.size
67
+ assert_equal "Only Two", merged.first[:title]
68
+ end
69
+
70
+ def test_diff_reports_added_and_removed_items
71
+ old_feed = SimpleRSS.parse <<~XML
72
+ <?xml version="1.0" encoding="UTF-8"?>
73
+ <rss version="2.0">
74
+ <channel>
75
+ <title>Old Feed</title>
76
+ <item>
77
+ <guid>stay</guid>
78
+ <title>Stay</title>
79
+ </item>
80
+ <item>
81
+ <guid>remove</guid>
82
+ <title>Remove</title>
83
+ </item>
84
+ </channel>
85
+ </rss>
86
+ XML
87
+
88
+ new_feed = SimpleRSS.parse <<~XML
89
+ <?xml version="1.0" encoding="UTF-8"?>
90
+ <rss version="2.0">
91
+ <channel>
92
+ <title>New Feed</title>
93
+ <item>
94
+ <guid>stay</guid>
95
+ <title>Stay</title>
96
+ </item>
97
+ <item>
98
+ <guid>add</guid>
99
+ <title>Add</title>
100
+ </item>
101
+ </channel>
102
+ </rss>
103
+ XML
104
+
105
+ diff = old_feed.diff(new_feed)
106
+
107
+ assert_equal(["Add"], diff[:added].map { |item| item[:title] })
108
+ assert_equal(["Remove"], diff[:removed].map { |item| item[:title] })
109
+ end
110
+
111
+ def test_dedupe_mutates_items_and_keeps_unidentified_entries
112
+ feed = SimpleRSS.parse <<~XML
113
+ <?xml version="1.0" encoding="UTF-8"?>
114
+ <rss version="2.0">
115
+ <channel>
116
+ <title>Dedupe Feed</title>
117
+ <item>
118
+ <guid>duplicate</guid>
119
+ <title>First duplicate</title>
120
+ </item>
121
+ <item>
122
+ <guid>duplicate</guid>
123
+ <title>Second duplicate</title>
124
+ </item>
125
+ <item>
126
+ <title>Unidentified One</title>
127
+ </item>
128
+ <item>
129
+ <title>Unidentified Two</title>
130
+ </item>
131
+ </channel>
132
+ </rss>
133
+ XML
134
+
135
+ result = feed.dedupe
136
+
137
+ assert_same feed, result
138
+ assert_equal(["First duplicate", "Unidentified One", "Unidentified Two"], feed.items.map { |item| item[:title] })
139
+ end
140
+ end
@@ -0,0 +1,25 @@
1
+ require "test_helper"
2
+
3
+ # Integration tests that require network access
4
+ # These are skipped by default, run with NETWORK_TESTS=1
5
+ class FetchIntegrationTest < Test::Unit::TestCase
6
+ def test_fetch_real_feed
7
+ omit unless ENV["NETWORK_TESTS"]
8
+ rss = SimpleRSS.fetch("https://feeds.bbci.co.uk/news/rss.xml", timeout: 10)
9
+ assert_kind_of SimpleRSS, rss
10
+ assert rss.title
11
+ assert rss.items.any?
12
+ end
13
+
14
+ def test_fetch_stores_caching_headers
15
+ omit unless ENV["NETWORK_TESTS"]
16
+ rss = SimpleRSS.fetch("https://feeds.bbci.co.uk/news/rss.xml", timeout: 10)
17
+ assert(rss.etag || rss.last_modified, "Expected ETag or Last-Modified header")
18
+ end
19
+
20
+ def test_fetch_follows_redirect
21
+ omit unless ENV["NETWORK_TESTS"]
22
+ rss = SimpleRSS.fetch("https://github.com/cardmagic/simple-rss/commits/master.atom", timeout: 10)
23
+ assert_kind_of SimpleRSS, rss
24
+ end
25
+ end
@@ -0,0 +1,90 @@
1
+ require "test_helper"
2
+ require "net/http"
3
+
4
+ class FetchTest < Test::Unit::TestCase
5
+ def setup
6
+ @sample_feed = File.read(File.dirname(__FILE__) + "/../data/rss20.xml")
7
+ end
8
+
9
+ # Test attr_readers exist and default to nil for parsed feeds
10
+
11
+ def test_etag_attr_reader_exists
12
+ rss = SimpleRSS.parse(@sample_feed)
13
+ assert_respond_to rss, :etag
14
+ end
15
+
16
+ def test_last_modified_attr_reader_exists
17
+ rss = SimpleRSS.parse(@sample_feed)
18
+ assert_respond_to rss, :last_modified
19
+ end
20
+
21
+ def test_etag_nil_for_parsed_feed
22
+ rss = SimpleRSS.parse(@sample_feed)
23
+ assert_nil rss.etag
24
+ end
25
+
26
+ def test_last_modified_nil_for_parsed_feed
27
+ rss = SimpleRSS.parse(@sample_feed)
28
+ assert_nil rss.last_modified
29
+ end
30
+
31
+ # Test fetch class method exists
32
+
33
+ def test_fetch_class_method_exists
34
+ assert_respond_to SimpleRSS, :fetch
35
+ end
36
+
37
+ # Test fetch with invalid URL raises error
38
+
39
+ def test_fetch_raises_on_invalid_host
40
+ # Socket::ResolutionError was added in Ruby 3.3, use SocketError for older versions
41
+ expected_errors = [SocketError, Errno::ECONNREFUSED, SimpleRSSError]
42
+ expected_errors << Socket::ResolutionError if defined?(Socket::ResolutionError)
43
+ assert_raise(*expected_errors) do
44
+ SimpleRSS.fetch("http://this-host-does-not-exist-12345.invalid/feed.xml", timeout: 1)
45
+ end
46
+ end
47
+
48
+ # Test fetch options are accepted
49
+
50
+ def test_fetch_accepts_etag_option
51
+ # Just verify it doesn't raise an ArgumentError
52
+ assert_nothing_raised do
53
+ SimpleRSS.fetch("http://localhost:1/feed.xml", etag: '"abc123"', timeout: 0.1)
54
+ rescue StandardError
55
+ # Expected - connection will fail
56
+ end
57
+ end
58
+
59
+ def test_fetch_accepts_last_modified_option
60
+ assert_nothing_raised do
61
+ SimpleRSS.fetch("http://localhost:1/feed.xml", last_modified: "Wed, 21 Oct 2015 07:28:00 GMT", timeout: 0.1)
62
+ rescue StandardError
63
+ # Expected - connection will fail
64
+ end
65
+ end
66
+
67
+ def test_fetch_accepts_headers_option
68
+ assert_nothing_raised do
69
+ SimpleRSS.fetch("http://localhost:1/feed.xml", headers: { "X-Custom" => "test" }, timeout: 0.1)
70
+ rescue StandardError
71
+ # Expected - connection will fail
72
+ end
73
+ end
74
+
75
+ def test_fetch_accepts_timeout_option
76
+ assert_nothing_raised do
77
+ SimpleRSS.fetch("http://localhost:1/feed.xml", timeout: 0.1)
78
+ rescue StandardError
79
+ # Expected - connection will fail
80
+ end
81
+ end
82
+
83
+ def test_fetch_accepts_follow_redirects_option
84
+ assert_nothing_raised do
85
+ SimpleRSS.fetch("http://localhost:1/feed.xml", follow_redirects: false, timeout: 0.1)
86
+ rescue StandardError
87
+ # Expected - connection will fail
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,187 @@
1
+ require "test_helper"
2
+
3
+ class FilteringAndValidationTest < Test::Unit::TestCase
4
+ def setup
5
+ @rss09 = SimpleRSS.parse open(File.dirname(__FILE__) + "/../data/rss09.rdf")
6
+ @rss20 = SimpleRSS.parse open(File.dirname(__FILE__) + "/../data/rss20.xml")
7
+ @atom = SimpleRSS.parse open(File.dirname(__FILE__) + "/../data/atom.xml")
8
+ end
9
+
10
+ def test_feed_type_for_known_formats
11
+ assert_equal :rss1, @rss09.feed_type
12
+ assert_equal :rss2, @rss20.feed_type
13
+ assert_equal :atom, @atom.feed_type
14
+ end
15
+
16
+ def test_feed_type_unknown_for_non_standard_feed
17
+ feed = SimpleRSS.parse <<~XML
18
+ <?xml version="1.0" encoding="UTF-8"?>
19
+ <feed>
20
+ <title>Unknown Feed</title>
21
+ <entry>
22
+ <title>Post</title>
23
+ </entry>
24
+ </feed>
25
+ XML
26
+
27
+ assert_equal :unknown, feed.feed_type
28
+ end
29
+
30
+ def test_class_valid_returns_true_for_well_formed_feed
31
+ xml = <<~XML
32
+ <?xml version="1.0" encoding="UTF-8"?>
33
+ <rss version="2.0">
34
+ <channel>
35
+ <title>Valid Feed</title>
36
+ <link>http://example.com</link>
37
+ <item>
38
+ <title>Post</title>
39
+ </item>
40
+ </channel>
41
+ </rss>
42
+ XML
43
+
44
+ assert_equal true, SimpleRSS.valid?(xml)
45
+ end
46
+
47
+ def test_class_valid_returns_false_for_invalid_feed
48
+ invalid_xml = open(File.dirname(__FILE__) + "/../data/not-rss.xml").read
49
+
50
+ assert_equal false, SimpleRSS.valid?(invalid_xml)
51
+ end
52
+
53
+ def test_class_valid_returns_false_when_source_read_fails
54
+ unreadable_source = Object.new
55
+ unreadable_source.define_singleton_method(:read) do
56
+ raise IOError, "stream closed"
57
+ end
58
+
59
+ assert_equal false, SimpleRSS.valid?(unreadable_source)
60
+ end
61
+
62
+ def test_instance_valid_requires_metadata_and_items
63
+ valid_feed = SimpleRSS.parse <<~XML
64
+ <?xml version="1.0" encoding="UTF-8"?>
65
+ <rss version="2.0">
66
+ <channel>
67
+ <title>Valid Feed</title>
68
+ <item>
69
+ <title>Post</title>
70
+ </item>
71
+ </channel>
72
+ </rss>
73
+ XML
74
+
75
+ invalid_feed = SimpleRSS.parse <<~XML
76
+ <?xml version="1.0" encoding="UTF-8"?>
77
+ <rss version="2.0">
78
+ <channel>
79
+ <description>No title and no link</description>
80
+ <item>
81
+ <description>Body only</description>
82
+ </item>
83
+ </channel>
84
+ </rss>
85
+ XML
86
+
87
+ empty_feed = SimpleRSS.parse <<~XML
88
+ <?xml version="1.0" encoding="UTF-8"?>
89
+ <rss version="2.0">
90
+ <channel>
91
+ <title>No Items</title>
92
+ </channel>
93
+ </rss>
94
+ XML
95
+
96
+ assert_equal true, valid_feed.valid?
97
+ assert_equal false, invalid_feed.valid?
98
+ assert_equal false, empty_feed.valid?
99
+ end
100
+
101
+ def test_items_since_filters_by_date
102
+ threshold = Time.parse("Wed Aug 24 13:30:00 UTC 2005")
103
+
104
+ filtered = @rss20.items_since(threshold)
105
+
106
+ assert_equal 1, filtered.size
107
+ assert_operator filtered.first[:pubDate], :>, threshold
108
+ end
109
+
110
+ def test_items_by_category_matches_strings_and_arrays
111
+ feed_with_string_category = SimpleRSS.parse <<~XML
112
+ <?xml version="1.0" encoding="UTF-8"?>
113
+ <rss version="2.0">
114
+ <channel>
115
+ <title>String Category Feed</title>
116
+ <item>
117
+ <title>Ruby News</title>
118
+ <category>Technology</category>
119
+ </item>
120
+ <item>
121
+ <title>Sports News</title>
122
+ <category>Sports</category>
123
+ </item>
124
+ </channel>
125
+ </rss>
126
+ XML
127
+
128
+ feed_with_array_category = SimpleRSS.parse(
129
+ <<~XML,
130
+ <?xml version="1.0" encoding="UTF-8"?>
131
+ <rss version="2.0">
132
+ <channel>
133
+ <title>Array Category Feed</title>
134
+ <item>
135
+ <title>Dev Update</title>
136
+ <category>Technology</category>
137
+ <category>Ruby</category>
138
+ </item>
139
+ </channel>
140
+ </rss>
141
+ XML
142
+ array_tags: [:category]
143
+ )
144
+
145
+ string_results = feed_with_string_category.items_by_category("tech")
146
+ array_results = feed_with_array_category.items_by_category("ruby")
147
+
148
+ assert_equal 1, string_results.size
149
+ assert_equal "Ruby News", string_results.first[:title]
150
+ assert_equal 1, array_results.size
151
+ assert_equal "Dev Update", array_results.first[:title]
152
+ end
153
+
154
+ def test_search_matches_title_description_summary_and_content
155
+ feed = SimpleRSS.parse <<~XML
156
+ <?xml version="1.0" encoding="UTF-8"?>
157
+ <rss version="2.0">
158
+ <channel>
159
+ <title>Search Feed</title>
160
+ <item>
161
+ <title>Ruby Patterns</title>
162
+ <description>Language design</description>
163
+ </item>
164
+ <item>
165
+ <title>Other Topic</title>
166
+ <description>Talks about BREAKING updates</description>
167
+ </item>
168
+ <item>
169
+ <title>Third Topic</title>
170
+ <summary>A quick ruby summary</summary>
171
+ </item>
172
+ <item>
173
+ <title>Fourth Topic</title>
174
+ <content>Deep dive into Ruby internals</content>
175
+ </item>
176
+ </channel>
177
+ </rss>
178
+ XML
179
+
180
+ ruby_results = feed.search("ruby")
181
+ breaking_results = feed.search("breaking")
182
+
183
+ assert_equal 3, ruby_results.size
184
+ assert_equal 1, breaking_results.size
185
+ assert_equal "Other Topic", breaking_results.first[:title]
186
+ end
187
+ end
@@ -0,0 +1,142 @@
1
+ require "test_helper"
2
+
3
+ class HashXmlSerializationTest < Test::Unit::TestCase
4
+ def setup
5
+ @rss20 = SimpleRSS.parse open(File.dirname(__FILE__) + "/../data/rss20.xml")
6
+ @atom = SimpleRSS.parse open(File.dirname(__FILE__) + "/../data/atom.xml")
7
+ end
8
+
9
+ # to_hash tests
10
+
11
+ def test_to_hash_returns_hash
12
+ result = @rss20.to_hash
13
+ assert_kind_of Hash, result
14
+ end
15
+
16
+ def test_to_hash_includes_feed_title
17
+ result = @rss20.to_hash
18
+ assert_equal "Technoblog", result[:title]
19
+ end
20
+
21
+ def test_to_hash_includes_items
22
+ result = @rss20.to_hash
23
+ assert_kind_of Array, result[:items]
24
+ assert_equal 10, result[:items].size
25
+ end
26
+
27
+ def test_to_hash_is_alias_for_as_json
28
+ assert_equal @rss20.as_json, @rss20.to_hash
29
+ end
30
+
31
+ # to_xml RSS 2.0 tests
32
+
33
+ def test_to_xml_returns_string
34
+ result = @rss20.to_xml
35
+ assert_kind_of String, result
36
+ end
37
+
38
+ def test_to_xml_default_format_is_rss2
39
+ result = @rss20.to_xml
40
+ assert_match(/<rss version="2.0">/, result)
41
+ end
42
+
43
+ def test_to_xml_rss2_has_xml_declaration
44
+ result = @rss20.to_xml(format: :rss2)
45
+ assert_match(/^<\?xml version="1.0" encoding="UTF-8"\?>/, result)
46
+ end
47
+
48
+ def test_to_xml_rss2_has_channel
49
+ result = @rss20.to_xml(format: :rss2)
50
+ assert_match(/<channel>/, result)
51
+ assert_match(%r{</channel>}, result)
52
+ end
53
+
54
+ def test_to_xml_rss2_has_title
55
+ result = @rss20.to_xml(format: :rss2)
56
+ assert_match(%r{<title>Technoblog</title>}, result)
57
+ end
58
+
59
+ def test_to_xml_rss2_has_link
60
+ result = @rss20.to_xml(format: :rss2)
61
+ assert_match(%r{<link>http://tech.rufy.com</link>}, result)
62
+ end
63
+
64
+ def test_to_xml_rss2_has_items
65
+ result = @rss20.to_xml(format: :rss2)
66
+ assert_match(/<item>/, result)
67
+ assert_match(%r{</item>}, result)
68
+ end
69
+
70
+ def test_to_xml_rss2_item_has_title
71
+ result = @rss20.to_xml(format: :rss2)
72
+ assert_match(/<item>\n<title>/, result)
73
+ end
74
+
75
+ def test_to_xml_rss2_item_has_guid
76
+ result = @rss20.to_xml(format: :rss2)
77
+ assert_match(%r{<guid>http://tech.rufy.com/entry/\d+</guid>}, result)
78
+ end
79
+
80
+ def test_to_xml_rss2_escapes_special_characters
81
+ rss = SimpleRSS.parse('<rss version="2.0"><channel><title>Test &amp; Title</title><item><title>Item &lt;1&gt;</title></item></channel></rss>')
82
+ result = rss.to_xml(format: :rss2)
83
+ assert_match(/&amp;/, result)
84
+ end
85
+
86
+ # to_xml Atom tests
87
+
88
+ def test_to_xml_atom_format
89
+ result = @atom.to_xml(format: :atom)
90
+ assert_match(%r{<feed xmlns="http://www.w3.org/2005/Atom">}, result)
91
+ end
92
+
93
+ def test_to_xml_atom_has_title
94
+ result = @atom.to_xml(format: :atom)
95
+ assert_match(%r{<title>dive into mark</title>}, result)
96
+ end
97
+
98
+ def test_to_xml_atom_has_link
99
+ result = @atom.to_xml(format: :atom)
100
+ assert_match(%r{<link href="http://example.org/" rel="alternate"/>}, result)
101
+ end
102
+
103
+ def test_to_xml_atom_has_entries
104
+ result = @atom.to_xml(format: :atom)
105
+ assert_match(/<entry>/, result)
106
+ assert_match(%r{</entry>}, result)
107
+ end
108
+
109
+ def test_to_xml_atom_entry_has_title
110
+ result = @atom.to_xml(format: :atom)
111
+ assert_match(%r{<entry>\n<title>Atom draft-07 snapshot</title>}, result)
112
+ end
113
+
114
+ # Error handling
115
+
116
+ def test_to_xml_raises_on_unknown_format
117
+ assert_raise(ArgumentError) { @rss20.to_xml(format: :unknown) }
118
+ end
119
+
120
+ def test_to_xml_error_message_includes_supported_formats
121
+ error = assert_raise(ArgumentError) { @rss20.to_xml(format: :foo) }
122
+ assert_match(/Supported: :rss2, :atom/, error.message)
123
+ end
124
+
125
+ # Round-trip tests
126
+
127
+ def test_to_xml_rss2_can_be_reparsed
128
+ xml = @rss20.to_xml(format: :rss2)
129
+ reparsed = SimpleRSS.parse(xml)
130
+
131
+ assert_equal "Technoblog", reparsed.title
132
+ assert_equal 10, reparsed.items.size
133
+ end
134
+
135
+ def test_to_xml_atom_can_be_reparsed
136
+ xml = @atom.to_xml(format: :atom)
137
+ reparsed = SimpleRSS.parse(xml)
138
+
139
+ assert_equal "dive into mark", reparsed.title
140
+ assert_equal 1, reparsed.items.size
141
+ end
142
+ end
@@ -0,0 +1,81 @@
1
+ require "test_helper"
2
+ require "json"
3
+
4
+ class JsonSerializationTest < Test::Unit::TestCase
5
+ def setup
6
+ @rss20 = SimpleRSS.parse open(File.dirname(__FILE__) + "/../data/rss20.xml")
7
+ @atom = SimpleRSS.parse open(File.dirname(__FILE__) + "/../data/atom.xml")
8
+ end
9
+
10
+ def test_as_json_returns_hash
11
+ result = @rss20.as_json
12
+ assert_kind_of Hash, result
13
+ end
14
+
15
+ def test_as_json_includes_feed_title
16
+ result = @rss20.as_json
17
+ assert_equal "Technoblog", result[:title]
18
+ end
19
+
20
+ def test_as_json_includes_feed_link
21
+ result = @rss20.as_json
22
+ assert_equal "http://tech.rufy.com", result[:link]
23
+ end
24
+
25
+ def test_as_json_includes_items
26
+ result = @rss20.as_json
27
+ assert_kind_of Array, result[:items]
28
+ assert_equal 10, result[:items].size
29
+ end
30
+
31
+ def test_as_json_items_have_title
32
+ result = @rss20.as_json
33
+ assert result[:items].first[:title].include?("some_string.starts_with?")
34
+ end
35
+
36
+ def test_as_json_converts_time_to_iso8601
37
+ result = @rss20.as_json
38
+ pub_date = result[:items].first[:pubDate]
39
+ assert_kind_of String, pub_date
40
+ assert_match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/, pub_date)
41
+ end
42
+
43
+ def test_as_json_works_with_atom
44
+ result = @atom.as_json
45
+ assert_equal "dive into mark", result[:title]
46
+ assert_equal 1, result[:items].size
47
+ end
48
+
49
+ def test_to_json_returns_string
50
+ result = @rss20.to_json
51
+ assert_kind_of String, result
52
+ end
53
+
54
+ def test_to_json_is_valid_json
55
+ result = @rss20.to_json
56
+ parsed = JSON.parse(result)
57
+ assert_kind_of Hash, parsed
58
+ end
59
+
60
+ def test_to_json_roundtrip
61
+ json_string = @rss20.to_json
62
+ parsed = JSON.parse(json_string, symbolize_names: true)
63
+
64
+ assert_equal "Technoblog", parsed[:title]
65
+ assert_equal 10, parsed[:items].size
66
+ assert parsed[:items].first[:title].include?("some_string.starts_with?")
67
+ end
68
+
69
+ def test_as_json_excludes_nil_feed_tags
70
+ result = @rss20.as_json
71
+ # Feed tags that weren't in the source shouldn't appear
72
+ refute result.key?(:subtitle)
73
+ refute result.key?(:id)
74
+ end
75
+
76
+ def test_as_json_accepts_options_parameter
77
+ # Should not raise, even if options aren't used yet
78
+ result = @rss20.as_json(only: [:title])
79
+ assert_kind_of Hash, result
80
+ end
81
+ end