Warning: I don't know a ton of ruby, and most of this is generated using Claude. However, I ran it past a backend dev at the office and he agreed with the assessment.
Summary
The README documents three URL forms for WebValve.register:
- String +
* wildcards
Regexp — WebValve.register FakeBank, url: %r{\Ahttp://mybank.com(/.*)?\z}
Addressable::Template — WebValve.register FakeBank, url: Addressable::Template.new("http://mybank.com{/path*}{?query}")
In practice only (1) works. For Regexp and Addressable::Template, the registered fake's WebMock stub never matches a real request — the fake is silently inert. FakeServiceConfig#path_prefix also returns garbage ("(") for Regexp and raises URI::InvalidURIError for Addressable::Template, so any request that did match would crash inside FakeServiceWrapper#call.
Tested against webvalve 2.2.0 on Ruby 3.4.
Root cause
FakeServiceConfig#service_url calls strip_basic_auth(full_url), which does url.to_s.sub(...). That stringifies the URL before ServiceUrlConverter#regexp ever sees it, so the else url branch (which handles non-String URLs) is unreachable from Manager#webmock_service:
# lib/webvalve/fake_service_config.rb
def service_url
@service_url ||= strip_basic_auth(full_url) # url.to_s under the hood
end
def path_prefix
@path_prefix ||= URI.parse(service_url).path # parses the stringified URL
end
# lib/webvalve/manager.rb
def webmock_service(config)
WebMock.stub_request(:any, url_to_regexp(config.service_url)).to_rack(...)
# ^ String, even when input was Regexp/Template
end
def url_to_regexp(url)
ServiceUrlConverter.new(url: url).regexp
end
# lib/webvalve/service_url_converter.rb
def regexp
if url.is_a?(String)
regexp_string = Regexp.escape(url)
%r(\A#{regexp_string.gsub('\*', WILDCARD_SUBSTITUTION)}#{URL_SUFFIX_PATTERN})
else
url # never reached — url is always a String here
end
end
For a Regexp, to_s produces "(?-mix:...)", then Regexp.escape is applied to those literal characters — the resulting WebMock stub matches only URLs containing the literal string (?-mix:\Ahttp://...), i.e. nothing.
For an Addressable::Template, to_s returns the inspect string ("#<Addressable::Template:0x...>"), and URI.parse raises on it.
Why the existing test suite doesn't catch this
spec/webvalve/manager_spec.rb exercises Regexp URLs via expect(WebMock).to have_received(:stub_request).with(:any, %r{...}), which never invokes the service_url → stringification path. There's no integration spec that actually services a request through FakeServiceWrapper#call with a Regexp or Addressable::Template URL — so neither the matching bug nor the path_prefix crash fires under the test suite.
Reproduction
Save and run with ruby webvalve_repro.rb:
#!/usr/bin/env ruby
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "webvalve", "2.2.0"
gem "addressable"
end
require "webvalve"
require "addressable/template"
class FakeStub < WebValve::FakeService; end
def report(label, url, expected_match_url)
config = WebValve::FakeServiceConfig.new(service_class_name: "FakeStub", url: url)
service_url = config.service_url rescue "RAISED #{$!.class}: #{$!.message[0..80]}"
path_prefix = config.path_prefix rescue "RAISED #{$!.class}: #{$!.message[0..80]}"
webmock_pattern =
begin
WebValve::ServiceUrlConverter.new(url: service_url).regexp
rescue => e
"RAISED #{e.class}: #{e.message[0..80]}"
end
matches =
if webmock_pattern.is_a?(Regexp)
!!(expected_match_url =~ webmock_pattern)
else
"n/a"
end
puts "=" * 78
puts label
puts " registered url: #{url.inspect[0..120]}"
puts " url class: #{config.full_url.class}"
puts " service_url: #{service_url.inspect[0..120]}"
puts " path_prefix: #{path_prefix.inspect[0..120]}"
puts " WebMock stub pattern: #{webmock_pattern.inspect[0..120]}"
puts " matches #{expected_match_url.inspect}? #{matches}"
puts
end
report(
"1. STRING URL with wildcard (baseline)",
"https://*.example.com",
"https://api.example.com/v1/things"
)
report(
"2. REGEXP URL",
%r{\Ahttps://example\.com(/.*)?\z},
"https://example.com/v1/things"
)
report(
"3. ADDRESSABLE::TEMPLATE URL",
Addressable::Template.new("https://example.com{/path*}"),
"https://example.com/v1/things"
)
Output
==============================================================================
1. STRING URL with wildcard (baseline)
registered url: "https://*.example.com"
url class: String
service_url: "https://*.example.com"
path_prefix: ""
WebMock stub pattern: /\Ahttps:\/\/[^\.:\/\?\#@&=]*\.example\.com(([\.:\/\?\#@&=]|(?<=[\.:\/\?\#@&=])).*)?\z/
matches "https://api.example.com/v1/things"? true
==============================================================================
2. REGEXP URL
registered url: /\Ahttps:\/\/example\.com(\/.*)?\z/
url class: Regexp
service_url: "(?-mix:\\Ahttps:\\/\\/example\\.com(\\/.*)?\\z)"
path_prefix: "("
WebMock stub pattern: /\A\(\?\-mix:\\Ahttps:\\\/\\\/example\\\.com\(\\\/\.[^\.:\/\?\#@&=]*\)\?\\z\)(([\.:\/\?\#@&=]|(?<=[\.:\/\?\#@&=])).*)?\z/
registered url: #<Addressable::Template:0x... PATTERN:https://example.com{/path*}>
url class: Addressable::Template
service_url: "#<Addressable::Template:0x...>"
path_prefix: "RAISED URI::InvalidURIError: bad URI (is not URI?): \"#<Addressable::Template:0x...>\""
WebMock stub pattern: /\A\#<Addressable::Template:0x...>(([\.:\/\?\#@&=]|(?<=[\.:\/\?\#@&=])).*)?\z/
matches "https://example.com/v1/things"? false
Expected behavior
The README's documented examples for Regexp and Addressable::Template URLs should produce WebMock stubs that match the URLs they describe, and path_prefix should not crash at request time.
Suggested fix
Two minimal changes in lib/webvalve/fake_service_config.rb:
def service_url
@service_url ||= begin
raise missing_url_message if full_url.blank?
full_url.is_a?(String) ? strip_basic_auth(full_url) : full_url
end
end
def path_prefix
@path_prefix ||= full_url.is_a?(String) ? URI.parse(service_url).path : ""
end
This preserves Regexp and Addressable::Template objects all the way through to ServiceUrlConverter#regexp, which already takes the correct else url branch for them, and short-circuits path_prefix for non-String URLs (no path to strip).
A complementary integration spec — registering a fake with a Regexp URL, making a real request via Faraday/Net::HTTP, and asserting the fake's response is returned — would prevent regressions.
Warning: I don't know a ton of ruby, and most of this is generated using Claude. However, I ran it past a backend dev at the office and he agreed with the assessment.
Summary
The README documents three URL forms for
WebValve.register:*wildcardsRegexp—WebValve.register FakeBank, url: %r{\Ahttp://mybank.com(/.*)?\z}Addressable::Template—WebValve.register FakeBank, url: Addressable::Template.new("http://mybank.com{/path*}{?query}")In practice only (1) works. For
RegexpandAddressable::Template, the registered fake's WebMock stub never matches a real request — the fake is silently inert.FakeServiceConfig#path_prefixalso returns garbage ("(") forRegexpand raisesURI::InvalidURIErrorforAddressable::Template, so any request that did match would crash insideFakeServiceWrapper#call.Tested against webvalve 2.2.0 on Ruby 3.4.
Root cause
FakeServiceConfig#service_urlcallsstrip_basic_auth(full_url), which doesurl.to_s.sub(...). That stringifies the URL beforeServiceUrlConverter#regexpever sees it, so theelse urlbranch (which handles non-StringURLs) is unreachable fromManager#webmock_service:For a
Regexp,to_sproduces"(?-mix:...)", thenRegexp.escapeis applied to those literal characters — the resulting WebMock stub matches only URLs containing the literal string(?-mix:\Ahttp://...), i.e. nothing.For an
Addressable::Template,to_sreturns the inspect string ("#<Addressable::Template:0x...>"), andURI.parseraises on it.Why the existing test suite doesn't catch this
spec/webvalve/manager_spec.rbexercisesRegexpURLs viaexpect(WebMock).to have_received(:stub_request).with(:any, %r{...}), which never invokes theservice_url→ stringification path. There's no integration spec that actually services a request throughFakeServiceWrapper#callwith aRegexporAddressable::TemplateURL — so neither the matching bug nor thepath_prefixcrash fires under the test suite.Reproduction
Save and run with
ruby webvalve_repro.rb:Output
Expected behavior
The README's documented examples for
RegexpandAddressable::TemplateURLs should produce WebMock stubs that match the URLs they describe, andpath_prefixshould not crash at request time.Suggested fix
Two minimal changes in
lib/webvalve/fake_service_config.rb:This preserves
RegexpandAddressable::Templateobjects all the way through toServiceUrlConverter#regexp, which already takes the correctelse urlbranch for them, and short-circuitspath_prefixfor non-StringURLs (no path to strip).A complementary integration spec — registering a fake with a
RegexpURL, making a real request viaFaraday/Net::HTTP, and asserting the fake's response is returned — would prevent regressions.