em-campfire 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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