rhosync 2.0.9 → 2.1.0.beta.1

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.
Files changed (52) hide show
  1. data/CHANGELOG +9 -0
  2. data/Rakefile +1 -1
  3. data/bench/benchapp/sources/mock_adapter.rb +2 -2
  4. data/bench/benchapp/tmp/restart.txt +0 -0
  5. data/bench/spec/mock_adapter_spec.rb +1 -1
  6. data/lib/rhosync/api/stats.rb +21 -0
  7. data/lib/rhosync/bulk_data/bulk_data.rb +1 -1
  8. data/lib/rhosync/client.rb +10 -0
  9. data/lib/rhosync/console/app/public/home.css +9 -4
  10. data/lib/rhosync/console/app/public/images/foot_logo_rhosync.png +0 -0
  11. data/lib/rhosync/console/app/public/images/landing_header.jpg +0 -0
  12. data/lib/rhosync/console/app/public/jqplot/excanvas.min.js +35 -0
  13. data/lib/rhosync/console/app/public/jqplot/jqplot.barRenderer.min.js +34 -0
  14. data/lib/rhosync/console/app/public/jqplot/jqplot.canvasAxisLabelRenderer.js +187 -0
  15. data/lib/rhosync/console/app/public/jqplot/jqplot.canvasAxisTickRenderer.js +226 -0
  16. data/lib/rhosync/console/app/public/jqplot/jqplot.canvasTextRenderer.js +408 -0
  17. data/lib/rhosync/console/app/public/jqplot/jqplot.categoryAxisRenderer.min.js +34 -0
  18. data/lib/rhosync/console/app/public/jqplot/jqplot.cursor.js +952 -0
  19. data/lib/rhosync/console/app/public/jqplot/jqplot.dateAxisRenderer.js +313 -0
  20. data/lib/rhosync/console/app/public/jqplot/jqplot.dateAxisRenderer.min.js +34 -0
  21. data/lib/rhosync/console/app/public/jqplot/jqplot.pointLabels.min.js +34 -0
  22. data/lib/rhosync/console/app/public/jqplot/jquery-1.4.2.min.js +154 -0
  23. data/lib/rhosync/console/app/public/jqplot/jquery.jqplot.min.css +1 -0
  24. data/lib/rhosync/console/app/public/jqplot/jquery.jqplot.min.js +34 -0
  25. data/lib/rhosync/console/app/routes/timing.rb +223 -7
  26. data/lib/rhosync/console/app/views/content.erb +1 -1
  27. data/lib/rhosync/console/app/views/headermenu.erb +4 -4
  28. data/lib/rhosync/console/app/views/jqplot.erb +52 -0
  29. data/lib/rhosync/console/app/views/layout.erb +86 -11
  30. data/lib/rhosync/console/rhosync_api.rb +19 -0
  31. data/lib/rhosync/jobs/bulk_data_job.rb +2 -2
  32. data/lib/rhosync/stats/middleware.rb +4 -6
  33. data/lib/rhosync/stats/record.rb +51 -26
  34. data/lib/rhosync/store.rb +12 -1
  35. data/lib/rhosync/tasks.rb +4 -2
  36. data/lib/rhosync/user.rb +11 -1
  37. data/lib/rhosync/version.rb +2 -2
  38. data/lib/rhosync.rb +3 -3
  39. data/spec/api/rhosync_api_spec.rb +26 -0
  40. data/spec/api/stats_spec.rb +66 -0
  41. data/spec/client_spec.rb +40 -0
  42. data/spec/client_sync_spec.rb +14 -0
  43. data/spec/stats/middleware_spec.rb +11 -5
  44. data/spec/stats/record_spec.rb +30 -11
  45. data/spec/store_spec.rb +15 -1
  46. data/spec/user_spec.rb +44 -0
  47. metadata +32 -14
  48. data/doc/protocol.html +0 -2291
  49. data/doc/public/css/print.css +0 -29
  50. data/doc/public/css/screen.css +0 -257
  51. data/doc/public/css/style.css +0 -20
  52. data/lib/rhosync/console/app/public/images/header_halo copy.jpg +0 -0
