rappfirst 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ config.*
2
+ spec/fixtures/**
3
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Andrew Gross
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Rappfirst
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'rappfirst'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install rappfirst
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.test_files = FileList['spec/lib/rappfirst/*_spec.rb']
5
+ t.verbose = true
6
+ end
7
+
8
+ task :default => :test
@@ -0,0 +1,44 @@
1
+ module Rappfirst
2
+ class Client
3
+ include HTTParty
4
+ format :json
5
+
6
+ def initialize(username=nil, api_key=nil)
7
+ if username.nil? || api_key.nil?
8
+ config = YAML::load( File.open( 'config.yml') )
9
+ username = config['username']
10
+ api_key = config['password']
11
+ end
12
+ self.class.basic_auth username, api_key
13
+ self.class.base_uri 'https://wwws.appfirst.com/api/v3'
14
+ end
15
+
16
+ def servers(query_string=nil)
17
+ response = get_servers(query_string)
18
+ if response.length == 1
19
+ return server(response.first['id'])
20
+ else
21
+ s = Array.new
22
+ response.each do |r|
23
+ s << server(r['id'])
24
+ end
25
+ return s
26
+ end
27
+ end
28
+
29
+ def server(id)
30
+ api_options = self.class.default_options
31
+ Rappfirst::Server.new(id, api_options=api_options)
32
+ end
33
+
34
+ private
35
+
36
+ def get_servers(query_string=nil)
37
+ if query_string && ! query_string.start_with?('?')
38
+ query_string = '?' + query_string
39
+ end
40
+ self.class.get("/servers/#{query_string}")
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,112 @@
1
+ module Rappfirst
2
+ class Server
3
+ include HTTParty
4
+ format :json
5
+
6
+ attr_accessor :id
7
+
8
+ def writeable?(name)
9
+ return ['description', 'nickname'].include?(name)
10
+ end
11
+
12
+ def initialize(id, api_options=nil)
13
+ if api_options && api_options.keys.include?(:basic_auth)
14
+ username = api_options[:basic_auth][:username]
15
+ api_key = api_options[:basic_auth][:api_key]
16
+ else
17
+ username = get_config('username')
18
+ api_key = get_config('password')
19
+ end
20
+
21
+ if api_options && api_options.keys.include?(:basic_uri)
22
+ base_uri = api_options[:base_uri]
23
+ else
24
+ base_uri = 'https://wwws.appfirst.com/api/v3'
25
+ end
26
+
27
+ self.class.basic_auth username, api_key
28
+ self.class.base_uri base_uri
29
+
30
+ self.id = id
31
+ set_attributes
32
+ end
33
+
34
+ def polled_data(refresh=false)
35
+ refresh ? @polled_data = get_polled_data : @polled_data ||= get_polled_data
36
+ end
37
+
38
+ def polled_data=(new_polled_data_config)
39
+ #set_polled_data(new_polled_data_config)
40
+ end
41
+
42
+ def outages(refresh=false)
43
+ refresh ? @outage_data = get_outage_data : @outage_data ||= get_outage_data
44
+ end
45
+
46
+ def tags(refresh=false)
47
+ refresh ? @tags = get_tags : @tags ||= get_tags
48
+ end
49
+
50
+ def delete
51
+ delete_self
52
+ end
53
+
54
+ private
55
+
56
+ def delete_self
57
+ response = self.class.delete("/servers/#{self.id}/")
58
+ unless response.code == 200
59
+ raise "Unable to delete server, received HTTP code #{response.code}"
60
+ end
61
+ end
62
+
63
+ def get_tags
64
+ return self.class.get("/servers/#{self.id}/tags/")['server_tags']
65
+ end
66
+
67
+ def get_outage_data
68
+ return self.class.get("/servers/#{self.id}/outages/")
69
+ end
70
+
71
+ def get_polled_data
72
+ return self.class.get("/servers/#{self.id}/polled_data_config/")
73
+ end
74
+
75
+ def set_polled_data(config)
76
+ return self.class.put("/servers/#{self.id}/polled_data_config/", config)
77
+ end
78
+
79
+ def set_attributes
80
+ response = get_attributes
81
+ response.each do |name, v|
82
+ create_method( "#{name}=".to_sym ) { |val|
83
+ if ! instance_variable_get("@" + name)
84
+ instance_variable_set("@" + name, val)
85
+ elsif self.writeable?(name) && instance_variable_get("@" + name)
86
+ instance_variable_set("@" + name, val)
87
+ end
88
+ }
89
+
90
+ create_method( name.to_sym ) {
91
+ instance_variable_get("@" + name)
92
+ }
93
+
94
+ instance_variable_set("@" + name, v)
95
+ end
96
+ end
97
+
98
+ def get_attributes
99
+ return self.class.get("/servers/#{self.id}/")
100
+ end
101
+
102
+ def create_method(name, &block)
103
+ self.class.send(:define_method, name, &block)
104
+ end
105
+
106
+ def get_config(key)
107
+ config = YAML::load( File.open('config.yml'))
108
+ return config[key]
109
+ end
110
+
111
+ end
112
+ end
@@ -0,0 +1,3 @@
1
+ module Rappfirst
2
+ VERSION = "0.1.0"
3
+ end
data/lib/rappfirst.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "httparty"
2
+
3
+ Dir[File.dirname(__FILE__) + '/rappfirst/*.rb'].each do |file|
4
+ require file
5
+ end
data/rappfirst.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rappfirst/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "rappfirst"
8
+ gem.version = Rappfirst::VERSION
9
+ gem.authors = ["Andrew Gross"]
10
+ gem.email = ["andrew.w.gross@gmail.com"]
11
+ gem.description = "See Readme.md"
12
+ gem.summary = "Appfirst API v3 Wrapper"
13
+ gem.homepage = "https://github.com/andrewgross/rappfirst"
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ gem.require_paths = ["lib"]
18
+
19
+ gem.add_runtime_dependency 'httparty'
20
+
21
+ gem.add_development_dependency 'debugger'
22
+ gem.add_development_dependency 'webmock'
23
+ gem.add_development_dependency 'vcr'
24
+ gem.add_development_dependency 'turn'
25
+ gem.add_development_dependency 'rake'
26
+ end
@@ -0,0 +1,99 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ describe Rappfirst::Client do
4
+
5
+ describe "default attributes" do
6
+
7
+ it "must include httparty methods" do
8
+ Rappfirst::Client.must_include HTTParty
9
+ end
10
+
11
+ it "must have the base url set to the Appfirst API" do
12
+ Rappfirst::Client.
13
+ instance_variable_get("@default_options")[:base_uri].
14
+ must_equal 'https://wwws.appfirst.com/api/v3'
15
+ end
16
+
17
+ it "must have API Credentials" do
18
+ Rappfirst::Client.
19
+ instance_variable_get("@default_options")[:basic_auth].
20
+ keys.must_include(:username)
21
+ end
22
+
23
+ end
24
+
25
+ describe "GET servers" do
26
+
27
+ let(:client) { Rappfirst::Client.new }
28
+
29
+ describe "Get all servers" do
30
+
31
+ before do
32
+ VCR.insert_cassette 'servers', :record => :new_episodes
33
+ end
34
+
35
+ after do
36
+ VCR.eject_cassette
37
+ end
38
+
39
+ it "must have a servers method" do
40
+ client.must_respond_to :servers
41
+ end
42
+
43
+ it "must parse the api response from JSON to Hash" do
44
+ client.servers.must_be_instance_of Array
45
+ end
46
+
47
+ it "should return an array of server objects" do
48
+ client.servers.each { |s| s.must_be_instance_of Rappfirst::Server}
49
+ end
50
+
51
+ end
52
+
53
+ describe "Get specific server" do
54
+
55
+ before do
56
+ VCR.insert_cassette 'search_for_server', :record => :new_episodes
57
+ end
58
+
59
+ after do
60
+ VCR.eject_cassette
61
+ end
62
+
63
+ it "must accept query parameters without a question mark" do
64
+ client.servers(query='hostname=yipit-linkedin-worker2').
65
+ must_be_instance_of Rappfirst::Server
66
+ end
67
+
68
+ it "should return a server object" do
69
+ client.servers(query='?hostname=yipit-linkedin-worker2').
70
+ must_be_instance_of Rappfirst::Server
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+
77
+ describe "Query single server" do
78
+
79
+ let(:client) { Rappfirst::Client.new }
80
+
81
+ before do
82
+ VCR.insert_cassette 'single_server', :record => :new_episodes
83
+ end
84
+
85
+ after do
86
+ VCR.eject_cassette
87
+ end
88
+
89
+ it "must have a server method" do
90
+ client.must_respond_to :server
91
+ end
92
+
93
+ it "must return a server object" do
94
+ client.server('11743').must_be_instance_of Rappfirst::Server
95
+ end
96
+
97
+ end
98
+
99
+ end
@@ -0,0 +1,248 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ describe Rappfirst::Server do
4
+
5
+ describe "default attributes" do
6
+
7
+ it "must include httparty methods" do
8
+ Rappfirst::Server.must_include HTTParty
9
+ end
10
+
11
+ it "must have the base url set to the Appfirst API" do
12
+ Rappfirst::Server.
13
+ instance_variable_get("@default_options")[:base_uri].
14
+ must_equal 'https://wwws.appfirst.com/api/v3'
15
+ end
16
+
17
+ it "must have API Credentials" do
18
+ Rappfirst::Client.
19
+ instance_variable_get("@default_options")[:basic_auth].
20
+ keys.must_include(:username)
21
+ end
22
+
23
+ end
24
+
25
+
26
+
27
+ describe "attributes" do
28
+
29
+ let(:server) { Rappfirst::Server.new('11743') }
30
+
31
+ before do
32
+ VCR.insert_cassette 'server', :record => :new_episodes
33
+ end
34
+
35
+ after do
36
+ VCR.eject_cassette
37
+ end
38
+
39
+ describe "retrieve and create methods" do
40
+
41
+ it "must populate the hostname attribute" do
42
+ server.must_respond_to :hostname
43
+ end
44
+
45
+ it "must make the hostname attribute read-only" do
46
+ h = server.hostname
47
+ server.hostname = "foobar"
48
+ server.hostname.must_equal h
49
+ end
50
+
51
+ it "must populate the nickname attribute" do
52
+ server.must_respond_to :nickname
53
+ end
54
+
55
+ it "must make the nickname attribute writeable" do
56
+ n = server.nickname
57
+ server.nickname = "foobar"
58
+ server.nickname.wont_equal n
59
+ end
60
+
61
+ end
62
+
63
+ describe "cache data" do
64
+
65
+ before do
66
+ server.polled_data
67
+ stub_request(:any, /wwws.appfirst.com/).to_timeout
68
+ end
69
+
70
+ it "must cache attributes" do
71
+ skip "TODO: Implement Refreshing of base attributes"
72
+ lambda { server.hostname }.must_raise Timeout::Error
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+
79
+
80
+
81
+ describe "polled data config" do
82
+
83
+ let(:server) { Rappfirst::Server.new('11743') }
84
+
85
+ before do
86
+ VCR.insert_cassette 'server', :record => :new_episodes
87
+ end
88
+
89
+ after do
90
+ VCR.eject_cassette
91
+ end
92
+
93
+ it "must have a polled data method" do
94
+ server.must_respond_to :polled_data
95
+ end
96
+
97
+ it "must fetch the current polled data config" do
98
+ server.polled_data.must_be_instance_of Hash
99
+ end
100
+
101
+ it "must have proper results" do
102
+ ["file_type", "file_contents", "server_id", "file_path", "frequency"].each do |key|
103
+ server.polled_data.keys.must_include(key)
104
+ end
105
+ end
106
+
107
+ it "must have a polled data setter" do
108
+ server.must_respond_to :polled_data=
109
+ end
110
+
111
+ it "must upload new configs" do
112
+ skip "API is returning 500 Errors"
113
+ current_data = server.polled_data.clone
114
+ new_data = current_data.clone
115
+ new_data['frequency'] = 600
116
+ server.polled_data = new_data
117
+ server.polled_data(refresh=true)['frequency'].wont_equal current_data['frequency']
118
+ end
119
+
120
+ before do
121
+ server.polled_data
122
+ stub_request(:any, /wwws.appfirst.com/).to_timeout
123
+ end
124
+
125
+ it "must refresh the results if forced" do
126
+ lambda { server.polled_data(refresh=true) }.must_raise Timeout::Error
127
+ end
128
+
129
+ end
130
+
131
+
132
+
133
+ describe "outages" do
134
+
135
+ let(:server) { Rappfirst::Server.new('11743') }
136
+
137
+ before do
138
+ VCR.insert_cassette 'server', :record => :new_episodes
139
+ end
140
+
141
+ after do
142
+ VCR.eject_cassette
143
+ end
144
+
145
+ describe "retrieve outages" do
146
+
147
+ it "must have an outages method" do
148
+ server.must_respond_to :outages
149
+ end
150
+
151
+ it "must retrieve outage data" do
152
+ server.outages.must_be_instance_of Array
153
+ end
154
+
155
+ end
156
+
157
+ describe "cached data" do
158
+
159
+ before do
160
+ server.outages
161
+ stub_request(:any, /wwws.appfirst.com/).to_timeout
162
+ end
163
+
164
+ it "must cache outage data" do
165
+ lambda { server.outages(refresh = true) }.must_raise Timeout::Error
166
+ end
167
+
168
+ end
169
+
170
+ end
171
+
172
+
173
+
174
+ describe "tags" do
175
+
176
+ let(:server) { Rappfirst::Server.new('11743') }
177
+
178
+ before do
179
+ VCR.insert_cassette 'server', :record => :new_episodes
180
+ end
181
+
182
+ after do
183
+ VCR.eject_cassette
184
+ end
185
+
186
+ describe "retrieve tags" do
187
+
188
+ it "must have a tags method" do
189
+ server.must_respond_to :tags
190
+ end
191
+
192
+ it "must retrieve tag data" do
193
+ server.tags.must_be_instance_of Array
194
+ end
195
+
196
+ end
197
+
198
+ describe "cached data" do
199
+
200
+ before do
201
+ server.tags
202
+ stub_request(:any, /wwws.appfirst.com/).to_timeout
203
+ end
204
+
205
+ it "must cache outage data" do
206
+ lambda { server.tags(refresh = true) }.must_raise Timeout::Error
207
+ end
208
+
209
+ end
210
+
211
+ end
212
+
213
+ describe "delete server" do
214
+
215
+ let(:server) { Rappfirst::Server.new('245342') }
216
+
217
+ before do
218
+ VCR.insert_cassette 'server', :record => :new_episodes
219
+ end
220
+
221
+ after do
222
+ VCR.eject_cassette
223
+ end
224
+
225
+ describe "signature" do
226
+
227
+ it "must have a deletion method" do
228
+ server.must_respond_to :delete
229
+ end
230
+
231
+ end
232
+
233
+ describe "failed deletion" do
234
+
235
+ before do
236
+ stub_request(:any, /wwws.appfirst.com/).
237
+ to_return(:status => [500, "Internal Server Error"])
238
+ end
239
+
240
+ it "must delete itself" do
241
+ lambda { server.delete }.must_raise RuntimeError
242
+ end
243
+
244
+ end
245
+
246
+ end
247
+
248
+ end
@@ -0,0 +1,22 @@
1
+ require_relative "../lib/rappfirst"
2
+
3
+ require 'minitest/autorun'
4
+ require "minitest/spec"
5
+ require 'webmock/minitest'
6
+ require 'vcr'
7
+ require 'turn'
8
+
9
+ # Turn Config
10
+ Turn.config do |c|
11
+ # :outline - turn's original case/test outline mode [default]
12
+ c.format = :outline
13
+ # use humanized test names (works only with :outline format)
14
+ c.natural = true
15
+ end
16
+
17
+ # VCR config
18
+ VCR.configure do |c|
19
+ c.cassette_library_dir = 'spec/fixtures/rappfirst_cassettes'
20
+ c.hook_into :webmock
21
+ end
22
+
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rappfirst
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Andrew Gross
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: httparty
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: debugger
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: webmock
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: vcr
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: turn
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rake
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: See Readme.md
111
+ email:
112
+ - andrew.w.gross@gmail.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - .gitignore
118
+ - Gemfile
119
+ - LICENSE.txt
120
+ - README.md
121
+ - Rakefile
122
+ - lib/rappfirst.rb
123
+ - lib/rappfirst/client.rb
124
+ - lib/rappfirst/server.rb
125
+ - lib/rappfirst/version.rb
126
+ - rappfirst.gemspec
127
+ - spec/lib/rappfirst/client_spec.rb
128
+ - spec/lib/rappfirst/server_spec.rb
129
+ - spec/spec_helper.rb
130
+ homepage: https://github.com/andrewgross/rappfirst
131
+ licenses: []
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ none: false
144
+ requirements:
145
+ - - ! '>='
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubyforge_project:
150
+ rubygems_version: 1.8.23
151
+ signing_key:
152
+ specification_version: 3
153
+ summary: Appfirst API v3 Wrapper
154
+ test_files:
155
+ - spec/lib/rappfirst/client_spec.rb
156
+ - spec/lib/rappfirst/server_spec.rb
157
+ - spec/spec_helper.rb