em-campfire 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,43 +1,25 @@
1
1
  module EventMachine
2
2
  class Campfire
3
3
  module Users
4
-
5
- # attr_accessor :user_cache
6
-
7
- private
8
-
9
- # Return the user_id if we haven't got the real name and
10
- # kick off a user data fetch
11
- def username_for(user_id)
12
- # if cached_user?(user_id)
13
- # user_cache[user_id]["name"]
14
- # else
15
- fetch_data_for(user_id)
16
- # user_id.to_s
17
- # end
18
- end
19
-
20
- # def is_me?(user_id)
21
- # if user_cache['me']
22
- # user_cache['me']['id'] == user_id
23
- # else
24
- # fetch_data_for('me')
25
- # false
26
- # end
27
- # end
28
- #
29
- # def cached_user? user_id
30
- # user_cache[user_id] != nil
31
- # end
32
-
33
- def fetch_data_for(user_id)
34
- return unless user_id
4
+ def fetch_user_data_for_user_id(user_id, &block)
35
5
  url = "https://#{subdomain}.campfirenow.com/users/#{user_id}.json"
36
- http = EventMachine::HttpRequest.new(url).get(:head => {'authorization' => [api_key, 'X'], "Content-Type" => "application/json"})
6
+
7
+ etag_header = {}
8
+ if cached_user_data = cache.get(user_cache_key(user_id))
9
+ etag_header = {"ETag" => cached_user_data["etag"]}
10
+ end
11
+
12
+ http = EventMachine::HttpRequest.new(url).get(:head => {'authorization' => [api_key, 'X'], "Content-Type" => "application/json"}.merge(etag_header))
13
+
37
14
  http.callback do
38
15
  if http.response_header.status == 200
39
- logger.debug "Got the data for #{user_id}"
40
- update_user_cache_with(user_id, Yajl::Parser.parse(http.response)['user'])
16
+ logger.debug "Got the user data for #{user_id}"
17
+ user_data = Yajl::Parser.parse(http.response)['user']
18
+ cache.set(user_cache_key(user_id), user_data.merge({'etag' => http.response_header.etag}))
19
+ yield user_data if block_given?
20
+ elsif http.response_header.status == 304
21
+ logger.debug "HTTP response was 304, serving user data for user 456 \(#{cached_user_data['name']}\) from cache"
22
+ yield cached_user_data if block_given?
41
23
  else
42
24
  logger.error "Couldn't fetch user data for user #{user_id} with url #{url}, http response from API was #{http.response_header.status}"
43
25
  end
@@ -46,11 +28,50 @@ module EventMachine
46
28
  logger.error "Couldn't connect to #{url} to fetch user data for user #{user_id}"
47
29
  end
48
30
  end
49
-
50
- # def update_user_cache_with(user_id, data)
51
- # logger.debug "Updated user cache for #{data['name']}"
52
- # user_cache[user_id] = data
53
- # end
31
+
32
+ def fetch_user_data_for_self
33
+ url = "https://#{subdomain}.campfirenow.com/users/me.json"
34
+
35
+ etag_header = {}
36
+ if cached_user_data = cache.get(user_cache_key('me'))
37
+ etag_header = {"ETag" => cached_user_data["etag"]}
38
+ end
39
+
40
+ http = EventMachine::HttpRequest.new(url).get(:head => {'authorization' => [api_key, 'X'], "Content-Type" => "application/json"}) #.merge(etag_header)
41
+ http.callback do
42
+ if http.response_header.status == 200
43
+ logger.debug "Got the user data for self"
44
+ user_data = Yajl::Parser.parse(http.response)['user']
45
+ cache.set(user_cache_key('me'), user_data.merge({'etag' => http.response_header.etag}))
46
+ yield user_data if block_given?
47
+ elsif http.response_header.status == 304
48
+ logger.debug "HTTP response was 304, serving user data for self from cache"
49
+ yield cached_user_data if block_given?
50
+ else
51
+ logger.error "Couldn't fetch user data for self with url #{url}, http response from API was #{http.response_header.status}"
52
+ end
53
+ end
54
+
55
+ http.errback do
56
+ logger.error "Couldn't connect to #{url} to fetch user data for self"
57
+ end
58
+ end
59
+
60
+ def is_me?(user_id)
61
+ if cache_data = cache.get(user_cache_key('me'))
62
+ return cache_data['id'] == user_id
63
+ else
64
+ logger.debug "No user data cache exists for me, fetching it now"
65
+ fetch_user_data_for_self
66
+ return false
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def user_cache_key(user_id)
73
+ "user-data-#{user_id}"
74
+ end
54
75
  end