@@ -9,7 +9,24 @@
9
9
  <link rel="stylesheet" href="<%=url('home.css')%>" type="text/css" media="screen" />
10
10
  <link rel="stylesheet" href="<%=url('style.css')%>" type="text/css" media="screen" />
11
11
  <link rel="stylesheet" href="<%=url('ThickBox.css')%>" type="text/css" media="screen" />
12
+
13
+ <!--[if IE]><script language="javascript" type="text/javascript" src="<%=url('jqplot/excanvas.js')%>"></script><![endif]-->
14
+ <script language="javascript" type="text/javascript" src="<%=url('jqplot/jquery-1.4.2.min.js')%>"></script>
15
+ <script language="javascript" type="text/javascript" src="<%=url('jqplot/jquery.jqplot.min.js')%>"></script>
16
+ <script language="javascript" type="text/javascript" src="<%=url('jqplot/jqplot.categoryAxisRenderer.min.js')%>"></script>
17
+ <script language="javascript" type="text/javascript" src="<%=url('jqplot/jqplot.dateAxisRenderer.js')%>"></script>
18
+ <script language="javascript" type="text/javascript" src="<%=url('jqplot/jqplot.canvasTextRenderer.js')%>"></script>
19
+ <script language="javascript" type="text/javascript" src="<%=url('jqplot/jqplot.canvasAxisLabelRenderer.js')%>"></script>
20
+ <script language="javascript" type="text/javascript" src="<%=url('jqplot/jqplot.canvasAxisTickRenderer.js')%>"></script>
21
+ <script language="javascript" type="text/javascript" src="<%=url('jqplot/jqplot.cursor.js')%>"></script>
22
+ <!--<script language="javascript" type="text/javascript" src="<%=url('jqplot/jqplot.barRenderer.min.js')%>"></script>-->
23
+ <!--<script language="javascript" type="text/javascript" src="<%=url('jqplot/jqplot.pointLabels.min.js')%>"></script>-->
24
+
25
+ <link rel="stylesheet" type="text/css" href="<%=url('jqplot/jquery.jqplot.min.css')%>" />
26
+
12
27
  <script type="text/javascript">
28
+ $.jqplot.config.enablePlugins = true;
29
+
13
30
 
14
31
  function loadXMLDoc(url,id)
15
32
  {
@@ -30,6 +47,7 @@
30
47
  if (element != null)
31
48
  {
32
49
  element.innerHTML=xmlhttp.responseText;
50
+ parseScript(xmlhttp.responseText);
33
51
  }
34
52
  }
35
53
  }
@@ -46,10 +64,41 @@
46
64
  if(document.getElementById('link7') != null)document.getElementById('link7').className = "no_class";
47
65
  document.getElementById(elem).className = "selected";
48
66
  }
49
-
67
+
68
+ function parseScript(_source) {
69
+ var source = _source;
70
+ var scripts = new Array();
71
+
72
+ // Strip out tags
73
+ while(source.indexOf("<script") > -1 || source.indexOf("</script") > -1) {
74
+ var s = source.indexOf("<script");
75
+ var s_e = source.indexOf(">", s);
76
+ var e = source.indexOf("</script", s);
77
+ var e_e = source.indexOf(">", e);
78
+
79
+ // Add to scripts array
80
+ scripts.push(source.substring(s_e+1, e));
81
+ // Strip from source
82
+ source = source.substring(0, s) + source.substring(e_e+1);
83
+ }
84
+
85
+ // Loop through every script collected and eval it
86
+ for(var i=0; i<scripts.length; i++) {
87
+ try {
88
+ eval(scripts[i]);
89
+ }
90
+ catch(ex) {
91
+ // do what you want here when a script fails
92
+ }
93
+ }
94
+
95
+ // Return the cleaned source
96
+ return source;
97
+ }
50
98
 
51
99
  </script>
52
100
 
101
+
53
102
  </head>
54
103
  <body>
55
104
  <div class="container">
@@ -69,19 +118,45 @@
69
118
  <div class="land_separator"></div>
70
119
  </div><!--END header_landing -->
