webvalve 1.3.1 → 2.0.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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +37 -0
- data/lib/webvalve/manager.rb +2 -1
- data/lib/webvalve/service_url_converter.rb +24 -0
- data/lib/webvalve/version.rb +1 -1
- data/spec/webvalve/manager_spec.rb +18 -8
- data/spec/webvalve/service_url_converter_spec.rb +194 -0
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e03b34e13263947acd26c73387bc7f1dcf6f8ae5749463fc83187fd43d75dc98
|
4
|
+
data.tar.gz: 85994585f1c19f5f63ea966fdc7c919e5e870e650c980fbea10accb30fb568ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '02801b418f46db96e7d6ef9cfad07c39a29054cd2a7357cfbb39029838a2cbf733f1e1a183590e731a6ef734fa6061de2a265eca761cffdccd3882d1b24ecb7e'
|
7
|
+
data.tar.gz: a100512db4c9e940a7853d168b7d0269db9e688048df3be24b1ccd31e4b881f64a3a14ec4da83bb1c9192ccd4867fe660ff30ebe2a8659f5b3ed59cc5d325d2d
|
data/CHANGELOG.md
CHANGED
@@ -9,6 +9,10 @@ and this project aims to adhere to [Semantic Versioning](http://semver.org/spec/
|
|
9
9
|
### Added
|
10
10
|
### Removed
|
11
11
|
|
12
|
+
## [2.0.0] - 2023-07-20
|
13
|
+
### Added
|
14
|
+
- Dynamic URL support via wildcards, Regexps, and Addressable::Templates
|
15
|
+
|
12
16
|
## [1.3.1] - 2023-07-20
|
13
17
|
### Changed
|
14
18
|
- Replace usage of deprecated `File.exists?` in generator
|
data/README.md
CHANGED
@@ -218,6 +218,43 @@ WebValve.register FakeBank, url: ENV.fetch("SOME_CUSTOM_API_URL")
|
|
218
218
|
WebValve.register FakeBank, url: "https://some-service.com"
|
219
219
|
```
|
220
220
|
|
221
|
+
## Dynamic URLs
|
222
|
+
|
223
|
+
If the service you are interacting with contains dynamic elements, e.g.
|
224
|
+
an instance-specific subdomain, you can specify a wildcard in your url
|
225
|
+
with the `*` character to match a series of zero or more characters
|
226
|
+
within the same URL segment. For example:
|
227
|
+
|
228
|
+
```bash
|
229
|
+
export BANK_API_URL=https://*.mybank.com/
|
230
|
+
```
|
231
|
+
|
232
|
+
or
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
WebValve.register FakeBank, url: "https://*.mybank.com"
|
236
|
+
```
|
237
|
+
|
238
|
+
Note: unlike filesystem globbing, `?` isn't respected to mean "exactly
|
239
|
+
one character" because it's a URL delimiter character. Only `*` works
|
240
|
+
for WebValve URL wildcards.
|
241
|
+
|
242
|
+
Alternatively you can use `Addressable::Template`s or `Regexp`s to
|
243
|
+
specify dynamic URLs if they for some reason aren't a good fit for the
|
244
|
+
wildcard syntax. Note that there is no `ENV` var support for these
|
245
|
+
formats because there is no detection logic to determine a URL string is
|
246
|
+
actually meant to represent a URL template or regexp. For example:
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
WebValve.register FakeBank, url: Addressable::Template.new("http://mybank.com{/path*}{?query}")
|
250
|
+
```
|
251
|
+
|
252
|
+
or
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
WebValve.register FakeBank, url: %r{\Ahttp://mybank.com(/.*)?\z}
|
256
|
+
```
|
257
|
+
|
221
258
|
## What's in a `FakeService`?
|
222
259
|
|
223
260
|
The definition of `FakeService` is really simple. It's just a
|
data/lib/webvalve/manager.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'webmock'
|
2
2
|
require 'singleton'
|
3
3
|
require 'set'
|
4
|
+
require 'webvalve/service_url_converter'
|
4
5
|
|
5
6
|
module WebValve
|
6
7
|
ALWAYS_ENABLED_ENVS = %w(development test).freeze
|
@@ -148,7 +149,7 @@ module WebValve
|
|
148
149
|
end
|
149
150
|
|
150
151
|
def url_to_regexp(url)
|
151
|
-
|
152
|
+
ServiceUrlConverter.new(url: url).regexp
|
152
153
|
end
|
153
154
|
|
154
155
|
def ensure_non_duplicate_stub(config)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module WebValve
|
2
|
+
class ServiceUrlConverter
|
3
|
+
TOKEN_BOUNDARY_CHARS = Regexp.escape('.:/?#@&=').freeze
|
4
|
+
WILDCARD_SUBSTITUTION = ('[^' + TOKEN_BOUNDARY_CHARS + ']*').freeze
|
5
|
+
URL_PREFIX_BOUNDARY = ('[' + TOKEN_BOUNDARY_CHARS + ']').freeze
|
6
|
+
URL_SUFFIX_PATTERN = ('((' + URL_PREFIX_BOUNDARY + '|(?<=' + URL_PREFIX_BOUNDARY + ')).*)?\z').freeze
|
7
|
+
|
8
|
+
attr_reader :url
|
9
|
+
|
10
|
+
def initialize(url:)
|
11
|
+
@url = url
|
12
|
+
end
|
13
|
+
|
14
|
+
def regexp
|
15
|
+
if url.is_a?(String)
|
16
|
+
regexp_string = Regexp.escape(url)
|
17
|
+
substituted_regexp_string = regexp_string.gsub('\*', WILDCARD_SUBSTITUTION)
|
18
|
+
%r(\A#{substituted_regexp_string}#{URL_SUFFIX_PATTERN})
|
19
|
+
else
|
20
|
+
url
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/webvalve/version.rb
CHANGED
@@ -38,6 +38,9 @@ RSpec.describe WebValve::Manager do
|
|
38
38
|
end
|
39
39
|
|
40
40
|
describe '#setup' do
|
41
|
+
let(:wildcard_substitution) { WebValve::ServiceUrlConverter::WILDCARD_SUBSTITUTION }
|
42
|
+
let(:url_suffix_pattern) { WebValve::ServiceUrlConverter::URL_SUFFIX_PATTERN }
|
43
|
+
|
41
44
|
context 'when WebValve is disabled' do
|
42
45
|
around do |ex|
|
43
46
|
with_rails_env 'production' do
|
@@ -94,10 +97,17 @@ RSpec.describe WebValve::Manager do
|
|
94
97
|
|
95
98
|
it 'allowlists configured urls in webmock' do
|
96
99
|
allow(WebMock).to receive(:disable_net_connect!)
|
97
|
-
results = [
|
100
|
+
results = [
|
101
|
+
%r{\Ahttp://foo\.dev#{url_suffix_pattern}},
|
102
|
+
%r{\Ahttp://bar\.dev#{url_suffix_pattern}},
|
103
|
+
%r{\Ahttp://bar\.#{wildcard_substitution}\.dev#{url_suffix_pattern}},
|
104
|
+
%r{\Ahttp://bar\.dev/\?foo=bar#{url_suffix_pattern}}
|
105
|
+
]
|
98
106
|
|
99
107
|
subject.allow_url 'http://foo.dev'
|
100
108
|
subject.allow_url 'http://bar.dev'
|
109
|
+
subject.allow_url 'http://bar.*.dev'
|
110
|
+
subject.allow_url 'http://bar.dev/?foo=bar'
|
101
111
|
|
102
112
|
subject.setup
|
103
113
|
|
@@ -115,7 +125,7 @@ RSpec.describe WebValve::Manager do
|
|
115
125
|
subject.setup
|
116
126
|
end
|
117
127
|
|
118
|
-
expect(WebMock).to have_received(:stub_request).with(:any, %r{\Ahttp://something\.dev})
|
128
|
+
expect(WebMock).to have_received(:stub_request).with(:any, %r{\Ahttp://something\.dev#{url_suffix_pattern}})
|
119
129
|
expect(web_mock_stubble).to have_received(:to_rack)
|
120
130
|
end
|
121
131
|
|
@@ -153,7 +163,7 @@ RSpec.describe WebValve::Manager do
|
|
153
163
|
subject.register other_disabled_service.name
|
154
164
|
|
155
165
|
expect { subject.setup }.to_not raise_error
|
156
|
-
expect(WebMock).to have_received(:stub_request).with(:any, %r{\Ahttp://something\.dev}).twice
|
166
|
+
expect(WebMock).to have_received(:stub_request).with(:any, %r{\Ahttp://something\.dev#{url_suffix_pattern}}).twice
|
157
167
|
end
|
158
168
|
end
|
159
169
|
end
|
@@ -208,8 +218,8 @@ RSpec.describe WebValve::Manager do
|
|
208
218
|
subject.setup
|
209
219
|
end
|
210
220
|
|
211
|
-
expect(WebMock).to have_received(:stub_request).with(:any, %r{\Ahttp://something\.dev})
|
212
|
-
expect(WebMock).not_to have_received(:stub_request).with(:any, %r{\Ahttp://other\.dev})
|
221
|
+
expect(WebMock).to have_received(:stub_request).with(:any, %r{\Ahttp://something\.dev#{url_suffix_pattern}})
|
222
|
+
expect(WebMock).not_to have_received(:stub_request).with(:any, %r{\Ahttp://other\.dev#{url_suffix_pattern}})
|
213
223
|
expect(web_mock_stubble).to have_received(:to_rack).once
|
214
224
|
end
|
215
225
|
|
@@ -261,9 +271,9 @@ RSpec.describe WebValve::Manager do
|
|
261
271
|
subject.setup
|
262
272
|
end
|
263
273
|
|
264
|
-
expect(WebMock).to have_received(:stub_request).with(:any, %r{\Ahttp://something\.dev})
|
265
|
-
expect(WebMock).to have_received(:stub_request).with(:any, %r{\Ahttp://something\-else\.dev})
|
266
|
-
expect(WebMock).to have_received(:stub_request).with(:any, %r{\Ahttp://other\.dev})
|
274
|
+
expect(WebMock).to have_received(:stub_request).with(:any, %r{\Ahttp://something\.dev#{url_suffix_pattern}})
|
275
|
+
expect(WebMock).to have_received(:stub_request).with(:any, %r{\Ahttp://something\-else\.dev#{url_suffix_pattern}})
|
276
|
+
expect(WebMock).to have_received(:stub_request).with(:any, %r{\Ahttp://other\.dev#{url_suffix_pattern}})
|
267
277
|
expect(web_mock_stubble).to have_received(:to_rack).exactly(3).times
|
268
278
|
end
|
269
279
|
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe WebValve::ServiceUrlConverter do
|
4
|
+
let(:url) { "http://bar.com" }
|
5
|
+
|
6
|
+
subject { described_class.new(url: url) }
|
7
|
+
|
8
|
+
describe '#regexp' do
|
9
|
+
it "returns a regexp" do
|
10
|
+
expect(subject.regexp).to be_a(Regexp)
|
11
|
+
end
|
12
|
+
|
13
|
+
context "with a regexp" do
|
14
|
+
let(:url) { %r{\Ahttp://foo\.com} }
|
15
|
+
|
16
|
+
it "returns the same object" do
|
17
|
+
expect(subject.regexp).to be_a(Regexp)
|
18
|
+
expect(subject.regexp).to equal(url)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "with an empty url" do
|
23
|
+
let(:url) { "" }
|
24
|
+
|
25
|
+
it "matches empty string" do
|
26
|
+
expect("").to match(subject.regexp)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "matches a string starting with a URL delimiter because the rest is just interpreted as suffix" do
|
30
|
+
expect(":do:do:dodo:do:do").to match(subject.regexp)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "doesn't match a string that doesn't start with a delimiter" do
|
34
|
+
expect("jamietart:do:do:dodo:do:do").not_to match(subject.regexp)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "with a boundary char on the end" do
|
39
|
+
let(:url) { "http://bar.com/" }
|
40
|
+
|
41
|
+
it "matches arbitrary suffixes" do
|
42
|
+
expect("http://bar.com/baz/bump/beep").to match(subject.regexp)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "with multiple asterisks" do
|
47
|
+
let(:url) { "http://bar.com/**/bump" }
|
48
|
+
|
49
|
+
it "matches like a single asterisk" do
|
50
|
+
expect("http://bar.com/foo/bump").to match(subject.regexp)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "doesn't match like a filesystem glob" do
|
54
|
+
expect("http://bar.com/foo/bar/bump").not_to match(subject.regexp)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "with a trailing *" do
|
59
|
+
let(:url) { "http://bar.com/*" }
|
60
|
+
|
61
|
+
it "matches when empty" do
|
62
|
+
expect("http://bar.com/").to match(subject.regexp)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "matches when existing" do
|
66
|
+
expect("http://bar.com/foobaloo").to match(subject.regexp)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "matches with additional tokens" do
|
70
|
+
expect("http://bar.com/foobaloo/wink").to match(subject.regexp)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "doesn't match when missing the trailing slash tho" do
|
74
|
+
expect("http://bar.com").not_to match(subject.regexp)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "with a totally wildcarded protocol" do
|
79
|
+
let(:url) { "*://bar.com" }
|
80
|
+
|
81
|
+
it "matches http" do
|
82
|
+
expect("http://bar.com/").to match(subject.regexp)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "matches anything else" do
|
86
|
+
expect("gopher://bar.com/").to match(subject.regexp)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "matches empty" do
|
90
|
+
expect("://bar.com").to match(subject.regexp)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "with a wildcarded partial protocol" do
|
95
|
+
let(:url) { "http*://bar.com" }
|
96
|
+
|
97
|
+
it "matches empty" do
|
98
|
+
expect("http://bar.com/").to match(subject.regexp)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "matches full" do
|
102
|
+
expect("https://bar.com/").to match(subject.regexp)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "with a TLD that is a substring of another TLD" do
|
107
|
+
let(:url) { "http://bar.co" }
|
108
|
+
|
109
|
+
it "doesn't match a different TLD when extending" do
|
110
|
+
expect("http://bar.com").not_to match(subject.regexp)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "with a wildcard subdomain" do
|
115
|
+
let(:url) { "http://*.bar.com" }
|
116
|
+
|
117
|
+
it "matches" do
|
118
|
+
expect("http://foo.bar.com").to match(subject.regexp)
|
119
|
+
end
|
120
|
+
|
121
|
+
it "doesn't match when too many subdomains" do
|
122
|
+
expect("http://beep.foo.bar.com").not_to match(subject.regexp)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context "with a partial postfix wildcard subdomain" do
|
127
|
+
let(:url) { "http://foo*.bar.com" }
|
128
|
+
|
129
|
+
it "matches when present" do
|
130
|
+
expect("http://foobaz.bar.com").to match(subject.regexp)
|
131
|
+
end
|
132
|
+
|
133
|
+
it "matches when empty" do
|
134
|
+
expect("http://foo.bar.com").to match(subject.regexp)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "doesn't match when out of order" do
|
138
|
+
expect("http://bazfoo.bar.com").not_to match(subject.regexp)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context "with a partial prefix wildcard subdomain" do
|
143
|
+
let(:url) { "http://*baz.bar.com" }
|
144
|
+
|
145
|
+
it "matches when present" do
|
146
|
+
expect("http://foobaz.bar.com").to match(subject.regexp)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "matches when empty" do
|
150
|
+
expect("http://baz.bar.com").to match(subject.regexp)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context "with a wildcarded basic auth url" do
|
155
|
+
let(:url) { "http://*:*@bar.com" }
|
156
|
+
|
157
|
+
it "matches when present" do
|
158
|
+
expect("http://bilbo:baggins@bar.com").to match(subject.regexp)
|
159
|
+
end
|
160
|
+
|
161
|
+
it "doesn't match when malformed" do
|
162
|
+
expect("http://bilbobaggins@bar.com").not_to match(subject.regexp)
|
163
|
+
end
|
164
|
+
|
165
|
+
it "doesn't match when missing password part" do
|
166
|
+
expect("http://bilbo@bar.com").not_to match(subject.regexp)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context "with a wildcarded path" do
|
171
|
+
let(:url) { "http://bar.com/*/whatever" }
|
172
|
+
|
173
|
+
it "matches with arbitrarily spicy but legal, non-URL-significant characters" do
|
174
|
+
expect("http://bar.com/a0-_~[]!$'(),;%+/whatever").to match(subject.regexp)
|
175
|
+
end
|
176
|
+
|
177
|
+
it "doesn't match when you throw a URL-significant char in there" do
|
178
|
+
expect("http://bar.com/life=love/whatever").not_to match(subject.regexp)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context "with a wildcarded query param" do
|
183
|
+
let(:url) { "http://bar.com/whatever?foo=*&bar=bump" }
|
184
|
+
|
185
|
+
it "matches when present" do
|
186
|
+
expect("http://bar.com/whatever?foo=baz&bar=bump").to match(subject.regexp)
|
187
|
+
end
|
188
|
+
|
189
|
+
it "doesn't match when you throw a URL-significant char in there" do
|
190
|
+
expect("http://bar.com/whatever?foo=baz#&bar=bump").not_to match(subject.regexp)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webvalve
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Moore
|
@@ -173,6 +173,7 @@ files:
|
|
173
173
|
- lib/webvalve/monkey_patches.rb
|
174
174
|
- lib/webvalve/railtie.rb
|
175
175
|
- lib/webvalve/rspec.rb
|
176
|
+
- lib/webvalve/service_url_converter.rb
|
176
177
|
- lib/webvalve/version.rb
|
177
178
|
- spec/dummy/config/application.rb
|
178
179
|
- spec/examples.txt
|
@@ -182,13 +183,28 @@ files:
|
|
182
183
|
- spec/webvalve/fake_service_config_spec.rb
|
183
184
|
- spec/webvalve/fake_service_spec.rb
|
184
185
|
- spec/webvalve/manager_spec.rb
|
186
|
+
- spec/webvalve/service_url_converter_spec.rb
|
185
187
|
- spec/webvalve_spec.rb
|
186
188
|
homepage: https://github.com/Betterment/webvalve
|
187
189
|
licenses:
|
188
190
|
- MIT
|
189
191
|
metadata:
|
190
192
|
rubygems_mfa_required: 'true'
|
191
|
-
post_install_message:
|
193
|
+
post_install_message: |
|
194
|
+
Thanks for installing WebValve!
|
195
|
+
|
196
|
+
Note for upgraders: If you're upgrading from a version less than 2.0, service
|
197
|
+
URL behavior has changed. Please verify that your app isn't relying on the
|
198
|
+
previous behavior:
|
199
|
+
|
200
|
+
1. `*` characters are now interpreted as wildcards, enabling dynamic URL
|
201
|
+
segments. In the unlikely event that your URLs use `*` literals, you'll need
|
202
|
+
to URL encode them (`%2A`) both in your URL spec and at runtime.
|
203
|
+
|
204
|
+
2. URL suffix matching is now strict. For example, `BAR_URL=http://bar.co` will
|
205
|
+
no longer match `https://bar.com`, but it will match `http://bar.co/foo`. If
|
206
|
+
you need to preserve the previous behavior, you can add a trailing `*` to
|
207
|
+
your URL spec, e.g. `BAR_URL=http://bar.co*`.
|
192
208
|
rdoc_options: []
|
193
209
|
require_paths:
|
194
210
|
- lib
|
@@ -216,4 +232,5 @@ test_files:
|
|
216
232
|
- spec/webvalve/fake_service_config_spec.rb
|
217
233
|
- spec/webvalve/fake_service_spec.rb
|
218
234
|
- spec/webvalve/manager_spec.rb
|
235
|
+
- spec/webvalve/service_url_converter_spec.rb
|
219
236
|
- spec/webvalve_spec.rb
|