endpoint_stub 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b16dff51967c4be2433bd1743c9fcd182775613e
4
+ data.tar.gz: f2b80f0274615d21885838acf3ce83a7b5e405d9
5
+ SHA512:
6
+ metadata.gz: 3f1f45d0961b3c4ad738d76789b5a49a8ef3170f0f64f390a07f139dd14035c85231d2599d063127941999b17013f4f903e6dcb6a144939285421739d4f04a94
7
+ data.tar.gz: ac4bf26b1026758711958739f75bbdcbb984454e63fdb32f8c1e7cf0829530bb7f4ec10d8aceaa667a73dbbc89d2158f4910fe5fae87a80993cf4670c33dfaa0
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .idea
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.idea/.name ADDED
@@ -0,0 +1 @@
1
+ endpoint_stub
data/.idea/.rakeTasks ADDED
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Settings><!--This file was automatically generated by Ruby plugin.
3
+ You are allowed to:
4
+ 1. Remove rake task
5
+ 2. Add existing rake tasks
6
+ To add existing rake tasks automatically delete this file and reload the project.
7
+ --><RakeGroup description="" fullCmd="" taksId="rake"><RakeTask description="Build endpoint_stub-0.0.1.gem into the pkg directory" fullCmd="build" taksId="build" /><RakeTask description="Build and install endpoint_stub-0.0.1.gem into system gems" fullCmd="install" taksId="install" /><RakeTask description="Create tag v0.0.1 and build and push endpoint_stub-0.0.1.gem to Rubygems" fullCmd="release" taksId="release" /></RakeGroup></Settings>
@@ -0,0 +1,3 @@
1
+ <component name="ProjectDictionaryState">
2
+ <dictionary name="devmini" />
3
+ </component>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
4
+ </project>
5
+
@@ -0,0 +1,42 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="RUBY_MODULE" version="4">
3
+ <component name="CompassSettings">
4
+ <option name="compassSupportEnabled" value="true" />
5
+ </component>
6
+ <component name="FacetManager">
7
+ <facet type="gem" name="Ruby Gem">
8
+ <configuration>
9
+ <option name="GEM_APP_ROOT_PATH" value="$MODULE_DIR$" />
10
+ <option name="GEM_APP_TEST_PATH" value="" />
11
+ <option name="GEM_APP_LIB_PATH" value="$MODULE_DIR$/lib" />
12
+ </configuration>
13
+ </facet>
14
+ </component>
15
+ <component name="NewModuleRootManager">
16
+ <content url="file://$MODULE_DIR$" />
17
+ <orderEntry type="inheritedJdk" />
18
+ <orderEntry type="sourceFolder" forTests="false" />
19
+ <orderEntry type="library" scope="PROVIDED" name="activemodel (v4.0.8, RVM: ruby-2.1.1) [gem]" level="application" />
20
+ <orderEntry type="library" scope="PROVIDED" name="activeresource (v4.0.0, RVM: ruby-2.1.1) [gem]" level="application" />
21
+ <orderEntry type="library" scope="PROVIDED" name="activesupport (v4.0.8, RVM: ruby-2.1.1) [gem]" level="application" />
22
+ <orderEntry type="library" scope="PROVIDED" name="addressable (v2.3.6, RVM: ruby-2.1.1) [gem]" level="application" />
23
+ <orderEntry type="library" scope="PROVIDED" name="builder (v3.1.4, RVM: ruby-2.1.1) [gem]" level="application" />
24
+ <orderEntry type="library" scope="PROVIDED" name="bundler (v1.5.3, RVM: ruby-2.1.1) [gem]" level="application" />
25
+ <orderEntry type="library" scope="PROVIDED" name="crack (v0.4.2, RVM: ruby-2.1.1) [gem]" level="application" />
26
+ <orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.2.5, RVM: ruby-2.1.1) [gem]" level="application" />
27
+ <orderEntry type="library" scope="PROVIDED" name="i18n (v0.6.11, RVM: ruby-2.1.1) [gem]" level="application" />
28
+ <orderEntry type="library" scope="PROVIDED" name="multi_json (v1.10.1, RVM: ruby-2.1.1) [gem]" level="application" />
29
+ <orderEntry type="library" scope="PROVIDED" name="rails-observers (v0.1.2, RVM: ruby-2.1.1) [gem]" level="application" />
30
+ <orderEntry type="library" scope="PROVIDED" name="rake (v10.3.2, RVM: ruby-2.1.1) [gem]" level="application" />
31
+ <orderEntry type="library" scope="PROVIDED" name="rspec (v3.0.0, RVM: ruby-2.1.1) [gem]" level="application" />
32
+ <orderEntry type="library" scope="PROVIDED" name="rspec-core (v3.0.2, RVM: ruby-2.1.1) [gem]" level="application" />
33
+ <orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v3.0.2, RVM: ruby-2.1.1) [gem]" level="application" />
34
+ <orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v3.0.2, RVM: ruby-2.1.1) [gem]" level="application" />
35
+ <orderEntry type="library" scope="PROVIDED" name="rspec-support (v3.0.2, RVM: ruby-2.1.1) [gem]" level="application" />
36
+ <orderEntry type="library" scope="PROVIDED" name="safe_yaml (v1.0.3, RVM: ruby-2.1.1) [gem]" level="application" />
37
+ <orderEntry type="library" scope="PROVIDED" name="thread_safe (v0.3.4, RVM: ruby-2.1.1) [gem]" level="application" />
38
+ <orderEntry type="library" scope="PROVIDED" name="tzinfo (v0.3.40, RVM: ruby-2.1.1) [gem]" level="application" />
39
+ <orderEntry type="library" scope="PROVIDED" name="webmock (v1.18.0, RVM: ruby-2.1.1) [gem]" level="application" />
40
+ </component>
41
+ </module>
42
+
data/.idea/misc.xml ADDED
@@ -0,0 +1,48 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectInspectionProfilesVisibleTreeState">
4
+ <entry key="Project Default">
5
+ <profile-state>
6
+ <expanded-state>
7
+ <State>
8
+ <id />
9
+ </State>
10
+ <State>
11
+ <id>Cucumber</id>
12
+ </State>
13
+ <State>
14
+ <id>Naming ConventionsRuby</id>
15
+ </State>
16
+ <State>
17
+ <id>Rails</id>
18
+ </State>
19
+ <State>
20
+ <id>Ruby</id>
21
+ </State>
22
+ </expanded-state>
23
+ <selected-state>
24
+ <State>
25
+ <id>CoffeeScript</id>
26
+ </State>
27
+ </selected-state>
28
+ </profile-state>
29
+ </entry>
30
+ </component>
31
+ <component name="ProjectRootManager" version="2" project-jdk-name="RVM: ruby-2.1.1" project-jdk-type="RUBY_SDK" />
32
+ <component name="masterDetails">
33
+ <states>
34
+ <state key="ScopeChooserConfigurable.UI">
35
+ <settings>
36
+ <splitter-proportions>
37
+ <option name="proportions">
38
+ <list>
39
+ <option value="0.2" />
40
+ </list>
41
+ </option>
42
+ </splitter-proportions>
43
+ </settings>
44
+ </state>
45
+ </states>
46
+ </component>
47
+ </project>
48
+
data/.idea/modules.xml ADDED
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/endpoint_stub.iml" filepath="$PROJECT_DIR$/.idea/endpoint_stub.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
9
+
@@ -0,0 +1,5 @@
1
+ <component name="DependencyValidationManager">
2
+ <state>
3
+ <option name="SKIP_IMPORT_STATEMENTS" value="false" />
4
+ </state>
5
+ </component>
data/.idea/vcs.xml ADDED
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
7
+
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --warnings
3
+ --require spec_helper
4
+ --format d
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development, :test do
4
+ gem 'rspec', '~> 3.0.0'
5
+ end
6
+
7
+ # Specify your gem's dependencies in endpoint_stub.gemspec
8
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Resonious
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,98 @@
1
+ # EndpointStub
2
+
3
+ I found that testing solutions for ActiveResource were either sub-optimal or
4
+ outdated, so I decided to make my own using WebMock.
5
+
6
+ EndpointStub is kind of like the built in ActiveResource HTTPMock, except
7
+ you can bind logic and dynamic routes to it, so it's like a mini controller
8
+ almost. EndpointStub comes with the default RESTful CRUD actions supported
9
+ by ActiveResource build in (currently JSON format only). It also comes with
10
+ an interface for defining your own routes and logic.
11
+
12
+ Nested resources are currently pending, but definitely implementable via custom
13
+ response mocking.
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ gem 'endpoint_stub'
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install endpoint_stub
28
+
29
+ ## Usage
30
+
31
+ Add
32
+
33
+ require 'endpoint_stub'
34
+
35
+ to your spec_helper / test_helper.
36
+ Then, in your tests, you can call
37
+
38
+ Endpoint::Stub.create_for(MyActiveResourceModel)
39
+
40
+ which will bring MyActiveResourceModel to life!
41
+ Here is a more in-depth example.
42
+
43
+ class Greeter < ActiveResource::Base
44
+ def say_hi_to(someone)
45
+ "#{self.greeting}, someone"
46
+ end
47
+
48
+ self.site = "http://example.com/api/greeter"
49
+ end
50
+
51
+ Endpoint::Stub.create_for(Greeter)
52
+ record = Greeter.create(greeting: 'hello')
53
+ record.say_hi_to('sir') # ===> "hello, sir"
54
+
55
+ record.greeting = 'hey...'
56
+ record.save # ===> true
57
+ record.greeting # ===> 'hey...'
58
+
59
+ record.destroy
60
+ record.destroyed? # ===> true
61
+
62
+ Greeter.create(greeting: 'hi')
63
+ Greeter.create(greeting: 'ahoy')
64
+
65
+ Greeter.all # ===> [{id: 1, greeting: 'hi'}, {id: 2, greeting: 'ahoy'}]
66
+
67
+ Also, custom responses and default attributes:
68
+
69
+ class Test < ActiveResource::Base
70
+ self.site = "http://example.com/api/whatever"
71
+ end
72
+
73
+ Endpoint::Stub.create_for(Test) do
74
+ add_default test_attr: 'nice'
75
+
76
+ mock_response(:put, '/:id/change') do |response, params, stub|
77
+ stub.update_record params[:id], test_attr: '*changed*'
78
+ { body: "did it" }
79
+ end
80
+ end
81
+
82
+ record = Test.create
83
+ record.test_attr # ===> 'nice'
84
+ record.put(:change).body # ===> 'did it'
85
+ record.reload
86
+ record.test_attr # ===> '*changed*'
87
+
88
+ Afterwards, custom responses can be un-mocked:
89
+
90
+ Endpoint::Stub[Test].unmock_response(:put, '/:id/change') # ===> true
91
+
92
+ ## Contributing
93
+
94
+ 1. Fork it ( http://github.com/<my-github-username>/endpoint_stub/fork )
95
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
96
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
97
+ 4. Push to the branch (`git push origin my-new-feature`)
98
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'endpoint_stub/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "endpoint_stub"
8
+ spec.version = EndpointStub::VERSION
9
+ spec.authors = ["Nigel Baillie"]
10
+ spec.email = ["metreckk@gmail.com"]
11
+ spec.summary = %q{Uses WebMock to intercept http requests to perform basic CRUD operations with ActiveResource.}
12
+ spec.description = %q{
13
+ Kind of like the built-in HttpMock that ActiveResource comes with, except EntpointStub
14
+ actually creates and destroys records, and also allows you to bind custom logic to a
15
+ particular path. Kind of like a controller.
16
+ }
17
+ spec.homepage = ""
18
+ spec.license = "MIT"
19
+
20
+ spec.files = `git ls-files -z`.split("\x0")
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_development_dependency "activeresource"
26
+ spec.add_development_dependency "webmock"
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.5"
29
+ spec.add_development_dependency "rake"
30
+ end
@@ -0,0 +1,307 @@
1
+ require 'endpoint_stub'
2
+ require 'webmock'
3
+
4
+ module Endpoint
5
+ ##
6
+ # Represents a stubbed endpoint that creates, updates,
7
+ # destroys, and stores data based on http requests.
8
+ class Stub
9
+ @stubs = {}
10
+ class << self
11
+ attr_reader :stubs
12
+ ##
13
+ # Creates a fake endpoint for the given ActiveResource model.
14
+ #
15
+ # The options hash currently only accepts :defaults, which allows
16
+ # you to define default attribute values for the endpoint to
17
+ # consider on record creation.
18
+ #
19
+ # If a block is supplied, it will be executed in the context
20
+ # of the new Endpoint::Stub, allowing you to elegantly mock
21
+ # custom responses if needed.
22
+ def create_for(model, options={}, &block)
23
+ model = assure_model model
24
+ return if stubs.keys.include? model
25
+ new_stub = Stub.new(model, options)
26
+
27
+ EndpointStub::Config.default_responses.each do |response|
28
+ new_stub.mock_response(*response)
29
+ end
30
+
31
+ @stubs[model] = new_stub
32
+
33
+ new_stub.instance_eval(&block) if block_given?
34
+ new_stub
35
+ end
36
+
37
+ ##
38
+ # Removes fake endpoint for the given model, meaning any
39
+ # ActiveResource activity on the model will raise errors
40
+ # once again.
41
+ def clear_for(model)
42
+ stubs.delete assure_model model
43
+ end
44
+
45
+ def get_for(model)
46
+ @stubs[assure_model(model)]
47
+ end
48
+
49
+ ##
50
+ # Clears all endpoint stubs.
51
+ def clear!
52
+ @stubs = {}
53
+ end
54
+
55
+ ##
56
+ # Gets or creates a stub for the given model.
57
+ # i.e. Endpoint::Stub[Post]
58
+ def [](model)
59
+ create_for model or get_for model
60
+ end
61
+
62
+ private
63
+ def assure_model(model)
64
+ if model.ancestors.include? ActiveResource::Base
65
+ model
66
+ else
67
+ Kernel.const_get model
68
+ end
69
+ end
70
+ end
71
+
72
+ attr_reader :defaults
73
+ attr_reader :model
74
+ attr_accessor :records
75
+ def initialize(model, options)
76
+ @defaults = options[:defaults] || {}
77
+
78
+ @model = model
79
+ @site = URI "#{model.site}/#{model.name.underscore.pluralize}"
80
+
81
+ @responses = {}
82
+
83
+ @records = []
84
+ end
85
+
86
+ ##
87
+ # Adds a record to the stub, automatically assigning an id as though
88
+ # it were in a database.
89
+ def add_record(attrs)
90
+ unless attrs.is_a? Hash
91
+ raise "Endpoint::Stub#add_record expects a Hash. Got #{attrs.class.name}."
92
+ end
93
+ attrs[:id] = current_id
94
+ attrs.merge!(@defaults) { |k,a,b| a }
95
+ @records << attrs
96
+ end
97
+
98
+ ##
99
+ # Updates the record with the given id with the given attributes.
100
+ def update_record(id, attrs)
101
+ unless attrs.is_a? Hash
102
+ raise "Endpoint::Stub#update_record expects a Hash. Got #{attrs.class.name}."
103
+ end
104
+ id = id.to_i
105
+ if @records[id]
106
+ @records[id].merge! attrs
107
+ end
108
+ end
109
+
110
+ ##
111
+ # Removes the record with the given id from the fake database.
112
+ def remove_record(id)
113
+ id = id.to_i
114
+ if @records[id]
115
+ @records[id] = nil
116
+ true
117
+ end
118
+ end
119
+
120
+ def record(id)
121
+ @records[id.to_i]
122
+ end
123
+
124
+ ##
125
+ # The last assigned id.
126
+ def last_id
127
+ @records.count-1
128
+ end
129
+
130
+ ##
131
+ # The next id for a record to be assigned to.
132
+ def current_id
133
+ @records.count
134
+ end
135
+
136
+ ##
137
+ # The name of the represented model in underscore notation.
138
+ def model_name
139
+ @model.name.underscore
140
+ end
141
+
142
+ ##
143
+ # Gets the url location for the given id, as used by RESTful
144
+ # record creation.
145
+ def location(id)
146
+ site = @site.to_s[-1] == '/' ? @site.to_s[0...-1] : @site
147
+ "#{site}/#{id}"
148
+ end
149
+
150
+ ##
151
+ # Adds default attributes for record creation.
152
+ def add_default(attrs)
153
+ @defaults.merge!(attrs)
154
+ end
155
+ alias_method :add_defaults, :add_default
156
+
157
+ ##
158
+ # Mock a custom response. Requires a type (http mthod), and route.
159
+ # This method will override any previous responses assigned to the
160
+ # given type and route.
161
+ #
162
+ # The route is the uri relative to the record's assigned site and
163
+ # can be formatted similarly to rails routes. Such as:
164
+ # '/test/:some_param.json'
165
+ # or
166
+ # '.xml' to simply imply the model's site with '.xml' appended.
167
+ #
168
+ # Lastly, a proc or block is needed to actually handle requests.
169
+ # The proc will be called with the request object, the extracted
170
+ # parameters from the uri, and the stub object so that you can
171
+ # interact with the stubbed records.
172
+ def mock_response(type, route='', proc=nil, &block)
173
+ proc = block if block_given?
174
+
175
+ route = clean_route route
176
+
177
+ site = "#{@site.scheme}://#{@site.host}"
178
+ path = @site.path.split(/\/+/).reject(&:empty?)
179
+ if route[0] == '.' && !route.include?('/')
180
+ # This allows passing '.json', etc as the route
181
+ if path.last
182
+ path = path[0...-1] + [path.last+route]
183
+ else
184
+ site += route
185
+ end
186
+ else
187
+ path += route.split('/')
188
+ end
189
+
190
+ @responses[type] ||= {}
191
+ @responses[type][route] = Response.new(type, URI.parse(site+'/'+path.join('/')), self, &proc)
192
+ @responses[type][route].activate!
193
+ end
194
+
195
+ ##
196
+ # Remove a mocked response with the given type and route.
197
+ def unmock_response(type, route)
198
+ route = clean_route route
199
+ if @responses[type] && @responses[type][route]
200
+ @responses[type][route].deactivate!
201
+ @responses[type][route] = nil
202
+ true
203
+ end
204
+ end
205
+
206
+ private
207
+ def clean_route(route)
208
+ route = route[1..-1] if route[0] == '/'
209
+ route = route[0...-1] if route[-1] == '/'
210
+ route
211
+ end
212
+
213
+ class Response
214
+ include WebMock::API
215
+
216
+ # For remembering where a uri-based parameter is located.
217
+ ParamIndices = Struct.new(:slash, :dot)
218
+ # Allows more comfortable use of Symbol keys when accessing
219
+ # params (which are string keys).
220
+ class Params < Hash
221
+ def [](key)
222
+ super(key.to_s)
223
+ end
224
+ def []=(key, value)
225
+ super(key.to_s, value)
226
+ end
227
+ end
228
+
229
+ def initialize(type, url, stub, &proc)
230
+ @param_indices = {}
231
+
232
+ @url_regex = build_url_regex!(url)
233
+
234
+ @type = type
235
+ @proc = proc
236
+ @stub = stub
237
+ end
238
+
239
+ # Should be called only once, internally to perform the actual WebMock stubbing.
240
+ def activate!
241
+ @stubbed_request = stub_request(@type, @url_regex).to_return do |request|
242
+ params = extract_params(request)
243
+
244
+ results = @proc.call(request, params, @stub)
245
+ results[:body] = results[:body].to_json unless results[:body].is_a? String
246
+ results
247
+ end
248
+ end
249
+
250
+ # This should remove the request stubbed by #activate!
251
+ def deactivate!
252
+ remove_request_stub @stubbed_request
253
+ end
254
+
255
+ private
256
+ # Bang is there because this method populates @param_indices.
257
+ def build_url_regex!(url)
258
+ regex = ""
259
+ separate(url).each_with_index do |x, slash_index|
260
+ regex += '/' unless slash_index == 0
261
+ # If there is a colon, it's a parameter. i.e. /resource/:id.json
262
+ if x.include? ':' and !(x[1..-1] =~ /^\d$/) # If it's just numbers, it's probably a port number
263
+ # We split by dot at this point to separate the parameter from any
264
+ # format/domain related suffix.
265
+ dot_split = x.split('.')
266
+ inner_regex = []
267
+
268
+ dot_split.each_with_index do |name, dot_index|
269
+ # A parameter can show up after a dot as well. i.e. /resource/:id.:format
270
+ inner_regex << if name.include? ':'
271
+ param_name = name[1..-1]
272
+ @param_indices[param_name] = ParamIndices.new(slash_index, dot_index)
273
+ # Add .+ regex to capture any data at this point in the url.
274
+ ".+"
275
+ else
276
+ # If there's no colon, it's a static part of the target url.
277
+ Regexp.escape(name)
278
+ end
279
+ end
280
+
281
+ # "inner_regex" was built by splitting on dots, so we put the dots back.
282
+ regex += inner_regex.join('\.')
283
+ else
284
+ # No colon, so this segment is static.
285
+ regex += Regexp.escape(x)
286
+ end
287
+ end
288
+ Regexp.new regex
289
+ end
290
+
291
+ def extract_params(request)
292
+ url = separate request.uri
293
+ params = Params.new
294
+ @param_indices.each do |param_name, index|
295
+ value = url[index.slash].split('.')[index.dot]
296
+
297
+ params[param_name] = value
298
+ end
299
+ params
300
+ end
301
+
302
+ def separate(url)
303
+ url.to_s[url.to_s.index('://')+3..-1].split '/'
304
+ end
305
+ end
306
+ end
307
+ end
@@ -0,0 +1,3 @@
1
+ module EndpointStub
2
+ VERSION = "1.1.0"
3
+ end
@@ -0,0 +1,87 @@
1
+ require "endpoint_stub/version"
2
+ require 'endpoint/stub'
3
+ require 'webmock'
4
+
5
+ module EndpointStub
6
+ class Config
7
+ class << self
8
+ attr_accessor :activated
9
+ attr_accessor :default_responses
10
+ end
11
+ end
12
+
13
+ # Enable endpoint stubbing.
14
+ # This will cause all HTTP requests to raise an error,
15
+ # as per WebMock, unless relating to an ActiveResource
16
+ # model.
17
+ def self.activate!
18
+ return if Config.activated
19
+ WebMock.enable!
20
+ Config.activated = true
21
+ end
22
+ # Disable endpoint stubbing.
23
+ # This allows real HTTP requests again.
24
+ def self.deactivate!
25
+ return unless Config.activated
26
+ WebMock.disable!
27
+ Config.activated = false
28
+ end
29
+
30
+ # Calls deactivate, clears all stubs, then re-activates.
31
+ def self.refresh!
32
+ deactivate!
33
+ Endpoint::Stub.clear!
34
+ activate!
35
+ end
36
+
37
+ # Feel free to add to these, and they will be applied to every
38
+ # stubbed endpoint thereafter.
39
+ Config.default_responses = [
40
+ ### Index ###
41
+ [:get, '.json', ->(request, params, stub) {
42
+ { body: stub.records }
43
+ }],
44
+
45
+ ### Show ###
46
+ [:get, '/:id.json', ->(request, params, stub) {
47
+ { body: stub.records[params[:id].to_i] }
48
+ }],
49
+
50
+ ### Create ###
51
+ [:post, '.json', ->(request, params, stub) {
52
+ stub.add_record(JSON.parse(request.body))
53
+ { body: '',
54
+ status: 201,
55
+ headers: { 'Location' => stub.location(stub.last_id) }
56
+ }
57
+ }],
58
+
59
+ ### Update ###
60
+ [:put, '/:id.json', ->(request, params, stub) {
61
+ if stub.update_record(params[:id], JSON.parse(request.body))
62
+ { body: '', status: 204}
63
+ else
64
+ { body: "Failed to find #{stub.model_name} with id #{params[:id]}",
65
+ status: 404 }
66
+ end
67
+ }],
68
+
69
+ ### Destroy ###
70
+ [:delete, '/:id.json', ->(request, params, stub) {
71
+ if stub.remove_record(params[:id])
72
+ { body: '', status: 200}
73
+ else
74
+ { body: "Failed to find #{stub.model_name} with id #{params[:id]}",
75
+ status: 404 }
76
+ end
77
+ }]
78
+ ]
79
+ end
80
+
81
+ class Array
82
+ def to_json
83
+ '['+map do |e|
84
+ e.respond_to?(:to_json) ? e.to_json : e.to_s
85
+ end.join(', ')+']'
86
+ end
87
+ end
@@ -0,0 +1,146 @@
1
+ require 'spec_helper'
2
+
3
+ class TestModel < ActiveResource::Base
4
+ self.site = "http://www.not-a-site.com/api"
5
+ end
6
+
7
+ describe Endpoint::Stub, stub_spec: true do
8
+ before(:each) { EndpointStub.refresh! }
9
+
10
+ describe '.stubs' do
11
+ it 'should be a global hash of endpoint stubs, {modle => endpoint_stub}' do
12
+ expect(Endpoint::Stub.stubs).to be_a Hash
13
+ end
14
+ end
15
+
16
+ context 'http requests' do
17
+ it 'should fail when nothing is stubbed' do
18
+ expect{Net::HTTP.get "whocares.com", '/'}.to raise_error WebMock::NetConnectNotAllowedError
19
+ end
20
+ end
21
+
22
+ describe '.create_for' do
23
+ it 'should create a new Endpoint::Stub for the given ActiveResource model' do
24
+ Endpoint::Stub.create_for TestModel
25
+ expect(Endpoint::Stub.stubs.keys).to include TestModel
26
+ end
27
+
28
+ it 'should be able to set default attributes' do
29
+ Endpoint::Stub.create_for(TestModel, {defaults: { test_attr: 'hey' }})
30
+ expect(Endpoint::Stub[TestModel].defaults.keys).to include :test_attr
31
+ end
32
+ end
33
+
34
+ describe '.clear_for' do
35
+ it 'should remove the Endpoint::Stub entry for the given ActiveResource model' do
36
+ Endpoint::Stub.create_for TestModel
37
+ Endpoint::Stub.clear_for TestModel
38
+ expect(Endpoint::Stub.stubs.keys).to_not include TestModel
39
+ end
40
+ end
41
+
42
+
43
+ context 'With a stubbed model' do
44
+ let!(:test_model_stub) { Endpoint::Stub.create_for(TestModel) }
45
+ after(:each) do
46
+ Endpoint::Stub.clear_for TestModel
47
+ end
48
+
49
+ describe '.find' do
50
+ it 'retrieves the model' do
51
+ test_model_stub.records << { id: 0, test_attr: 'hey' }
52
+ test_model_stub.records << { id: 1, test_attr: 'nice!' }
53
+
54
+ subject = TestModel.find 1
55
+ expect(subject.test_attr).to eq 'nice!'
56
+ end
57
+ end
58
+
59
+ describe '.all' do
60
+ it 'retrieves all of the models' do
61
+ test_model_stub.records << { id: 0, test_attr: 'first!' }
62
+ test_model_stub.records << { id: 1, test_attr: 'even better' }
63
+
64
+ subjects = TestModel.all
65
+ expect(subjects.count).to eq 2
66
+ expect(subjects.first.test_attr).to eq 'first!'
67
+ expect(subjects.last.test_attr).to eq 'even better'
68
+ end
69
+ end
70
+
71
+ describe 'setting record attributes' do
72
+ it 'should work' do
73
+ subject = TestModel.new
74
+ subject.test_attr = "heyyyyyyy"
75
+ subject.save
76
+ expect(subject.test_attr).to eq "heyyyyyyy"
77
+ end
78
+ end
79
+
80
+ describe 'creating a new record' do
81
+ it 'should work' do
82
+ subject = TestModel.new
83
+ subject.test_attr = "alright...."
84
+ subject.save
85
+ expect(subject.test_attr).to eq 'alright....'
86
+ end
87
+
88
+ it 'should work with .create method' do
89
+ subject = TestModel.create(test_attr: 'wow')
90
+ expect(subject.id).to eq '0'
91
+ expect(subject.test_attr).to eq 'wow'
92
+ end
93
+ end
94
+
95
+ describe 'destroying a record' do
96
+ it 'should work' do
97
+ test_model_stub.records << { id: 0, test_attr: 'first!' }
98
+ test_model_stub.records << { id: 1, test_attr: 'even better' }
99
+
100
+ TestModel.find(0).destroy
101
+
102
+ expect{TestModel.find(0)}.to raise_error
103
+ end
104
+ end
105
+
106
+ describe 'the mocked responses' do
107
+ it 'should be removable' do
108
+ test_model_stub.records << { id: 0, test_attr: 'hey' }
109
+
110
+ expect{TestModel.find(0).test_attr}.to_not raise_error
111
+
112
+ expect(
113
+ test_model_stub.unmock_response(:get, '/:id.json')
114
+ ).to be_truthy
115
+
116
+ expect{TestModel.find(0).test_attr}.to raise_error
117
+ end
118
+ end
119
+
120
+ describe 'custom response' do
121
+ it 'should be addable to existing stubs' do
122
+ test_model_stub.records << { id: 0, test_attr: 'hey' }
123
+
124
+ test_model_stub.mock_response(:put, '/:id/change') do |response, params, stub|
125
+ stub.update_record params[:id], test_attr: '*changed*'
126
+ { body: "did it" }
127
+ end
128
+
129
+ subject = TestModel.find(0)
130
+ expect(subject.test_attr).to_not eq '*changed*'
131
+ expect(subject.put(:change).body).to eq 'did it'
132
+ subject.reload
133
+ expect(subject.test_attr).to eq '*changed*'
134
+ end
135
+
136
+ it 'should be removable' do
137
+ test_model_stub.mock_response(:put, '/test') do |r,p,s|
138
+ { body: 'test' }
139
+ end
140
+ expect{TestModel.put(:test)}.to_not raise_error
141
+ test_model_stub.unmock_response(:put, '/test')
142
+ expect{TestModel.put(:test)}.to raise_error
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe EndpointStub, endpoint_stub_spec: true do
4
+ describe '.activate!' do
5
+ it 'should make http requests raise WebMock::NetConnectNotAllowedError' do
6
+ EndpointStub.activate!
7
+ expect{Net::HTTP.get "test.com", "/nothing"}.to raise_error WebMock::NetConnectNotAllowedError
8
+ end
9
+ end
10
+
11
+ describe '.deactivate!' do
12
+ it 'should allow http requests once again' do
13
+ # TODO make this account for not being connected to the internet.
14
+ EndpointStub.activate!
15
+ EndpointStub.deactivate!
16
+ expect{Net::HTTP.get "example.com", "/foo"}.to_not raise_error
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,67 @@
1
+ require 'endpoint_stub'
2
+ require 'endpoint/stub'
3
+ require 'net/http'
4
+ require 'active_resource'
5
+
6
+ RSpec.configure do |config|
7
+ # The settings below are suggested to provide a good initial experience
8
+ # with RSpec, but feel free to customize to your heart's content.
9
+ =begin
10
+ # These two settings work together to allow you to limit a spec run
11
+ # to individual examples or groups you care about by tagging them with
12
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
13
+ # get run.
14
+ config.filter_run :focus
15
+ config.run_all_when_everything_filtered = true
16
+
17
+ # Many RSpec users commonly either run the entire suite or an individual
18
+ # file, and it's useful to allow more verbose output when running an
19
+ # individual spec file.
20
+ if config.files_to_run.one?
21
+ # Use the documentation formatter for detailed output,
22
+ # unless a formatter has already been configured
23
+ # (e.g. via a command-line flag).
24
+ config.default_formatter = 'doc'
25
+ end
26
+
27
+ # Print the 10 slowest examples and example groups at the
28
+ # end of the spec run, to help surface which specs are running
29
+ # particularly slow.
30
+ config.profile_examples = 10
31
+
32
+ # Run specs in random order to surface order dependencies. If you find an
33
+ # order dependency and want to debug it, you can fix the order by providing
34
+ # the seed, which is printed after each run.
35
+ # --seed 1234
36
+ config.order = :random
37
+
38
+ # Seed global randomization in this process using the `--seed` CLI option.
39
+ # Setting this allows you to use `--seed` to deterministically reproduce
40
+ # test failures related to randomization by passing the same `--seed` value
41
+ # as the one that triggered the failure.
42
+ Kernel.srand config.seed
43
+
44
+ # rspec-expectations config goes here. You can use an alternate
45
+ # assertion/expectation library such as wrong or the stdlib/minitest
46
+ # assertions if you prefer.
47
+ config.expect_with :rspec do |expectations|
48
+ # Enable only the newer, non-monkey-patching expect syntax.
49
+ # For more details, see:
50
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
51
+ expectations.syntax = :expect
52
+ end
53
+
54
+ # rspec-mocks config goes here. You can use an alternate test double
55
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
56
+ config.mock_with :rspec do |mocks|
57
+ # Enable only the newer, non-monkey-patching expect syntax.
58
+ # For more details, see:
59
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
60
+ mocks.syntax = :expect
61
+
62
+ # Prevents you from mocking or stubbing a method that does not exist on
63
+ # a real object. This is generally recommended.
64
+ mocks.verify_partial_doubles = true
65
+ end
66
+ =end
67
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: endpoint_stub
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nigel Baillie
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-07-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activeresource
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: webmock
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: "\n Kind of like the built-in HttpMock that ActiveResource comes with,
70
+ except EntpointStub\n actually creates and destroys records, and also allows
71
+ you to bind custom logic to a\n particular path. Kind of like a controller.\n
72
+ \ "
73
+ email:
74
+ - metreckk@gmail.com
75
+ executables: []
76
+ extensions: []
77
+ extra_rdoc_files: []
78
+ files:
79
+ - ".gitignore"
80
+ - ".idea/.name"
81
+ - ".idea/.rakeTasks"
82
+ - ".idea/dictionaries/devmini.xml"
83
+ - ".idea/encodings.xml"
84
+ - ".idea/endpoint_stub.iml"
85
+ - ".idea/misc.xml"
86
+ - ".idea/modules.xml"
87
+ - ".idea/scopes/scope_settings.xml"
88
+ - ".idea/vcs.xml"
89
+ - ".rspec"
90
+ - Gemfile
91
+ - LICENSE.txt
92
+ - README.md
93
+ - Rakefile
94
+ - endpoint_stub.gemspec
95
+ - lib/endpoint/stub.rb
96
+ - lib/endpoint_stub.rb
97
+ - lib/endpoint_stub/version.rb
98
+ - spec/endpoint/stub_spec.rb
99
+ - spec/endpoint_stub_spec.rb
100
+ - spec/spec_helper.rb
101
+ homepage: ''
102
+ licenses:
103
+ - MIT
104
+ metadata: {}
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 2.2.2
122
+ signing_key:
123
+ specification_version: 4
124
+ summary: Uses WebMock to intercept http requests to perform basic CRUD operations
125
+ with ActiveResource.
126
+ test_files:
127
+ - spec/endpoint/stub_spec.rb
128
+ - spec/endpoint_stub_spec.rb
129
+ - spec/spec_helper.rb