Skip to content

Commit

Permalink
Normalizes url by parsing params, converting to a list, and sorting
Browse files Browse the repository at this point in the history
Before this change, if the url query params were in a different order
than the url params in the cassette, the request body match would fail.

As of OTP 26, map key order is not guaranteed, so url params that are
created using maps can fail to match since the order of their keys is
not idempotent.

These changes convert the url params to a list and sort it before
comparing it to the url in the cassette. This ensures cassettes will be
matched as long as their url params contain the same set of key-value
pairs as the incoming url params (and the rest of the url matches too).
  • Loading branch information
patrickberkeley committed Aug 24, 2023
1 parent 321707f commit 24ad269
Show file tree
Hide file tree
Showing 6 changed files with 28 additions and 9 deletions.
2 changes: 1 addition & 1 deletion fixture/custom_cassettes/response_mocking_with_param.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[
{
"request": {
"url": "http://example.com?auth_token=123abc",
"url": "http://example.com?auth_token=123abc&another_param=456",
"method": "get"
},
"response": {
Expand Down
2 changes: 1 addition & 1 deletion fixture/vcr_cassettes/different_query_params_on.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"headers": [],
"method": "get",
"options": [],
"url": "http://localhost:34006/server?p=3"
"url": "http://localhost:34006/server?p=3&q=string"
},
"response": {
"body": "test_response_before",
Expand Down
20 changes: 16 additions & 4 deletions lib/exvcr/handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ defmodule ExVCR.Handler do
pattern = Regex.compile!(Enum.at(match, 1))
Regex.match?(pattern, key_url)
else
request_url == key_url
normalize_url(request_url) == normalize_url(key_url)
end
else
request_url = parse_url(request_url, recorder_options)
key_url = parse_url(key_url, recorder_options)

request_url == key_url
normalize_url(request_url) == normalize_url(key_url)
end
end

Expand Down Expand Up @@ -176,9 +176,21 @@ defmodule ExVCR.Handler do
true
end
end


defp normalize_url(url) do
original_url = URI.parse(url)

Map.put(original_url, :query, normalize_query(original_url.query))
end

defp normalize_request_body(request_body) do
request_body
normalize_query(request_body)
end

defp normalize_query(nil), do: nil

defp normalize_query(query) do
query
|> URI.decode_query()
|> Map.to_list()
|> Enum.sort_by(fn {key, _val} -> key end)
Expand Down
3 changes: 1 addition & 2 deletions test/handler_custom_mode_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ defmodule ExVCR.Adapter.HandlerCustomModeTest do

test "query param match succeeds with custom mode" do
use_cassette "response_mocking_with_param", custom: true do
HTTPotion.get("http://example.com?auth_token=123abc", []).body =~ ~r/Custom Response/
HTTPotion.get("http://example.com?another_param=456&auth_token=123abc", []).body =~ ~r/Custom Response/
end
end


test "custom with valid response" do
use_cassette "response_mocking", custom: true do
assert HTTPotion.get("http://example.com", []).body =~ ~r/Custom Response/
Expand Down
2 changes: 1 addition & 1 deletion test/handler_options_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule ExVCR.Adapter.HandlerOptionsTest do
test "specifying match_requests_on: [:query] matches query params" do
use_cassette "different_query_params_on", match_requests_on: [:query] do
HttpServer.start(path: "/server", port: @port, response: "test_response_before")
assert HTTPotion.get("#{@url}?p=3", []).body =~ ~r/test_response_before/
assert HTTPotion.get("#{@url}?q=string&p=3", []).body =~ ~r/test_response_before/
HttpServer.stop(@port)

# this method call should NOT be mocked as previous "test_response_before" response
Expand Down
8 changes: 8 additions & 0 deletions test/handler_stub_mode_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ defmodule ExVCR.Adapter.HandlerStubModeTest do
end
end

test "url matches as regardless of query param order" do
use_cassette :stub, [url: "http://localhost?param1=10&param2=20&param3=30"] do
{:ok, status_code, _headers, body} = :ibrowse.send_req('http://localhost?param3=30&param1=10&param2=20', [], :get)
assert status_code == '200'
assert to_string(body) =~ ~r/Hello World/
end
end

test "url matches as regex" do
use_cassette :stub, [url: "~r/.+/"] do
{:ok, status_code, _headers, body} = :ibrowse.send_req('http://localhost', [], :get)
Expand Down

0 comments on commit 24ad269

Please sign in to comment.