71
120
  <div style="float:left">
72
- <%unless login_required %>
73
- <% license = {}
74
- handle_api_error("Can't get license information") do
75
- license = RhosyncApi::get_license_info(session[:server],session[:token])
76
- end
77
- %>
78
- <%="Licensed to #{license['licensee']}: #{license['available']}/#{license['seats']} devices available"%>
79
- <%end%>
80
- <br/>Rhosync v<%=Rhosync::VERSION%><br/>
121
+
122
+ <br/>
81
123
  </div>
82
124
  </div><!--END header -->
83
125
  </div><!--END wrapper -->
84
126
  </div><!-- END container -->
85
-
127
+ <div id="footer">
128
+ <div class="footer_content">
129
+ <div id="foot_lefts" class="foot_left">
130
+ <img src="/console/images/foot_logo_rhosync.png"></a>
131
+ <p>© 2010 <a href="http://www.rhomobile.com/">Rhomobile</a> All rights reserved.</p>
132
+ </div>
133
+ <div class="foot_right">
134
+ <ul class="ul_footer">
135
+ <%unless login_required %>
136
+ <% license = {}
137
+ handle_api_error("Can't get license information") do
138
+ license = RhosyncApi::get_license_info(session[:server],session[:token])
139
+ end
140
+ %>
141
+ <%="<li>Licensed to #{license['licensee']}</li><li>|</li><li> #{license['available']}/#{license['seats']} devices available</li>"%>
142
+ <%end%>
143
+ <li>|</li>
144
+ <li>Rhosync v<%=Rhosync::VERSION%></li>
145
+ <!-- <li><a href="http://app.rhohub.com">Home</a></li>
146
+ <li>|</li>
147
+ <li><a href="http://app.rhohub.com/how_to_use">How to Use</a></li>
148
+ <li>|</li>
149
+ <li><a href="http://app.rhohub.com/pricing">Pricing</a></li>
150
+ <li>|</li>
151
+ <li><a class="largea3" target="_blank" href="http://wiki.rhomobile.com/index.php/RhoHub">Docs</a></li>
152
+ <li>|</li>
153
+ <li><a href="http://app.rhohub.com/terms">Terms of Service</a></li>
154
+ <li>|</li>
155
+ <li><a href="http://www.twitter.com/rhohub">Status</a></li>
156
+ --> </ul>
157
+ </div>
158
+ <br class="clear">
159
+ </div>
160
+ </div>
86
161
  </body>
87
162
  </html>
@@ -34,6 +34,16 @@ module RhosyncApi
34
34
  :content_type => :json)
35
35
  end
36
36
  end
37
+
38
+ def update_user(server,token,attributes)
39
+ if directcall?(server) and verify_token(token)
40
+ Server.update_user({:attributes => attributes},api_user(token))
41
+ else
42
+ RestClient.post("#{server}/api/update_user",
43
+ {:api_token => token,:attributes => attributes}.to_json,
44
+ :content_type => :json)
45
+ end
46
+ end
37
47
 
38
48
  def delete_user(server,token,user_id)
39
49
  if directcall?(server) and verify_token(token)
@@ -172,6 +182,15 @@ module RhosyncApi
172
182
  end
173
183
  end
174
184
 
185
+ def stats(server,token,params)
186
+ if directcall?(server) and verify_token(token)
187
+ Server.stats(params,api_user(token))
188
+ else
189
+ RestClient.post("#{server}/api/stats",
190
+ {:api_token => token}.merge!(params).to_json, :content_type => :json).body
191
+ end
192
+ end
193
+
175
194
  private
176
195
 
177
196
  # TODO: Kill this code when rest-client properly
@@ -16,7 +16,7 @@ module Rhosync
16
16
  ts = Time.now.to_i.to_s
17
17
  create_sqlite_data_file(bulk_data,ts)
18
18
  timer = lap_timer('create_sqlite_data_file',timer)
19
- log " bulk_data.dbfile : #{bulk_data.dbfile}"
19
+ log "bulk_data.dbfile: #{bulk_data.dbfile}"
20
20
  create_hsql_data_file(bulk_data,ts) if Rhosync.blackberry_bulk_sync