55
76
  end
56
77
  end
@@ -1,5 +1,5 @@
1
1
  module EventMachine
2
2
  class Campfire
3
- VERSION = "0.0.1"
3
+ VERSION = "1.0.0"
4
4
  end
5
5
  end
data/lib/em-campfire.rb CHANGED
@@ -8,6 +8,7 @@ require "em-campfire/connection"
8
8
  require "em-campfire/rooms"
9
9
  require "em-campfire/users"
10
10
  require "em-campfire/messages"
11
+ require "em-campfire/cache"
11
12
 
12
13
  module EventMachine
13
14
  class Campfire
@@ -21,26 +22,28 @@ module EventMachine
21
22
  def initialize(options = {})
22
23
  raise ArgumentError, "You must pass an API key" unless options[:api_key]
23
24
  raise ArgumentError, "You must pass a subdomain" unless options[:subdomain]
24
-
25
+
25
26
  options.each do |k,v|
26
27
  s = "#{k}="
27
28
  if respond_to?(s)
28
29
  send(s, v)
29
30
  else
30
- logger.warn "em-campfire initialized with #{k.inspect} => #{v.inspect} but NO UNDERSTAND!"
31
+ raise ArgumentError, "#{k.inspect} is not a valid option"
31
32
  end
32
33
  end
33
-
34
- # @rooms = {}
35
- # @user_cache = {}
36
- @room_cache = {}
37
-
38
- populate_room_list
39
-
40
- # # populate bot data separately, in case we are ignoring ourselves
41
- # fetch_data_for('me')
34
+
35
+ fetch_user_data_for_self
36
+ end
37
+
38
+ def cache=(a_cache)
39
+ raise(ArgumentError, "You must pass a conforming cache object") unless a_cache.respond_to(:set) && a_cache.respond_to(:get)
40
+ @cache = a_cache
42
41
  end
43
-
42
+
43
+ def cache
44
+ @cache ||= EventMachine::Campfire::Cache.new
45
+ end
46
+
44
47
  def logger
45
48
  unless @logger
46
49
  @logger = Logger.new(STDOUT)
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+
3
+ describe EventMachine::Campfire::Cache do
4
+ before :each do
5
+ @cache = EventMachine::Campfire::Cache.new
6
+ end
7
+
8
+ it "should cache data" do
9
+ @cache.set("foo", "bar")
10
+ @cache.get("foo").should eql("bar")
11
+ end
12
+
13
+ it "should stringify keys" do
14
+ @cache.set(123, "bar")
15
+ @cache.get("123").should eql("bar")
16
+ @cache.get(123).should eql("bar")
17
+ end
18
+
19
+ it "should yield cache data" do
20
+ @cache.set(123, "bar")
21
+ @cache.get(123) do |value|
22
+ value
23
+ end.should eql("bar")
24
+ end
25
+ end
@@ -1,12 +1,55 @@
1
- require "spec_helper"
1
+ require "users_helper"
2
+
3
+ class ConnectionTester < ModuleHarness
4
+ include EventMachine::Campfire::Connection
5
+ include EventMachine::Campfire::Users
6
+
7
+ def cache; @cache ||= EventMachine::Campfire::Cache.new; end
8
+
9
+ def receive_message(message)
10
+ process_message(message)
11
+ end
12
+ end
2
13
 
3
14
  describe EventMachine::Campfire::Connection do
