shove 1.0.5 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -9,9 +9,12 @@ end
9
9
 
10
10
  group :test do
11
11
  gem "rspec"
12
- gem "vcr", "2.0.0.rc1"
12
+ gem "vcr", "2.0.1"
13
13
  gem "webmock"
14
14
  gem "net-http-spy"
15
15
  end
16
16
 
17
- gemspec
17
+ gem "em-http-request", ">= 1.0.0"
18
+ gem "em-ws-client", ">= 0.2.0"
19
+ gem "yajl-ruby", ">= 1.1.0"
20
+ gem "confstruct", ">= 0.2.1"
data/Gemfile.lock CHANGED
@@ -1,12 +1,3 @@
1
- PATH
2
- remote: .
3
- specs:
4
- shove (1.0.4)
5
- confstruct (>= 0.2.1)
6
- em-http-request (>= 1.0.0)
7
- em-ws-client (>= 0.1.2)
8
- yajl-ruby (>= 1.1.0)
9
-
10
1
  GEM
11
2
  remote: http://rubygems.org/
12
3
  specs:
@@ -25,9 +16,8 @@ GEM
25
16
  http_parser.rb (>= 0.5.3)
26
17
  em-socksify (0.2.0)
27
18
  eventmachine (>= 1.0.0.beta.4)
28
- em-ws-client (0.1.2)
19
+ em-ws-client (0.2.0)
29
20
  eventmachine (~> 1.0.0.beta.4)
30
- state_machine (~> 1.0.2)
31
21
  eventmachine (1.0.0.beta.4)
32
22
  http_parser.rb (0.5.3)
33
23
  net-http-spy (0.2.1)
@@ -42,8 +32,7 @@ GEM
42
32
  rspec-expectations (2.9.1)
43
33
  diff-lcs (~> 1.1.3)
44
34
  rspec-mocks (2.9.0)
45
- state_machine (1.0.3)
46
- vcr (2.0.0.rc1)
35
+ vcr (2.0.1)
47
36
  webmock (1.8.6)
48
37
  addressable (>= 2.2.7)
49
38
  crack (>= 0.1.7)
@@ -54,10 +43,13 @@ PLATFORMS
54
43
 
55
44
  DEPENDENCIES
56
45
  albino
46
+ confstruct (>= 0.2.1)
47
+ em-http-request (>= 1.0.0)
48
+ em-ws-client (>= 0.2.0)
57
49
  net-http-spy
58
50
  rake
59
51
  redcarpet
60
52
  rspec
61
- shove!
62
- vcr (= 2.0.0.rc1)
53
+ vcr (= 2.0.1)
63
54
  webmock
55
+ yajl-ruby (>= 1.1.0)
data/README.markdown CHANGED
@@ -2,6 +2,7 @@ shove ruby client
2
2
  =================
3
3
  Ruby client and CLI for the shove.io HTTP and WebSocket APIs
4
4
 
5
+ <a name="installation"></a>
5
6
  Installation
6
7
  ------------
7
8
 
@@ -9,8 +10,7 @@ Installation
9
10
  gem install shove
10
11
  ```
11
12
 
12
- Grab your App ID and App Key from shove at [https://shove.io/apps][0]
13
-
13
+ <a name="configuration"></a>
14
14
  Configuring Shove
15
15
  -----------------
16
16
 
@@ -34,96 +34,136 @@ app = Shove::App.new do
34
34
  end
35
35
  ```
36
36
 