21
21
  lap_timer('create_hsql_data_file',timer)
22
22
  log "finished bulk data process"
@@ -177,7 +177,7 @@ module Rhosync
177
177
 
178
178
  def self.compress(archive,file)
179
179
  Zip::ZipFile.open(archive, 'w') do |zipfile|
180
- zipfile.add(File.basename(file),file)
180
+ zipfile.add(URI.escape(File.basename(file)),file)
181
181
  end
182
182
  end
183
183
  end
@@ -9,12 +9,10 @@ module Rhosync
9
9
  start = Time.now.to_f
10
10
  status, headers, body = @app.call(env)
11
11
  finish = Time.now.to_f
12
- metric = "http:#{env['REQUEST_METHOD']}:#{env['REQUEST_PATH']}"
13
- source_id = env['rack.request.query_hash']["source_id"] if env['rack.request.query_hash']
14
- metric << ":#{source_id}" if source_id
15
- Record.add(metric,finish - start) do |counter,aggregate|
16
- Record.save_average(counter,aggregate)
17
- end
12
+ metric = "http:#{env['REQUEST_METHOD']}:#{env['PATH_INFO']}"
13
+ source_name = env['rack.request.query_hash']["source_name"] if env['rack.request.query_hash']
14
+ metric << ":#{source_name}" if source_name
15
+ Record.save_average(metric,finish - start)
18
16
  [status, headers, body]
19
17
  end
20
18
  end
@@ -6,33 +6,41 @@ module Rhosync
6
6
  # Add a value to a metric. If zset already has a member,
7
7
  # update the existing member with an incremented value by default.
8
8
  # Also supports updating the value with a block (useful for averages)
9
- def add(metric, value = 1)
10
- start = Time.now.to_i
9
+ def set(metric, value = 0)
10
+ start = (Time.now.to_i / resolution(metric)) * resolution(metric)
11
11
  current, current_score = 0, start
12
- range = Store.db.zrevrange(key(metric), 0, 0)
13
- if !range.empty?
14
- member = range[0]
15
- m_current = member.split(':')[0]
16
- m_current_score = Store.db.zscore(key(metric), member).to_i
17
- if m_current_score > (start - resolution(metric))
18
- Store.db.zrem(key(metric), member)
19
- current, current_score = m_current, m_current_score
12
+ Store.lock(key(metric)) do
13
+ range = Store.db.zrevrange(key(metric), 0, 0)
14
+ if !range.empty?
15
+ member = range[0]
16
+ m_current = member.split(':')[0]
17
+ m_current_score = Store.db.zscore(key(metric), member).to_i
18
+ if m_current_score > (start - resolution(metric))
19
+ Store.db.zrem(key(metric), member)
20
+ current, current_score = m_current, m_current_score
21
+ end
20
22
  end
23
+ value = block_given? ? yield(current, value) : value
24
+ Store.db.zadd(key(metric), current_score, "#{value}:#{start}")
25
+ Store.db.zremrangebyscore(key(metric), 0, start - record_size(metric))
21
26
  end
22
- value = block_given? ? yield(current, value) : (current.to_i + value)
23
- Store.db.zadd(key(metric), current_score, "#{value}:#{start}")
24
- Store.db.zremrangebyscore(key(metric), 0, start - record_size(metric))
27
+ end
28
+
29
+ def add(metric, value = 1)
30
+ set(metric,value) { |current,value| current.to_i + value }
25
31
  end
26
32
 
27
33
  # Saves the accumulated average for a resolution in a metric
28
- def save_average(current, value)
29
- sum = value
30
- if current.is_a?(String)
31
- current,sum = current.split(',')
32
- current = current.to_f
33
- sum = sum.to_f+value
34
- end
35
- "#{current + 1},#{sum}"
34
+ def save_average(metric, value)
35
+ set(metric,value) do |current,value|
36
+ sum = value
37
+ if current.is_a?(String)
38
+ current,sum = current.split(',')
39
+ current = current.to_f
40
+ sum = sum.to_f+value
41
+ end
42
+ "#{current + 1},#{sum}"
43
+ end
36
44
  end