4
-
5
- context "#on_message" do
6
- before :each do
7
- EM.run_block { @adaptor = a EM::Campfire }
8
- end
9
-
10
- it "run a block when it receives a message"
15
+ before :each do
16
+ mock_logger(ConnectionTester)
17
+ @conn = ConnectionTester.new
18
+ end
19
+
20
+ it "should receive messages" do
21
+ @conn.receive_message "foo"
22
+ logger_output.should =~ /DEBUG.*Received message "foo"/
23
+ logger_output.should =~ /DEBUG.*on_message callback does not exist/
24
+ end
25
+
26
+ it "should process on_message an block if present" do
27
+ ping = mock
28
+ ping.expects(:ping).with("foo")
29
+ @conn.on_message {|message| ping.ping(message) }
30
+ @conn.receive_message "foo"
31
+ logger_output.should =~ /DEBUG.*on_message callback exists, calling it with "foo"/
32
+ end
33
+
34
+ it "should be able to ignore itself" do
35
+ @conn.ignore_self = true
36
+ @conn.cache.stubs(:get).with('user-data-me').returns({'id' => 789})
37
+
38
+ ping = mock
39
+ ping.expects(:ping).never
40
+
41
+ @conn.on_message { ping.ping }
42
+ EM.run_block { @conn.receive_message({:user_id => 789}) }
43
+ logger_output.should =~ /Ignoring message with user_id 789 as that is me and ignore_self is true/
44
+ end
45
+
46
+ it "should not ignore non-self messages" do
47
+ stub_self_data_request
48
+ @conn.ignore_self = true
49
+
50
+ ping = mock
51
+ ping.expects(:ping).once
52
+ @conn.on_message { ping.ping }
53
+ EM.run_block { @conn.receive_message({:user_id => 2}) }
11
54
  end
12
55
  end
@@ -3,7 +3,8 @@ require "spec_helper"
3
3
  describe EventMachine::Campfire do
4
4
 
5
5
  before :each do
6
- stub_rooms_data_request
6
+ # stub_room_list_data_request
7
+ @self_data_request_stub = stub_self_data_request
7
8
  end
8
9
 
9
10
  describe "#initialize" do
@@ -11,16 +12,19 @@ describe EventMachine::Campfire do
11
12
  EM.run_block { a(EM::Campfire).should be_a(EM::Campfire) }
12
13
  end
13
14
 
14
- it "should warn if given an option it doesn't know" do
15
- mock_logger
16
- EM.run_block { a(EM::Campfire, :fred => "estaire").should be_a(EM::Campfire) }
17
- logger_output.should =~ /WARN.*em-campfire initialized with :fred => "estaire" but NO UNDERSTAND!/
15
+ it "should raise when given an option it doesn't understand" do
16
+ lambda { EM::Campfire.new(valid_params.merge({:fred => "estaire"}))}.should raise_error(ArgumentError, ":fred is not a valid option")
18
17
  end
19
18
 
20
19
  it "should require essential parameters" do
21
20
  lambda { EM::Campfire.new }.should raise_error(ArgumentError, "You must pass an API key")
22
21
  lambda { EM::Campfire.new(:api_key => "foo") }.should raise_error(ArgumentError, "You must pass a subdomain")
23
22
  end
23
+
24
+ it "should fetch data about me" do
25
+ EM.run_block { a(EM::Campfire).should be_a(EM::Campfire) }
26
+ @self_data_request_stub.should have_been_requested
27
+ end
24
28
  end
25
29
 
26
30
  describe "#verbose" do
@@ -55,199 +59,29 @@ describe EventMachine::Campfire do
55
59
 
56
60
  it { @adaptor.logger.should be == @custom_logger }
57
61
  end
62
+ end
63
+
64
+ describe "#cache" do
65
+ before :each do
66
+ EM.run_block { @adaptor = a EM::Campfire }
67
+ @valid_cache_stub = stub(:get => "foo", :set => nil, :respond_to => true)
68
+ end
69
+
70
+ it "should accept a valid cache object" do
71
+ @adaptor.cache = @valid_cache_stub
72
+ @adaptor.cache.set
73
+ @adaptor.cache.get.should eql("foo")
74
+ end
75
+
76
+ it "should reject a cache object that doesn't have a conforming interface" do
77
+ lambda { @adaptor.cache = stub(:respond_to) }.should raise_error(ArgumentError, "You must pass a conforming cache object")
78
+ end
79
+
80
+ it "should provide a default cache" do
81
+ @adaptor.cache.set("foo", "bar")
82
+ @adaptor.cache.get("foo").should eql("bar")
83
+ end
58
84
 