37
+ Grab your App ID and App Key from shove: [https://shove.io][0]
38
+
39
+ <a name="http_client"></a>
37
40
  Using the HTTP Client
38
41
  ---------------------
39
42
  The HTTP client gives publishing and access control capabilities without
40
- a persistent WebSocket connection
43
+ a persistent WebSocket connection. The HTTP client does not act as a subscriber.
41
44
 
42
- Publish "Hello World!" to all connections on the notifications channel
45
+ Simple Publish
43
46
 
44
47
  ```ruby
45
- Shove.channel("notifications").publish("Hello World!") do |response|
46
- # handle response
47
- end
48
+ Shove.channel("notifications").publish("Hello World!")
48
49
  ```
49
50
 
50
- Publish "Hey buddy" to the client with id buddy.
51
+ Publish Direct
51
52
 
52
53
  ```ruby
53
- Shove.channel("direct:buddy").publish("Hey buddy") do |response|
54
- # handle response
55
- end
54
+ Shove.channel("direct:buddy").publish("Hey buddy")
56
55
  ```
57
56
 
58
- Apps, channels, and clients can be controlled from the HTTP API
59
-
60
- Grant a connection to dan@shove.io
57
+ Publish and handle the HTTP response
61
58
 
62
59
  ```ruby
63
- Shove.grant_connect("dan@shove.io") do |response|
64
- # handle response
60
+ Shove.channel("notifications").publish("...") do |response|
61
+ if reponse.error?
62
+ puts "#{response.status} - #{response.error}"
63
+ end
65
64
  end
66
65
  ```
67
66
 
68
- Grant subscription on the notifications channel to client dan
67
+ You can control access to your apps and channels, allowing for
68
+ granular security.
69
+
70
+ Grant subscription on the notifications channel
69
71
 
70
72
  ```ruby
71
- Shove.channel("notifications").grant_subscribe("dan") do |response|
72
- # handle response
73
- end
73
+ Shove.channel("notifications").grant_subscribe("dan@shove.io")
74
74
  ```
75
75
 
76
76
  Grant subscription on all channels to client dan
77
77
 
78
78
  ```ruby
79
- Shove.channel("*").grant_subscribe("dan@shove.io") do |response|
80
- # handle response
81
- end
79
+ Shove.channel("*").grant_subscribe("dan@shove.io")
82
80
  ```
83
81
 
84
82
  Grant publishing on chat:client_22733 channel to client dan
85
83
 
86
84
  ```ruby
87
- Shove.channel("chat:client_22733").grant_publish("dan") do |response|
88
- # handle response
89
- end
85
+ Shove.channel("chat:client_22733").grant_publish("dan")
90
86
  ```
91
87
 
92
88
  Deny publishing on chat:client_22733 channel to dan
93
89
 
94
90
  ```ruby
95
- Shove.channel("chat:client_22733").deny_publish("dan") do |response|
96
- # handle response
97
- end
91
+ Shove.channel("chat:client_22733").deny_publish("dan")
98
92
  ```
99
93
 
100
- Using the WebSocket Client
101
- --------------------------
94
+ Sometimes it's easier to give out an access key to a specific
95
+ channel, which is also an option.
96
+
97
+ <a name="channel_keys"></a>
98
+ Channel Keys
99
+ ------------
100
+ You can generate channel keys which allow clients of
101
+ your shove network to publish and subscribe to specific
102
+ channels.
102
103
 
103
- Create a WebSocket client on the default app
104
+ Example: Create a key for the channel groups:788
104
105
 
105
106
  ```ruby
106
- client = Shove.app.connect
107
+ key = Shove.channel_key "group:788"
107
108
  ```
108
109
 
109
- Or
110
+ If it's for a particular App, use:
110
111
 
111
112
  ```ruby
112
- app = Shove::App.new do
113
- app_id "myappid"
113
+ key = app.channel_key "group:788"
114
+ ```
115
+
116
+ This functionality becomes useful when you want to give
117
+ you site users access. A little haml for you:
118
+
119
+ ```haml
120
+ :javascript
121
+ var channel = "#{@channel}";
122
+ var key = "#{@shove.channel_key(@channel)}";
123
+ ```
124
+
125
+ Note: Channel keys are based off the app key. So, in order for
126
+ them to work, you must specify the app key:
127
+
128
+ ```ruby
129
+ Shove.configure do
130
+ app_key "key"
131
+ end
132
+ ```
133
+
134
+ <a name="websocket_client"></a>
135
+ Using the WebSocket Client
136
+ --------------------------
137
+ You can also use the gem to run a persistent client. This
138
+ requires that you are running an EventMachine reactor.
139
+
140
+ ```ruby
141
+ EM.run do
142
+ app = Shove::App.new do
143
+ app_id "myapp"
144
+ end
145
+
146
+ app.connect
147
+
148
+ # alternatively, supply your user id
149
+ client = app.connect "unique_id"
114
150
  end
115
- client = app.connect
116
151
  ```
117
152
 
118
- Create a client with a custom ID
153
+ ### Authorization
154
+ The client is treated like any other websocket
155
+ client and must be authorized to publish and subscribe.
119
156
 
120
157
  ```ruby
121
- client = Shove.app.connect("unique_id")
158
+ client.authorize "app_key"
159
+
160
+ # self authorize a particular channel
161
+ client.channel("channel").authorize "channel_key"
122
162
  ```
123
163
 
124
164
  ### Client events
125
165
 
126
- Handle connect event
166
+ Connect event:
127
167
 
128
168
  ```ruby
129
169
  client.on("connect") do
@@ -131,34 +171,31 @@ client.on("connect") do
131
171
  end
132
172
  ```
133
173
 
134
- Handle connect denies (private app)
174
+ Disconnect event:
135
175
 
136
176
  ```ruby
137
- client.on("connect_denied") do |id|
138
- # Silly, but:
139
- Shove.client(id).grant_connect do |response|
140
- # At this point, the client should receive
141
- # a connect event (through the shove app)
142
- end
177
+ client.on("disconnect") do
178
+ # disconnect code
143
179
  end
144
180
  ```
145
181
 
146
- And disconnect events
182
+ Error event:
147
183
 
148
184
  ```ruby
149
- client.on("disconnect") do
185
+ client.on("error") do |error|
186
+ log.error "Shove error: #{error}"
150
187
  end
151
188
  ```
152
189
 
153
- If there is any kind of error, log it
190
+ Connect denied event: (don't forget to authorize)
154
191
 
155
192
  ```ruby
156
- client.on("error") do |error|
157
- log.error "Shove error: #{error}"
193
+ client.on("connect_denied") do |id|
194
+ client.authorize
158
195
  end
159
196
  ```
160
197
 
161
- ### Clients and Channels
198
+ ### Publish and Subscribe
162
199
 
163
200
  Subscribe to a channel or get a subscribed channel
164
201
 
@@ -187,8 +224,8 @@ handle the subscribe_denied event
187
224
 
188
225
  ```ruby
189
226
  channel.on("subscribe_denied") do
190
- # Silly example
191
- Shove.client(client.id).grant_subscribe(channel.name)
227
+ channel.authorize "key"
228
+ channel.subscribe
192
229
  end
193
230
  ```
194
231
 
@@ -196,6 +233,7 @@ You can get the binding for a callback and cancel it
196
233
 
197
234
  ```ruby
198
235
  binding = channel.on("message") do |msg|
236
+ # important stuff here
199
237
  end
200
238
 
201
239
  binding.cancel
@@ -227,27 +265,6 @@ channel.publish("hi!")
227
265
  channel.publish(obj.to_json)
228
266
  ```
229
267
 
230
- Channel Keys
231
- ------------
232
- You can generate channel keys which allow clients of
233
- your shove network to publish and subscribe to specific
234
- channels.
235
-
236
- Example: Create a key for the channel groups:788
237
-
238
- ```ruby
239
- key = Shove.channel_key "group:788"
240
- ```
241
-
242
- If it's for a particular App, use:
243
-
244
- ```ruby
245
- key = app.channel_key "group:788"
246
- ```
247
-
248
- Note: if you don't have the app key speficied for the app, the
249
- channel key generated will not be correct.
250
-
251
268
  Using the Command Line
252
269
  ----------------------
253
270
 
@@ -281,5 +298,5 @@ Watch all activity on a channel
281
298
  shove watch -c chan
282
299
  ```
283
300
 
284
- [0]: https://shove.io/apps
285
- [1]: http://shove.io/documentation/cli
301
+ [0]: https://shove.io
302
+ [1]: https://shove.io
@@ -1,4 +1,3 @@
1
-
2
1
  module Shove
3
2
 
4
3
  # Used for storing and accessing shove credentials
@@ -10,8 +10,9 @@ module Shove
10
10
  # +name+ The name of the channel
11
11
  def initialize name, conn
12
12
  @conn = conn
13
- @name = name
13
+ @name = name.to_s
14
14
  @callbacks = {}
15
+ @subscribe_sent = false
15
16
  end
16
17
 
17
18
  # Bind a block to an event
@@ -23,6 +24,11 @@ module Shove
23
24
  end
24
25
  result = Callback.new(@callbacks[event], block)
25
26
  @callbacks[event] << result
27
+
28
+ if @name != "direct" && !@subscribe_sent
29
+ subscribe
30
+ end
31
+
26
32
  result
27
33
  end
28
34
 
@@ -46,6 +52,10 @@ module Shove
46
52
  when UNSUBSCRIBE_COMPLETE
47
53
  emit("unsubscribe")
48
54
  @callbacks.clear
55
+ when AUTHORIZE_DENIED
56
+ emit("authorize_denied")
57
+ when AUTHORIZE_COMPLETE
58
+ emit("authorize_complete")
49
59
  end
50
60
 
51
61
  end
@@ -59,6 +69,7 @@ module Shove
59
69
  # subscribe to the channel, by sending to the remote
60
70
  def subscribe
61
71
  @conn.send_data :opcode => SUBSCRIBE, :channel => @name
72
+ @subscribe_sent = true
62
73
  end
63
74
 
64
75
  # unsubscribe from the channel
@@ -68,7 +79,7 @@ module Shove
68
79
 
69
80
  # authorize pub/sub on this channel
70
81
  def authorize channel_key
71
- @conn.send_data :opcode => AUTHORIZE, :channel => @name, :data => channel_key
82
+ @conn.send_data :opcode => AUTHORIZE, :channel => @name, :data => channel_key.to_s
72
83
  end
73
84
 
74
85
  private
@@ -39,6 +39,14 @@ module Shove
39
39
  end
40
40
 
41
41
  @socket = EM::WebSocketClient.new(url)
42
+
43
+ @socket.onclose do
44
+ @connected = false
45
+ unless @forcedc
46
+ reconnect
47
+ end
48
+ end
49
+
42
50
  @socket.onopen do
43
51
  @connected = true
44
52
  send_data :opcode => CONNECT, :data => @id
@@ -46,23 +54,17 @@ module Shove
46
54
  send_data @queue.shift
47
55
  end
48
56
  end
49
-
50
- @socket.onmessage do |m|
51
- process Yajl::Parser.parse(m)
52
- end
53
57
 
54
- @socket.onclose do
55
- @connected = false
56
- unless @forcedc
57
- reconnect
58
- end
58
+ @socket.onmessage do |m, binary|
59
+ process(Yajl::Parser.parse(m))
59
60
  end
61
+
60
62
  end
61
63
 
62
64
  # Disconnect form the server
63
65
  def disconnect
64
66
  @forcedc = true
65
- @socket.disconnect
67
+ @socket.unbind
66
68
  end
67
69
 
68
70
  # Bind to events for a given channel.
@@ -81,9 +83,6 @@ module Shove
81
83
  def channel name
82
84
  unless @channels.key?(name)
83
85
  @channels[name] = Channel.new(name, self)
84
- if name != "direct"
85
- @channels[name].subscribe
86
- end
87
86
  end
88
87
  @channels[name]
89
88
  end
@@ -102,7 +101,7 @@ module Shove
102
101
 
103
102
  def send_data data
104
103
  if @connected
105
- @socket.send_data(Yajl::Encoder.encode(data))
104
+ @socket.send_message(Yajl::Encoder.encode(data))
106
105
  else
107
106
  @queue << data
108
107
  end
@@ -157,8 +156,18 @@ module Shove
157
156
  @closing = true
158
157
  emit "disconnecting"
159
158
  when AUTHORIZE_COMPLETE
159
+ if @channels.key?(channel)
160
+ @channels[channel].process(message)
161
+ else
162
+ emit "authorize_complete"
163
+ end
164
+ when AUTHORIZE_DENIED
165
+ if @channels.key?(channel)
166
+ @channels[channel].process(message)
167
+ else
168
+ emit "authorize_denied"
169
+ end
160
170
  else
161
- #TODO: logger
162
171
  puts "Unknown opcode"
163
172
  end
164
173
 
@@ -13,7 +13,7 @@ module Shove
13
13
  # +message+ the message to publish
14
14
  # +block+ called on response
15
15
  def publish message, &block
16
- @app.request("publish?channel=direct:#{@id}").post(message, &block)
16
+ @app.request("publish?channel=direct:#{@id}").post(message.to_s, &block)
17
17
  end
18
18
 
19
19
  # grant connection to client
data/lib/shove.rb CHANGED
@@ -13,12 +13,12 @@ require "digest/sha1"
13
13
  ##
14
14
  # Shove
15
15
  #
16
- # See http://shove.io for an account and client documentation
16
+ # See https://shove.io for an account and client documentation
17
17
  # See https://github.com/shove/shove-ruby for gem documentation
18
- # See https://github.com/shove/shove for client documentation
18
+ # See https://github.com/shove/shove for js client documentation
19
19
  module Shove
20
20
 
21
- Version = "1.0.5"
21
+ Version = "1.0.7"
22
22
 
23
23
  class ShoveException < Exception; end
24
24
 
data/shove.gemspec CHANGED
@@ -12,7 +12,7 @@ spec = Gem::Specification.new do |s|
12
12
  s.has_rdoc = true
13
13
 
14
14
  s.add_dependency("em-http-request", ">= 1.0.0")
15
- s.add_dependency("em-ws-client", ">= 0.1.2")
15
+ s.add_dependency("em-ws-client", ">= 0.2.0")
16
16
  s.add_dependency("yajl-ruby", ">= 1.1.0")
17
17
  s.add_dependency("confstruct", ">= 0.2.1")
18
18
 
data/spec/helper.rb CHANGED
@@ -49,13 +49,17 @@ module EM
49
49
  @onopen = block
50
50
  end
51
51
 
52
+ def onclose &block
53
+ @onclose = block
54
+ end
55
+
52
56
  def onmessage &block
53
57
  @onmessage = block
54
58
  $backdoor = block
55
59
  @onopen.call # called
56
60
  end
57
61
 
58
- def send_data data
62
+ def send_message data
59
63
  $queue << Yajl::Parser.parse(data)
60
64
  end
61
65
 
@@ -105,8 +105,10 @@ describe Shove::Client do
105
105
  it "should subscribe to a channel" do
106
106
  @client = Shove.app.connect
107
107
  @channel = @client.channel("channel")
108
- @message = $queue.last
109
- @message["opcode"].should == Shove::Protocol::SUBSCRIBE
108
+ $queue.last["opcode"].should_not == Shove::Protocol::SUBSCRIBE
109
+ @channel.on("message") do
110
+ end
111
+ $queue.last["opcode"].should == Shove::Protocol::SUBSCRIBE
110
112
  end
111
113
 
112
114
  it "should get a subscribe granted event" do
@@ -207,6 +209,14 @@ describe Shove::Client do
207
209
  item["data"].should == "key"
208
210
  item["channel"].should == "channel"
209
211
 
212
+ @triggered = false
213
+ @channel.on("authorize_complete") do
214
+ @triggered = true
215
+ end
216
+
217
+ backdoor :opcode => Shove::Protocol::AUTHORIZE_COMPLETE, :channel => "channel"
218
+
219
+ @triggered.should == true
210
220
  end
211
221
 
212
222
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shove
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.5
4
+ version: 1.0.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2012-02-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: em-http-request
16
- requirement: &70156622099200 !ruby/object:Gem::Requirement
16
+ requirement: &21823400 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,21 +21,21 @@ dependencies:
21
21
  version: 1.0.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70156622099200
24
+ version_requirements: *21823400
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: em-ws-client
27
- requirement: &70156622110300 !ruby/object:Gem::Requirement
27
+ requirement: &21822160 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
31
31
  - !ruby/object:Gem::Version
32
- version: 0.1.2
32
+ version: 0.2.0
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70156622110300
35
+ version_requirements: *21822160
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: yajl-ruby
38
- requirement: &70156622116560 !ruby/object:Gem::Requirement
38
+ requirement: &21820560 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 1.1.0
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70156622116560
46
+ version_requirements: *21820560
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: confstruct
49
- requirement: &70156622111420 !ruby/object:Gem::Requirement
49
+ requirement: &21819840 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: 0.2.1
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *70156622111420
57
+ version_requirements: *21819840
58
58
  description: Client side implementation for the shove.io API. See http://shove.io/documentation
59
59
  email: dan@shove.io
60
60
  executables:
@@ -144,44 +144,4 @@ rubygems_version: 1.8.11
144
144
  signing_key:
145
145
  specification_version: 3
146
146
  summary: Ruby gem for leveraging shove.io, the web push platform
147
- test_files:
148
- - spec/app_directory_spec.rb
149
- - spec/cassettes/should_authorize_on_a_channel.yml
150
- - spec/cassettes/should_authorize_oneself.yml
151
- - spec/cassettes/should_be_able_to_authorize_with_the_server.yml
152
- - spec/cassettes/should_cancel_a_binding.yml
153
- - spec/cassettes/should_configure_the_default.yml
154
- - spec/cassettes/should_configure_the_from_the_previous_test.yml
155
- - spec/cassettes/should_create_a_channel_context.yml
156
- - spec/cassettes/should_deny_a_connection.yml
157
- - spec/cassettes/should_deny_a_control_to_a_client.yml
158
- - spec/cassettes/should_deny_a_publishing_to_a_client.yml
159
- - spec/cassettes/should_deny_a_subscriptions_to_a_client.yml
160
- - spec/cassettes/should_deny_publishing_on_a_channel_context.yml
161
- - spec/cassettes/should_deny_subscriptions_on_a_channel_context.yml
162
- - spec/cassettes/should_get_a_set_of_nodes_for_the_network.yml
163
- - spec/cassettes/should_get_a_subscribe_granted_event.yml
164
- - spec/cassettes/should_grant_a_connection.yml
165
- - spec/cassettes/should_grant_a_control_to_a_client.yml
166
- - spec/cassettes/should_grant_a_publishing_to_a_client.yml
167
- - spec/cassettes/should_grant_a_subscriptions_to_a_client.yml
168
- - spec/cassettes/should_grant_publishing_on_a_channel_context.yml
169
- - spec/cassettes/should_grant_subscriptions_on_a_channel_context.yml
170
- - spec/cassettes/should_publish.yml
171
- - spec/cassettes/should_publish_on_a_channel_context.yml
172
- - spec/cassettes/should_publish_to_a_client.yml
173
- - spec/cassettes/should_receive_an_unsubscribe_event.yml
174
- - spec/cassettes/should_receive_messages_on_a_channel.yml
175
- - spec/cassettes/should_send_a_connect_op.yml
176
- - spec/cassettes/should_send_a_connect_op_with_an_id.yml
177
- - spec/cassettes/should_spawn_a_client.yml
178
- - spec/cassettes/should_subscribe_to_a_channel.yml
179
- - spec/cassettes/should_trigger_a_connect_denied_event.yml
180
- - spec/cassettes/should_trigger_a_connect_event.yml
181
- - spec/cassettes/should_trigger_a_disconnect_event.yml
182
- - spec/cassettes/should_trigger_an_error_event.yml
183
- - spec/cassettes/should_unsubscribe_from_a_channel.yml
184
- - spec/cassettes/should_update_the_default_app.yml
185
- - spec/helper.rb
186
- - spec/shove_client_spec.rb
187
- - spec/shove_http_spec.rb
147
+ test_files: []