37
45
 
38
46
  def update(metric)
@@ -41,14 +49,16 @@ module Rhosync
41
49
  # perform the operations
42
50
  yield
43
51
  finish = Time.now.to_f
44
- add(metric, finish - start) do |counter, aggregate|
45
- save_average(counter, aggregate)
46
- end
52
+ save_average(metric, finish - start)
47
53
  else
48
54
  yield
49
55
  end
50
56
  end
51
57
 
58
+ def keys(glob='*')
59
+ Store.db.keys(key(glob)).collect {|c| c[5..-1]}
60
+ end
61
+
52
62
  def reset(metric)
53
63
  Store.db.del(key(metric))
54
64
  end
@@ -56,6 +66,16 @@ module Rhosync
56
66
  def reset_all
57
67
  Store.flash_data('stat:*')
58
68
  end
69
+
70
+ # Returns simple string metric
71
+ def get_value(metric)
72
+ Store.get_value(key(metric))
73
+ end
74
+
75
+ # Sets a string metric
76
+ def set_value(metric, value)
77
+ Store.set_value(key(metric), value)
78
+ end
59
79
 
60
80
  # Returns the metric data, uses array indexing
61
81
  def range(metric, start, finish = -1)
@@ -64,16 +84,21 @@ module Rhosync
64
84
 
65
85
  # Returns the resolution for a given metric, default 60 seconds
66
86
  def resolution(metric)
67
- resolution = Object.const_get("#{metric.upcase}_RECORD_RESOLUTION") rescue nil
87
+ resolution = STATS_RECORD_RESOLUTION rescue nil
68
88
  resolution || 60 #=> 1 minute aggregate
69
89
  end
70
90
 
71
91
  # Returns the # of records to save for a given metric
72
92
  def record_size(metric)
73
- size = Object.const_get("#{metric.upcase}_RECORD_SIZE") rescue nil
93
+ size = STATS_RECORD_SIZE rescue nil
74
94
  size || 60 * 24 * 31 #=> 44640 minutes
75
95
  end
76
96
 
97
+ # Returns redis object type for a record
98
+ def rtype(metric)
99
+ Store.db.type(key(metric))
100
+ end
101
+
77
102
  def key(metric)
78
103
  "stat:#{metric}"
79
104
  end
data/lib/rhosync/store.rb CHANGED
@@ -56,6 +56,14 @@ module Rhosync
56
56
  def get_value(dockey)
57
57
  @@db.get(dockey) if dockey
58
58
  end
59
+
60
+ def incr(dockey)
61
+ @@db.incr(dockey)
62
+ end
63
+
64
+ def decr(dockey)
65
+ @@db.decr(dockey)
66
+ end
59
67
 
60
68
  # Retrieves set for given dockey,source,user
61
69
  def get_data(dockey,type=Hash)
@@ -179,6 +187,9 @@ module Rhosync
179
187
  @@db.rename(srckey,dstkey) if @@db.exists(srckey)
180
188
  end
181
189
 
190
+ alias_method :set_value, :put_value
191
+ alias_method :set_data, :put_data
192
+
182
193
  private
183
194
  def _get_redis(server=nil)
184
195
  if ENV[REDIS_URL]
@@ -195,7 +206,7 @@ module Rhosync
195
206
  end
196
207
 
197
208
  def _lock_key(dockey)
198
- "#{dockey}:lock"
209
+ "lock:#{dockey}"
199
210
  end
200
211
 
201
212
  def _is_reserved?(attrib,value) #:nodoc:
data/lib/rhosync/tasks.rb CHANGED
@@ -159,12 +159,14 @@ namespace :rhosync do
159
159
  system "stty echo"
160
160
  exit
161
161
  end
162
- if new_pass == new_pass_confirm
162
+ if new_pass == ''
163
+ puts "\nNew password can't be empty."
164
+ elsif new_pass == new_pass_confirm
163
165
  puts ""
164
166
  post("/api/update_user", {:app_name => $appname, :api_token => $token,
165
167
  :attributes => {:new_password => new_pass}})
166
168
  else
