radamanthus-superfeedr-ruby 0.4.3
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.
- data/LICENSE +20 -0
- data/README.rdoc +71 -0
- data/Rakefile +80 -0
- data/VERSION.yml +4 -0
- data/lib/config.yaml +2 -0
- data/lib/stanzas/iq_query_stanza.rb +36 -0
- data/lib/stanzas/notification_stanza.rb +278 -0
- data/lib/stanzas/subscribe_query_stanza.rb +26 -0
- data/lib/stanzas/subscriptions_query_stanza.rb +18 -0
- data/lib/stanzas/unsubscribe_query_stanza.rb +27 -0
- data/lib/superfeedr.rb +213 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/stanzas/iq_query_stanza_spec.rb +30 -0
- data/spec/stanzas/notifications_stanza_spec.rb +184 -0
- data/spec/stanzas/subscribe_stanza_spec.rb +15 -0
- data/spec/stanzas/subscriptions_stanza_spec.rb +15 -0
- data/spec/stanzas/unsubscribe_stanza_spec.rb +15 -0
- data/spec/superfeedr_ruby_spec.rb +237 -0
- metadata +115 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
class SubscribeQueryStanza < IqQueryStanza
|
2
|
+
|
3
|
+
def initialize(params)
|
4
|
+
raise NoFeedToSubscribe if params[:nodes].nil? or params[:nodes].empty?
|
5
|
+
raise TooManyFeeds if params[:nodes].size > 30
|
6
|
+
super(params.merge({:type => :set}))
|
7
|
+
@pubsub = Nokogiri::XML::Node.new("pubsub", @doc)
|
8
|
+
@pubsub["xmlns"] = "http://jabber.org/protocol/pubsub"
|
9
|
+
params[:nodes].each do |node|
|
10
|
+
add_node(node)
|
11
|
+
end
|
12
|
+
@iq.add_child(@pubsub)
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_node(node)
|
16
|
+
subscribe = Nokogiri::XML::Node.new("subscribe", @doc)
|
17
|
+
subscribe["node"] = node
|
18
|
+
subscribe["jid"] = from.split("/").first
|
19
|
+
@pubsub.add_child(subscribe)
|
20
|
+
end
|
21
|
+
|
22
|
+
def nodes
|
23
|
+
@pubsub.children.map {|c| c["node"]}
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class SubscriptionsQueryStanza < IqQueryStanza
|
2
|
+
|
3
|
+
def initialize(params)
|
4
|
+
super(params.merge({:type => :get}))
|
5
|
+
pubsub = Nokogiri::XML::Node.new("pubsub", @doc)
|
6
|
+
pubsub["xmlns"] = "http://jabber.org/protocol/pubsub"
|
7
|
+
@iq.add_child(pubsub)
|
8
|
+
subscriptions = Nokogiri::XML::Node.new("subscriptions", @doc)
|
9
|
+
subscriptions["page"] = params[:page].to_s
|
10
|
+
subscriptions["jid"] = from.split("/").first
|
11
|
+
pubsub.add_child(subscriptions)
|
12
|
+
end
|
13
|
+
|
14
|
+
def page
|
15
|
+
@iq.search("subscriptions").first["page"]
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class UnsubscribeQueryStanza < IqQueryStanza
|
2
|
+
|
3
|
+
def initialize(params)
|
4
|
+
raise NoFeedToSubscribe if params[:nodes].nil? or params[:nodes].empty?
|
5
|
+
raise TooManyFeeds if params[:nodes].size > 30
|
6
|
+
super(params.merge({:type => :set}))
|
7
|
+
|
8
|
+
@pubsub = Nokogiri::XML::Node.new("pubsub", @doc)
|
9
|
+
params[:nodes].each do |node|
|
10
|
+
add_node(node)
|
11
|
+
end
|
12
|
+
@pubsub["xmlns"] = "http://jabber.org/protocol/pubsub"
|
13
|
+
@iq.add_child(@pubsub)
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_node(node)
|
17
|
+
unsubscribe = Nokogiri::XML::Node.new("unsubscribe", @doc)
|
18
|
+
unsubscribe["node"] = node.to_s
|
19
|
+
unsubscribe["jid"] = from.split("/").first
|
20
|
+
@pubsub.add_child(unsubscribe)
|
21
|
+
end
|
22
|
+
|
23
|
+
def nodes
|
24
|
+
@pubsub.children.map {|c| c["node"]}
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/superfeedr.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
require "skates"
|
2
|
+
require "stanzas/iq_query_stanza.rb"
|
3
|
+
require "stanzas/notification_stanza.rb"
|
4
|
+
require "stanzas/subscribe_query_stanza.rb"
|
5
|
+
require "stanzas/unsubscribe_query_stanza.rb"
|
6
|
+
require "stanzas/subscriptions_query_stanza.rb"
|
7
|
+
|
8
|
+
##
|
9
|
+
# By default, the log level is at error. You can change that at anytime in your app
|
10
|
+
Skates.logger.level = Log4r::ERROR
|
11
|
+
|
12
|
+
##
|
13
|
+
# Based on the API documented there : http://superfeedr.com/documentation
|
14
|
+
module Superfeedr
|
15
|
+
|
16
|
+
class NotConnected < StandardError; end
|
17
|
+
|
18
|
+
@@connection = nil
|
19
|
+
@@callbacks = {}
|
20
|
+
@@connection_callback = nil
|
21
|
+
@@notification_callback = nil
|
22
|
+
|
23
|
+
##
|
24
|
+
# Connects your client to the Superfeedr.com XMPP server. You need to pass the following arguments :
|
25
|
+
# "jid" : login@superfeedr.com
|
26
|
+
# "password" : your superfeedr.com password
|
27
|
+
# ["host" : host for your jid or component : only useful if you use an external jid ]
|
28
|
+
# ["port" : port for your jid or component : only useful if you use an external jid ]
|
29
|
+
# ["app_type" : (client | component) only useful if you use an external jid ]
|
30
|
+
# The optional block will be called upon connection.
|
31
|
+
def self.connect(jid, password, host = nil, port = nil, app_type = "client", &block)
|
32
|
+
|
33
|
+
params = {
|
34
|
+
"jid" => jid,
|
35
|
+
"password" => password,
|
36
|
+
"host" => host,
|
37
|
+
"port" => port
|
38
|
+
}
|
39
|
+
@@connection_callback = block
|
40
|
+
|
41
|
+
run = Proc.new {
|
42
|
+
if app_type == "client"
|
43
|
+
Skates::ClientConnection.connect(params, self)
|
44
|
+
else
|
45
|
+
Skates::ComponentConnection.connect(params, self)
|
46
|
+
end
|
47
|
+
}
|
48
|
+
|
49
|
+
if EventMachine.reactor_running?
|
50
|
+
run.call
|
51
|
+
else
|
52
|
+
EventMachine.run {
|
53
|
+
run.call
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Subscribes to the multiple feeds, 30 by 30. Calls the block after each set of 30 feeds.
|
60
|
+
def self.subscribe(*feeds, &block)
|
61
|
+
return if feeds.flatten! == []
|
62
|
+
subset = feeds.slice!(0..29)
|
63
|
+
Superfeedr.add_feeds(subset) do |result|
|
64
|
+
subscribe(feeds, &block)
|
65
|
+
block.call(subset)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Ubsubscribe to multiple feeds, one by one. Calls the block after each set of 30 feeds.
|
71
|
+
def self.unsubscribe(*feeds, &block)
|
72
|
+
return if feeds.flatten! == []
|
73
|
+
subset = feeds.slice!(0..29)
|
74
|
+
Superfeedr.remove_feeds(subset) do |result|
|
75
|
+
unsubscribe(feeds, &block)
|
76
|
+
block.call(subset)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# List all subscriptions, by sending them by blocks (page), starting at page specified in argument
|
82
|
+
def self.subscriptions(start_page = 1, &block)
|
83
|
+
Superfeedr.subscriptions_by_page(start_page) do |page, result|
|
84
|
+
if !result.empty?
|
85
|
+
subscriptions(start_page + 1, &block)
|
86
|
+
end
|
87
|
+
block.call(page, result)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# Adds the url to the list of feeds you're monitoring. The block passed in argument will be called upon success.
|
93
|
+
# The block will take one boolen argument : true means everything went right... false means something failed!
|
94
|
+
# (Please set Skates's log to Log4r::INFO for more info)
|
95
|
+
def self.add_feeds(feeds_url, &block)
|
96
|
+
raise NotConnected unless connection
|
97
|
+
stanza = SubscribeQueryStanza.new({:nodes => feeds_url, :from => connection.jid})
|
98
|
+
@@callbacks[stanza.id] = Hash.new
|
99
|
+
@@callbacks[stanza.id][:method] = method(:on_subscribe)
|
100
|
+
@@callbacks[stanza.id][:param] = block
|
101
|
+
send(stanza)
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Unsubscribe from a feed. The block passed in argument will be called upon success.
|
106
|
+
# The block will take one boolen argument : true means everything went right... false means something failed!
|
107
|
+
# (Please set Skates's log to Log4r::INFO for more info)
|
108
|
+
def self.remove_feeds(feeds_url, &block)
|
109
|
+
raise NotConnected unless connection
|
110
|
+
stanza = UnsubscribeQueryStanza.new({:nodes => feeds_url, :from => connection.jid})
|
111
|
+
@@callbacks[stanza.id] = Hash.new
|
112
|
+
@@callbacks[stanza.id][:method] = method(:on_unsubscribe)
|
113
|
+
@@callbacks[stanza.id][:param] = block
|
114
|
+
send(stanza)
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Lists the subscriptions by page. The block passed in argument will be called with 2 arguments : the page,
|
119
|
+
# and an array of the feed's url in the page you requested.
|
120
|
+
# (Currently the Superfeedr API only supports 30 feeds per page.)
|
121
|
+
def self.subscriptions_by_page(page = 1, &block)
|
122
|
+
raise NotConnected unless connection
|
123
|
+
stanza = SubscriptionsQueryStanza.new({:page => page, :from => connection.jid})
|
124
|
+
@@callbacks[stanza.id] = Hash.new
|
125
|
+
@@callbacks[stanza.id][:method] = method(:on_subscriptions)
|
126
|
+
@@callbacks[stanza.id][:param] = block
|
127
|
+
send(stanza)
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Specifies the block that will be called upon notification.
|
132
|
+
# Your block should take a NotificationStanza instance argument.
|
133
|
+
def self.on_notification(&block)
|
134
|
+
@@notification_callback = block
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Called with a response to a subscriptions listing
|
139
|
+
def self.on_subscriptions(stanza, &block)
|
140
|
+
xmlns = {
|
141
|
+
'pubsub' => 'http://jabber.org/protocol/pubsub',
|
142
|
+
'superfeedr' => 'http://superfeedr.com/xmpp-pubsub-ext'
|
143
|
+
}
|
144
|
+
page = stanza.xpath('//pubsub:subscriptions/@superfeedr:page', xmlns).to_s.to_i
|
145
|
+
feeds = stanza.xpath('//pubsub:subscription', xmlns).map { |s| CGI.unescapeHTML(s["node"]) }
|
146
|
+
block.call(page, feeds)
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# Called with a response to a subscribe
|
151
|
+
def self.on_subscribe(stanza, &block)
|
152
|
+
block.call(stanza["type"] == "result")
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# Called with a response to an unsubscribe.
|
157
|
+
def self.on_unsubscribe(stanza, &block)
|
158
|
+
block.call(stanza["type"] == "result")
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# ::nodoc::
|
163
|
+
def self.callbacks
|
164
|
+
@@callbacks
|
165
|
+
end
|
166
|
+
|
167
|
+
##
|
168
|
+
# ::nodoc::
|
169
|
+
def self.connection
|
170
|
+
@@connection
|
171
|
+
end
|
172
|
+
|
173
|
+
##
|
174
|
+
# ::nodoc::
|
175
|
+
def self.send(xml)
|
176
|
+
connection.send_xml(xml)
|
177
|
+
end
|
178
|
+
|
179
|
+
##
|
180
|
+
# ::nodoc::
|
181
|
+
def self.on_connected(connection)
|
182
|
+
@@connection = connection
|
183
|
+
@@connection_callback.call
|
184
|
+
end
|
185
|
+
|
186
|
+
##
|
187
|
+
# ::nodoc::
|
188
|
+
def self.on_disconnected()
|
189
|
+
@@connection = false
|
190
|
+
end
|
191
|
+
|
192
|
+
##
|
193
|
+
# This shall not be called by your application. It is called upon stanza recetion. If it is a reply to a stanza we sent earlier, then, we just call it's associated callback. If it is a notification stanza, then, we call the notification callback (that you should have given when calling Superfeedr.connect) with a NotificationStanza instance.
|
194
|
+
def self.on_stanza(stanza)
|
195
|
+
if stanza["id"] && @@callbacks[stanza["id"]]
|
196
|
+
@@callbacks[stanza["id"]][:method].call(stanza, &@@callbacks[stanza["id"]][:param])
|
197
|
+
@@callbacks.delete(stanza["id"])
|
198
|
+
else
|
199
|
+
if stanza.name == "message" and !stanza.xpath("./ps:event", {"ps" => "http://jabber.org/protocol/pubsub#event"}).empty?
|
200
|
+
@@notification_callback.call(NotificationStanza.new(stanza)) if @@notification_callback
|
201
|
+
# Here we need to call the main notification callback!
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
##
|
207
|
+
# Config loaded from config.yaml
|
208
|
+
def self.conf
|
209
|
+
@@conf ||= YAML::load(File.read(File.dirname(__FILE__) + '/config.yaml'))
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
shared_examples_for "Iq Query Stanzas" do
|
4
|
+
|
5
|
+
it "should have the right type" do
|
6
|
+
IqQueryStanza.new(@params).type.should == @params[:type]
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should have the right to" do
|
10
|
+
IqQueryStanza.new(@params).to.should == "firehoser.superfeedr.com"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should have a random id" do
|
14
|
+
IqQueryStanza.new(@params).id.should match /[0..9]*/
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should have the right from" do
|
18
|
+
IqQueryStanza.new(@params).from.should == @params[:from]
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe IqQueryStanza do
|
24
|
+
before(:each) do
|
25
|
+
@params = { :type => "set", :from => "me@server.com/resource"}
|
26
|
+
end
|
27
|
+
|
28
|
+
it_should_behave_like "Iq Query Stanzas"
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
describe NotificationStanza do
|
3
|
+
|
4
|
+
before(:each) do
|
5
|
+
xml = <<-EOXML
|
6
|
+
<message xmlns="jabber:client" from="firehoser.superfeedr.com" to="julien@superfeedr.com">
|
7
|
+
<event xmlns="http://jabber.org/protocol/pubsub#event">
|
8
|
+
<status xmlns="http://superfeedr.com/xmpp-pubsub-ext" feed="http://pubsubhubbub-example-app.appspot.com/feed">
|
9
|
+
<http code="200">25002 bytes fetched in 0.73s for 1 new entries.</http>
|
10
|
+
<next_fetch>2010-03-25T17:06:30+00:00</next_fetch>
|
11
|
+
<title>PubSubHubBub example app</title>
|
12
|
+
</status>
|
13
|
+
<items node="http://pubsubhubbub-example-app.appspot.com/feed">
|
14
|
+
<item xmlns="http://jabber.org/protocol/pubsub" chunks="2" chunk="1">
|
15
|
+
<entry xmlns="http://www.w3.org/2005/Atom" xml:lang="" xmlns:xml="http://www.w3.org/XML/1998/namespace">
|
16
|
+
<id>tag:pubhubsubbub-example-app,2009:ahhwdWJzdWJodWJidWItZXhhbXBsZS1hcHByDQsSBUVudHJ5GMGOEAw</id>
|
17
|
+
<title>cool</title>
|
18
|
+
<content type="text">cool</content>
|
19
|
+
<summary>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</summary>
|
20
|
+
<published>2010-03-25T16:57:18Z</published>
|
21
|
+
<link type="text/html" href="http://pubsubhubbub-example-app.appspot.com/ahhwdWJzdWJodWJidWItZXhhbXBsZS1hcHByDQsSBUVudHJ5GMGOEAw" title="cool" rel="alternate"/>
|
22
|
+
<link href="http://domain.tld/entries/12345/comments.xml" rel="replies" type="application/atom+xml" />
|
23
|
+
<point xmlns="http://www.georss.org/georss">47.597553 -122.15925</point>
|
24
|
+
<author>
|
25
|
+
<name>John Doe</name>
|
26
|
+
<email>john@superfeedr.com</email>
|
27
|
+
</author>
|
28
|
+
<category term="tag" scheme="http://www.sixapart.com/ns/types#tag" />
|
29
|
+
<category term="category" scheme="http://www.sixapart.com/ns/types#tag" />
|
30
|
+
</entry>
|
31
|
+
</item>
|
32
|
+
<item xmlns="http://jabber.org/protocol/pubsub" chunks="2" chunk="2">
|
33
|
+
<entry xmlns="http://www.w3.org/2005/Atom" xml:lang="" xmlns:xml="http://www.w3.org/XML/1998/namespace">
|
34
|
+
<title>great</title>
|
35
|
+
<content type="text">great</content>
|
36
|
+
<id>tag:pubhubsubbub-example-app,2009:ahhwdWJzdWJodWJidWItZXhhbXBsZS1hcHByDQsSBUVudHJ5GMGOEAx</id>
|
37
|
+
<published>2010-03-25T16:57:19Z</published>
|
38
|
+
<link type="text/html" href="http://pubsubhubbub-example-app.appspot.com/ahhwdWJzdWJodWJidWItZXhhbXBsZS1hcHByDQsSBUVudHJ5GMGOEAx" title="" rel="alternate"/>
|
39
|
+
</entry>
|
40
|
+
</item>
|
41
|
+
</items>
|
42
|
+
</event>
|
43
|
+
</message>
|
44
|
+
EOXML
|
45
|
+
@stanza = NotificationStanza.new(Nokogiri::XML(xml).root)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should have the right feed_url" do
|
49
|
+
@stanza.feed_url.should == "http://pubsubhubbub-example-app.appspot.com/feed"
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should have the right message_status" do
|
53
|
+
@stanza.message_status.should == "25002 bytes fetched in 0.73s for 1 new entries."
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should have the right http_status" do
|
57
|
+
@stanza.http_status.should == 200
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should have the right next_fetch" do
|
61
|
+
@stanza.next_fetch.should == Time.parse("2010-03-25T17:06:30+00:00")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should have the title" do
|
65
|
+
@stanza.title.should == 'PubSubHubBub example app'
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should have the right number of items" do
|
69
|
+
@stanza.entries.count.should == 2
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "items" do
|
73
|
+
before(:each) do
|
74
|
+
@item = @stanza.entries.first
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should have the right chunk" do
|
78
|
+
@item.chunk.should == 1
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should have the right chunks" do
|
82
|
+
@item.chunks.should == 2
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should have the right title" do
|
86
|
+
@item.title.should == "cool"
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should have the right summary" do
|
90
|
+
@item.summary.should == "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should have the right unique_id" do
|
94
|
+
@item.unique_id.should == "tag:pubhubsubbub-example-app,2009:ahhwdWJzdWJodWJidWItZXhhbXBsZS1hcHByDQsSBUVudHJ5GMGOEAw"
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should have the right published" do
|
98
|
+
@item.published.should == Time.parse("2010-03-25T16:57:18Z")
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should have the right number of links" do
|
102
|
+
@item.should have(2).links
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "links" do
|
106
|
+
before(:each) do
|
107
|
+
@link = @item.links.first
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should have the right title" do
|
111
|
+
@link.title.should == "cool"
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should have the right href" do
|
115
|
+
@link.href.should == "http://pubsubhubbub-example-app.appspot.com/ahhwdWJzdWJodWJidWItZXhhbXBsZS1hcHByDQsSBUVudHJ5GMGOEAw"
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should have the right rel" do
|
119
|
+
@link.rel.should == "alternate"
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should have the right mime type" do
|
123
|
+
@link.type.should == "text/html"
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should have the right number of locations" do
|
129
|
+
@item.should have(1).locations
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "locations" do
|
133
|
+
before(:each) do
|
134
|
+
@location = @item.locations.first
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should have the right lat" do
|
138
|
+
@location.lat.should == 47.597553
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should have the right lon" do
|
142
|
+
@location.lon.should == -122.15925
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should have the right number of authors" do
|
148
|
+
@item.should have(1).authors
|
149
|
+
end
|
150
|
+
|
151
|
+
describe "authors" do
|
152
|
+
before(:each) do
|
153
|
+
@author = @item.authors.first
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should have the right name" do
|
157
|
+
@author.name.should == "John Doe"
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should have the right uri" do
|
161
|
+
@author.uri.should == nil
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should have the right email" do
|
165
|
+
@author.email.should == "john@superfeedr.com"
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should have the right number of categories" do
|
171
|
+
@item.should have(2).categories
|
172
|
+
end
|
173
|
+
|
174
|
+
describe "categories" do
|
175
|
+
before(:each) do
|
176
|
+
@category = @item.categories.first
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should have the right term" do
|
180
|
+
@category.term.should == "tag"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|