59
-
60
-
61
- #
62
- # it "should handle HTTP errors fetching individual room data" do
63
- # mock_logger
64
- # bot = a Scamp
65
- #
66
- # EM.run_block {
67
- # stub_request(:post, @message_post_url).
68
- # with(:headers => {'Authorization'=>[valid_params[:api_key], 'X'], 'Content-Type' => 'application/json'}).
69
- # to_return(:status => 502, :body => "", :headers => {'Content-Type'=>'text/html'})
70
- # lambda {bot.send(:send_message, 123, "Hi", "Textmessage")}.should_not raise_error
71
- # }
72
- # logger_output.should =~ /ERROR.*Couldn't post message "Hi" to room 123 using url #{@message_post_url}, http response from the API was 502/
73
- # end
74
- #
75
- # it "should handle network errors fetching individual room data" do
76
- # mock_logger
77
- # bot = a Scamp
78
- #
79
- # EM.run_block {
80
- # stub_request(:post, @message_post_url).
81
- # with(:headers => {'Authorization'=>[valid_params[:api_key], 'X'], 'Content-Type' => 'application/json'}).to_timeout
82
- # lambda {bot.send(:send_message, 123, "Hi", "Textmessage")}.should_not raise_error
83
- # }
84
- # logger_output.should =~ /ERROR.*Couldn't connect to #{@message_post_url} to post message "Hi" to room 123/
85
- # end
86
- #
87
-
88
-
89
-
90
- # context "user operations" do
91
- # it "should fetch user data" do
92
- # adaptor = a EM::Campfire
93
- #
94
- # EM.run_block {
95
- # stub_request(:get, "https://#{valid_params[:subdomain]}.campfirenow.com/users/123.json").
96
- # with(:headers => {'Authorization'=>[valid_params[:api_key], 'X'], 'Content-Type'=>'application/json'}).
97
- # to_return(:status => 200, :body => Yajl::Encoder.encode(:user => valid_user_cache_data[123]), :headers => {})
98
- # adaptor.send(:username_for, 123)
99
- # stub.should have_been_requested
100
- # }
101
- # end
102
-
103
- # it "should handle HTTP errors fetching user data" do
104
- # mock_logger
105
- # bot = a EM::Campfire
106
- #
107
- # url = "https://#{valid_params[:subdomain]}.campfirenow.com/users/123.json"
108
- # EM.run_block {
109
- # stub_request(:get, url).
110
- # with(:headers => {'Authorization'=>[valid_params[:api_key], 'X'], 'Content-Type'=>'application/json'}).
111
- # to_return(:status => 502, :body => "", :headers => {'Content-Type'=>'text/html'})
112
- # lambda {bot.username_for(123)}.should_not raise_error
113
- # }
114
- # logger_output.should =~ /ERROR.*Couldn't fetch user data for user 123 with url #{url}, http response from API was 502/
115
- # end
116
- #
117
- # it "should handle network errors fetching user data" do
118
- # mock_logger
119
- # bot = a EM::Campfire
120
- #
121
- # url = "https://#{valid_params[:subdomain]}.campfirenow.com/users/123.json"
122
- # EM.run_block {
123
- # stub_request(:get, url).
124
- # with(:headers => {'Authorization'=>[valid_params[:api_key], 'X'], 'Content-Type'=>'application/json'}).to_timeout
125
- # lambda {bot.username_for(123)}.should_not raise_error
126
- # }
127
- # logger_output.should =~ /ERROR.*Couldn't connect to #{url} to fetch user data for user 123/
128
- # end
129
- end
130
-
131
- context "room operations" do
132
- before do
133
- @room_list_url = "https://#{valid_params[:subdomain]}.campfirenow.com/rooms.json"
134
- @me_list_url = "https://#{valid_params[:subdomain]}.campfirenow.com/users/me.json"
135
- @room_url = "https://#{valid_params[:subdomain]}.campfirenow.com/room/123.json"
136
- @stream_url = "https://streaming.campfirenow.com/room/123/live.json"
137
- end
138
-
139
- # it "should fetch a room list" do
140
- # mock_logger
141
- # bot = a EM::Campfire
142
- #
143
- # EM.run_block {
144
- # stub_request(:get, @room_list_url).
145
- # with(:headers => {'Authorization'=>[valid_params[:api_key], 'X']}).
146
- # to_return(:status => 200, :body => Yajl::Encoder.encode(:rooms => valid_room_cache_data.values), :headers => {})
147
- # bot.send(:populate_room_list)
148
- # stub.should have_been_requested
149
- # }
150
- # logger_output.should =~ /DEBUG.*Fetched room list/
151
- # end
152
-
153
- # it "should invoke the post connection callback" do
154
- # mock_logger
155
- # bot = a EM::Campfire
156
- #
157
- # invoked_cb = false
158
- #
159
- # EM.run_block {
160
- # stub_request(:get, @room_list_url).
161
- # with(:headers => {
162
- # 'Authorization'=>[valid_params[:api_key], 'X'],
163
- # 'Content-Type' => 'application/json'
164
- # }).
165
- # to_return(:status => 200, :body => Yajl::Encoder.encode(:rooms => valid_room_cache_data.values), :headers => {})
166
- #
167
- # stub_request(:get, @room_list_url).
168
- # with(:headers => {
169
- # 'Authorization'=>[valid_params[:api_key], 'X']
170
- # }).
171
- # to_return(:status => 200, :body => Yajl::Encoder.encode(:rooms => valid_room_cache_data.values), :headers => {})
172
- #
173
- # # Disable fetch_data_for, not important to this test.
174
- # EM::Campfire.any_instance.expects(:fetch_data_for).returns(nil)
175
- #
176
- # bot.send(:connect!, [valid_room_cache_data.keys.first]) do
177
- # invoked_cb = true
178
- # end
179
- # }
180
- # invoked_cb.should be_true
181
- # end
182
- #
183
- # it "should handle HTTP errors fetching the room list" do
184
- # mock_logger
185
- # bot = a EM::Campfire
186
- #
187
- # EM.run_block {
188
- # # stub_request(:get, url).
189
- # # with(:headers => {'Authorization'=>[valid_params[:api_key], 'X'], 'Content-Type'=>'application/json'}).
190
- # # to_return(:status => 502, :body => "", :headers => {'Content-Type'=>'text/html'})
191
- # stub_request(:get, @room_list_url).
192
- # with(:headers => {'Authorization'=>[valid_params[:api_key], 'X']}).
193
- # to_return(:status => 502, :body => "", :headers => {'Content-Type'=>'text/html'})
194
- # lambda {bot.send(:populate_room_list)}.should_not raise_error
195
- # }
196
- # logger_output.should =~ /ERROR.*Couldn't fetch room list with url #{@room_list_url}, http response from API was 502/
197
- # end
198
- #
199
- # it "should handle network errors fetching the room list" do
200
- # mock_logger
201
- # bot = a EM::Campfire
202
- # EM.run_block {
203
- # stub_request(:get, @room_list_url).
204
- # with(:headers => {'Authorization'=>[valid_params[:api_key], 'X']}).to_timeout
205
- # lambda {bot.send(:populate_room_list)}.should_not raise_error
206
- # }
207
- # logger_output.should =~ /ERROR.*Couldn't connect to url #{@room_list_url} to fetch room list/
208
- # end
209
- #
210
- # it "should fetch individual room data" do
211
- # mock_logger
212
- # bot = a EM::Campfire
213
- #
214
- # EM.run_block {
215
- # stub_request(:get, @room_url).
216
- # with(:headers => {'Authorization'=>[valid_params[:api_key], 'X']}).
217
- # to_return(:status => 200, :body => Yajl::Encoder.encode(:room => valid_room_cache_data[123]), :headers => {})
218
- # bot.room_name_for(123)
219
- # }
220
- # logger_output.should =~ /DEBUG.*Fetched room data for 123/
221
- # end
222
- #
223
- # it "should handle HTTP errors fetching individual room data" do
224
- # mock_logger
225
- # bot = a EM::Campfire
226
- #
227
- # EM.run_block {
228
- # stub_request(:get, @room_url).
229
- # with(:headers => {'Authorization'=>[valid_params[:api_key], 'X']}).
230
- # to_return(:status => 502, :body => "", :headers => {'Content-Type'=>'text/html'})
231
- # lambda {bot.room_name_for(123)}.should_not raise_error
232
- # }
233
- # logger_output.should =~ /ERROR.*Couldn't fetch room data for room 123 with url #{@room_url}, http response from API was 502/
234
- # end
235
- #
236
- # it "should handle network errors fetching individual room data" do
237
- # mock_logger
238
- # bot = a EM::Campfire
239
- #
240
- # EM.run_block {
241
- # stub_request(:get, @room_url).
242
- # with(:headers => {'Authorization'=>[valid_params[:api_key], 'X']}).to_timeout
243
- # lambda {bot.room_name_for(123)}.should_not raise_error
244
- # }
245
- # logger_output.should =~ /ERROR.*Couldn't connect to #{@room_url} to fetch room data for room 123/
246
- # end
247
- #
248
- # it "should stream a room"
249
- # it "should handle HTTP errors streaming a room"
250
- # it "should handle network errors streaming a room"
251
85
  end
252
86
  end
253
87