167
- puts "\nNew password and confirm didn't match."
169
+ puts "\nNew password and confirmation must match."
168
170
  end
169
171
  end
170
172
 
data/lib/rhosync/user.rb CHANGED
@@ -11,10 +11,15 @@ module Rhosync
11
11
  set :clients, :string
12
12
  field :admin, :int
13
13
  field :token_id, :string
14
-
14
+
15
15
  class << self
16
16
  def create(fields={})
17
17
  fields[:id] = fields[:login]
18
+ if Rhosync.stats
19
+ Rhosync::Stats::Record.set('users') { Store.incr('user:count') }
20
+ else
21
+ Store.incr('user:count')
22
+ end
18
23
  super(fields)
19
24
  end
20
25
 
@@ -41,6 +46,11 @@ module Rhosync
41
46
  Client.load(client_id,{:source_name => '*'}).delete
42
47
  end
43
48
  self.token.delete if self.token
49
+ if Rhosync.stats
50
+ Rhosync::Stats::Record.set('users') { Store.decr('user:count') }
51
+ else
52
+ Store.decr('user:count')
53
+ end
44
54
  super
45
55
  end
46
56
 
@@ -1,3 +1,3 @@
1
1
  module Rhosync
2
- VERSION = '2.0.9'
3
- end
2
+ VERSION = '2.1.0.beta.1'
3
+ end
data/lib/rhosync.rb CHANGED
@@ -9,10 +9,12 @@ require 'rhosync/document'
9
9
  require 'rhosync/lock_ops'
10
10
  require 'rhosync/model'
11
11
  require 'rhosync/source'
12
+ require 'rhosync/store'
13
+ require 'rhosync/stats/record'
14
+ require 'rhosync/stats/middleware'
12
15
  require 'rhosync/user'
13
16
  require 'rhosync/api_token'
14
17
  require 'rhosync/app'
15
- require 'rhosync/store'
16
18
  require 'rhosync/client'
17
19
  require 'rhosync/read_state'
18
20
  require 'rhosync/client_sync'
@@ -21,8 +23,6 @@ require 'rhosync/source_sync'
21
23
  require 'rhosync/rho_indifferent_access'
22
24
  require 'rhosync/jobs/source_job'
23
25
  require 'rhosync/jobs/ping_job'
24
- require 'rhosync/stats/record'
25
- require 'rhosync/stats/middleware'
26
26
  require 'rhosync/bulk_data'
27
27
 
28
28
  REDIS_URL = 'REDIS' unless defined? REDIS_URL
@@ -47,6 +47,17 @@ describe "RhosyncApi" do
47
47
  RhosyncApi::create_user('some_url',@api_token,'testuser1','testpass1').should == "User created"
48
48
  end
49
49
 
50
+ it "should update user using direct api call" do
51
+ RhosyncApi::update_user('',@api_token, {:new_password => '123'})
52
+ User.authenticate('rhoadmin','123').login.should == 'rhoadmin'
53
+ end
54
+
55
+ it "should update user using rest call" do
56
+ RestClient.stub(:post)
57
+ RestClient.should_receive(:post).once
58
+ RhosyncApi::update_user('some_url',@api_token, {:new_password => '123'})
59
+ end
60
+
50
61
  it "should delete user direct api call" do
51
62
  RhosyncApi::create_user('',@api_token,'testuser1','testpass1').should == "User created"
52
63
  User.is_exist?('testuser1').should == true
@@ -302,5 +313,20 @@ describe "RhosyncApi" do
302
313
  RestClient.should_receive(:post).once
303
314
  RhosyncApi::get_license_info('some_url',@api_token)
304
315
  end
316
+
317
+ it "should get stats using direct api call" do
318
+ Rhosync.stats = true
319
+ Store.set_value('stat:foo','bar')
320
+ RhosyncApi::stats('',@api_token,:metric => 'foo').should == 'bar'
321
+ Rhosync.stats = false
322
+ end
323
+
324
+ it "should get stats using rest call" do
325
+ res = mock('HttpResponse')
326
+ res.stub!(:body).and_return('bar')
327
+ RestClient.stub(:post).and_return(res)
328
+ RestClient.should_receive(:post).once.and_return(res)
329
+ RhosyncApi::stats('some_url',@api_token,:metric => 'foo')
330
+ end
305
331
 
