endpoint_stub 1.1.0

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.
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