306
332
  end
@@ -0,0 +1,66 @@
1
+ require File.join(File.dirname(__FILE__),'api_helper')
2
+
3
+ describe "RhosyncApiStats" do
4
+ it_should_behave_like "ApiHelper"
5
+
6
+ before(:each) do
7
+ Rhosync.stats = true
8
+ end
9
+
10
+ after(:each) do
11
+ Rhosync.stats = false
12
+ end
13
+
14
+ it "should retrieve metric names" do
15
+ Store.set_value('stat:foo', '1')
16
+ Store.set_value('stat:bar', '2')
17
+ post "/api/stats", {
18
+ :api_token => @api_token,
19
+ :names => '*'
20
+ }
21
+ last_response.should be_ok
22
+ JSON.parse(last_response.body).sort.should == ['bar', 'foo']
23
+ end
24
+
25
+ it "should retrieve range metric" do
26
+ Store.db.zadd('stat:foo', 2, "1:2")
27
+ Store.db.zadd('stat:foo', 3, "1:3")
28
+ post "/api/stats", {
29
+ :api_token => @api_token,
30
+ :metric => 'foo',
31
+ :start => 0,
32
+ :finish => -1
33
+ }
34
+ last_response.should be_ok
35
+ JSON.parse(last_response.body).should == ["1:2", "1:3"]
36
+ end
37
+
38
+ it "should retrieve string metric" do
39
+ Store.db.set('stat:foo', 'bar')
40
+ post "/api/stats", {
41
+ :api_token => @api_token,
42
+ :metric => 'foo'
43
+ }
44
+ last_response.should be_ok
45
+ last_response.body.should == 'bar'
46
+ end
47
+
48
+ it "should raise error on unknown metric" do
49
+ post "/api/stats", {
50
+ :api_token => @api_token,
51
+ :metric => 'foo'
52
+ }
53
+ last_response.status.should == 404
54
+ last_response.body.should == 'Unknown metric'
55
+ end
56
+
57
+ it "should raise error if stats not enabled" do
58
+ Rhosync.stats = false
59
+ post "/api/stats", {
60
+ :api_token => @api_token,
61
+ :metric => 'foo'
62
+ }
63
+ last_response.status.should == 500
64
+ last_response.body.should == 'Stats not enabled'
65
+ end
66
+ end
data/spec/client_spec.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  require File.join(File.dirname(__FILE__),'spec_helper')
2
2
 
3
+ STATS_RECORD_RESOLUTION = 2 unless defined? STATS_RECORD_RESOLUTION
4
+ STATS_RECORD_SIZE = 8 unless defined? STATS_RECORD_SIZE
5
+
3
6
  describe "Client" do
4
7
  it_should_behave_like "SpecBootstrapHelper"
5
8
  it_should_behave_like "SourceAdapterHelper"
@@ -16,6 +19,7 @@ describe "Client" do
16
19
  end
17
20
 
18
21
  it "should create client with user_id" do
22
+ Store.get_value('client:count').should == "1"
19
23
  @c.id.length.should == 32
20
24
  @c.user_id.should == @c_fields[:user_id]
21
25
  @u.clients.members.should == [@c.id]
@@ -38,6 +42,7 @@ describe "Client" do
38
42
  it "should free seat when client is deleted" do
39
43
  current = Store.get_value(License::CLIENT_DOCKEY).to_i
40
44
  @c.delete
45
+ Store.get_value('client:count').should == '0'
41
46
  Store.get_value(License::CLIENT_DOCKEY).to_i.should == current - 1
42
47
  end
43
48
 
@@ -55,4 +60,39 @@ describe "Client" do
55
60
  verify_result(@c.docname(:cd) => @data,
56
61
  @s.docname(:md_copy) => @data)
57
62
  end
63
+
64
+ describe "Client Stats" do
65
+
66
+ before(:each) do
67
+ Rhosync::Stats::Record.reset('clients')
68
+ end
69
+
70
+ after(:each) do
71
+ Rhosync::Stats::Record.reset('clients')
72
+ end
73
+
74
+ after(:all) do
75
+ Store.flash_data('stat:clients*')
76
+ end
77
+
78
+ it "should increment clients stats on create" do
79
+ Time.stub!(:now).and_return(10)
80
+ Rhosync.stats = true
81
+ Client.create(@c_fields,{:source_name => @s_fields[:name]})
82
+ Rhosync::Stats::Record.range('clients',0,-1).should == ["2:10"]
83
+ Store.get_value('client:count').should == "2"
84
+ Rhosync.stats = false
85
+ end
86
+
87
+ it "should decrement clients stats on delete" do
88
+ Time.stub!(:now).and_return(10)
89
+ Rhosync.stats = true
90
+ c = Client.create(@c_fields,{:source_name => @s_fields[:name]})
91
+ Rhosync::Stats::Record.range('clients',0,-1).should == ["2:10"]
92
+ c.delete
93
+ Rhosync::Stats::Record.range('clients',0,-1).should == ["1:10"]
94
+ Store.get_value('client:count').should == "1"
95
+ Rhosync.stats = false
96
+ end
97
+ end
58
98
  end
@@ -444,6 +444,20 @@ describe "ClientSync" do
444
444
  "source:#{@a_fields[:name]}:#{@u_fields[:login]}:#{@s_fields[:name]}:md_copy" => @data)
445
445
  end
446
446
 
447
+ it "should escape bulk data url" do
448
+ name = 'a b'
449
+ data = BulkData.create(:name => bulk_data_docname(@a.id,name),
450
+ :state => :inprogress,
451
+ :app_id => @a.id,
452
+ :user_id => name,
453
+ :sources => [@s_fields[:name]])
454
+ puts "data: #{data.inspect}"
455
+ BulkDataJob.perform("data_name" => bulk_data_docname(@a.id,name))
456
+ data = BulkData.load(bulk_data_docname(@a.id,name))
457
+ data.url.should match /a%20b/
458
+ data.delete
459
+ end
460
+
447
461
  it "should return bulk data url for completed bulk data app partition" do
448
462
  set_state('test_db_storage' => @data)
449
463
  @s.partition = :app
@@ -1,6 +1,6 @@
1
1
  require 'rhosync'
2
- FOO_RECORD_RESOLUTION = 2
3
- FOO_RECORD_SIZE = 8
2
+ STATS_RECORD_RESOLUTION = 2 unless defined? STATS_RECORD_RESOLUTION
3
+ STATS_RECORD_SIZE = 8 unless defined? STATS_RECORD_SIZE
4
4
 
5
5
  include Rhosync
6
6
  include Rhosync::Stats
@@ -13,20 +13,26 @@ describe "Middleware" do
13
13
  app = mock('app')
14
14
  app.stub!(:call)
15
15
  @middleware = Middleware.new(app)
16
+ Store.stub!(:lock).and_yield
16
17
  end
17
18
 
18
19
  it "should compute http average" do
19
20
  Time.stub!(:now).and_return { @now += 0.3; @now }
20
21
  env = {
21
22
  'rack.request.query_hash' => {
22
- 'source_id' => 'SampleAdapter'
23
+ 'source_name' => 'SampleAdapter'
23
24
  },
24
25
  'REQUEST_METHOD' => 'GET',
25
- 'REQUEST_PATH' => '/application'
26
+ 'PATH_INFO' => '/application'
26
27
  }
27
28
  10.times { @middleware.call(env) }
28
29
  metric = 'http:GET:/application:SampleAdapter'
29
30
  Record.key(metric).should == "stat:#{metric}"
30
- Record.range(metric, 0, -1).should == ["10.0,3.0:19"]
31
+ Record.range(metric, 0, -1).should == [
32
+ "2.0,0.600000000000002:12",
33
+ "2.0,0.600000000000002:14",
34
+ "2.0,0.600000000000002:16",
35
+ "2.0,0.600000000000002:18"
36
+ ]
31
37
  end
32
38
  end