Skip to content

Commit

Permalink
handle request body differently if it is json
Browse files Browse the repository at this point in the history
  • Loading branch information
barnabasJ committed Sep 13, 2023
1 parent 5023de6 commit 02cf526
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 34 deletions.
111 changes: 78 additions & 33 deletions lib/exvcr/handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ defmodule ExVCR.Handler do
def get_response(nil, request) do
:meck.passthrough(request)
end

def get_response(recorder, request) do
if ignore_request?(request, recorder) do
get_response_from_server(request, recorder, false)
else
get_response_from_cache(request, recorder) ||
ignore_server_fetch!(request, recorder) ||
get_response_from_server(request, recorder, true)
ignore_server_fetch!(request, recorder) ||
get_response_from_server(request, recorder, true)
end
end

Expand All @@ -32,13 +33,16 @@ defmodule ExVCR.Handler do
{response, responses} = find_response(Recorder.get(recorder), params, recorder_options)
response = adapter.hook_response_from_cache(request, response)

case { response, stub_mode?(recorder_options) } do
{ nil, true } ->
case {response, stub_mode?(recorder_options)} do
{nil, true} ->
raise ExVCR.InvalidRequestError,
message: "response for [#{invalid_request_details(recorder_options, params)}] was not found"
{ nil, false } ->
message:
"response for [#{invalid_request_details(recorder_options, params)}] was not found"

{nil, false} ->
nil
{ response, _ } ->

{response, _} ->
ExVCR.Checker.add_cache_count(recorder)
Recorder.set(responses, recorder)
adapter.get_response_value_from_cache(response)
Expand Down Expand Up @@ -73,39 +77,43 @@ defmodule ExVCR.Handler do
flags = options[:match_requests_on] || []

if is_list(flags) == false do
raise "Invalid match_requests_on option is specified - #{inspect flags}"
raise "Invalid match_requests_on option is specified - #{inspect(flags)}"
end

Enum.member?(flags, type)
end

defp find_response(responses, keys, recorder_options), do: find_response(responses, keys, recorder_options, [])
defp find_response(responses, keys, recorder_options),
do: find_response(responses, keys, recorder_options, [])

defp find_response([], _keys, _recorder_options, _acc), do: {nil, nil}
defp find_response([response|tail], keys, recorder_options, acc) do

defp find_response([response | tail], keys, recorder_options, acc) do
case match_response(response, keys, recorder_options) do
true -> {response[:response], Enum.reverse(acc) ++ tail ++ [response]}
false -> find_response(tail, keys, recorder_options, [response|acc])
true -> {response[:response], Enum.reverse(acc) ++ tail ++ [response]}
false -> find_response(tail, keys, recorder_options, [response | acc])
end
end

defp match_response(response, keys, recorder_options) do
match_by_url(response, keys, recorder_options)
and match_by_method(response, keys)
and match_by_request_body(response, keys, recorder_options)
and match_by_headers(response, keys, recorder_options)
and match_by_custom_matchers(response, keys, recorder_options)
match_by_url(response, keys, recorder_options) and
match_by_method(response, keys) and
match_by_request_body(response, keys, recorder_options) and
match_by_headers(response, keys, recorder_options) and
match_by_custom_matchers(response, keys, recorder_options)
end

defp match_by_custom_matchers(response, keys, recorder_options) do
custom_matchers = recorder_options[:custom_matchers] || []

Enum.reduce_while(custom_matchers, true, fn matcher, _acc ->
if matcher.(response, keys, recorder_options), do: {:cont, true}, else: {:halt, false}
end)
end

defp match_by_url(response, keys, recorder_options) do
request_url = response[:request].url
key_url = to_string(keys[:url]) |> ExVCR.Filter.filter_sensitive_data
key_url = to_string(keys[:url]) |> ExVCR.Filter.filter_sensitive_data()

if stub_mode?(recorder_options) do
if match = Regex.run(~r/~r\/(.+)\//, request_url) do
Expand All @@ -116,7 +124,7 @@ defmodule ExVCR.Handler do
end
else
request_url = parse_url(request_url, recorder_options)
key_url = parse_url(key_url, recorder_options)
key_url = parse_url(key_url, recorder_options)

normalize_url(request_url) == normalize_url(key_url)
end
Expand Down Expand Up @@ -149,7 +157,7 @@ defmodule ExVCR.Handler do
if has_match_requests_on(:query, options) do
to_string(url)
else
to_string(url) |> ExVCR.Filter.strip_query_params
to_string(url) |> ExVCR.Filter.strip_query_params()
end
end

Expand All @@ -162,9 +170,10 @@ defmodule ExVCR.Handler do
end

defp match_by_request_body(response, keys, recorder_options) do
if stub_with_non_empty_request_body?(recorder_options) || has_match_requests_on(:request_body, recorder_options) do
if stub_with_non_empty_request_body?(recorder_options) ||
has_match_requests_on(:request_body, recorder_options) do
request_body = response[:request].body || response[:request].request_body
key_body = keys[:request_body] |> to_string |> ExVCR.Filter.filter_sensitive_data
key_body = keys[:request_body] |> to_string |> ExVCR.Filter.filter_sensitive_data()

if match = Regex.run(~r/~r\/(.+)\//, request_body) do
pattern = Regex.compile!(Enum.at(match, 1))
Expand All @@ -179,17 +188,45 @@ defmodule ExVCR.Handler do

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

original_url
|> Map.put(:query, normalize_query(original_url.query))
|> URI.to_string()
end

defp normalize_request_body(request_body) do
normalize_query(request_body)
defp normalize_request_body(request_body) when is_binary(request_body) do
case Jason.decode(request_body) do
{:ok, decoded} ->
normalize_request_body(decoded)

{:error, _} ->
normalize_query(request_body)
end
end

defp normalize_request_body(request_body) when is_map(request_body) do
request_body
|> Map.to_list()
|> Enum.sort_by(fn {key, _val} -> key end)
|> Enum.map(fn
{key, val} when is_map(val) -> {key, normalize_request_body(val)}
{key, val} when is_list(val) -> {key, normalize_request_body(val)}
{key, val} -> {key, val}
end)
|> URI.encode_query()
end

defp normalize_request_body(request_body) when is_list(request_body) do
request_body
|> Enum.map(fn
val when is_map(val) -> normalize_request_body(val)
val when is_list(val) -> normalize_request_body(val)
val -> val
end)
|> Enum.map_join(&to_string/1)
end
defp normalize_query(nil), do: nil

defp normalize_query(nil), do: nil

defp normalize_query(query) do
query
Expand All @@ -201,19 +238,23 @@ defmodule ExVCR.Handler do

defp get_response_from_server(request, recorder, record?) do
adapter = ExVCR.Recorder.options(recorder)[:adapter]
response = :meck.passthrough(request)
|> adapter.hook_response_from_server

response =
:meck.passthrough(request)
|> adapter.hook_response_from_server

if record? do
raise_error_if_cassette_already_exists(recorder, inspect(request))
Recorder.append(recorder, adapter.convert_to_string(request, response))
ExVCR.Checker.add_server_count(recorder)
end

response
end

defp ignore_request?(request, recorder) do
ignore_localhost?(request, recorder) ||
ignore_urls?(request, recorder)
ignore_urls?(request, recorder)
end

defp ignore_localhost?(request, recorder) do
Expand All @@ -232,7 +273,8 @@ defmodule ExVCR.Handler do
end

defp ignore_urls?(request, recorder) do
ignore_urls = ExVCR.Recorder.options(recorder)[:ignore_urls] || ExVCR.Setting.get(:ignore_urls)
ignore_urls =
ExVCR.Recorder.options(recorder)[:ignore_urls] || ExVCR.Setting.get(:ignore_urls)

if ignore_urls do
adapter = ExVCR.Recorder.options(recorder)[:adapter]
Expand All @@ -249,8 +291,7 @@ defmodule ExVCR.Handler do
end

defp ignore_server_fetch!(request, recorder) do
strict_mode =
Keyword.get(Recorder.options(recorder), :strict_mode, Setting.get(:strict_mode))
strict_mode = Keyword.get(Recorder.options(recorder), :strict_mode, Setting.get(:strict_mode))

if strict_mode do
message = """
Expand All @@ -260,20 +301,24 @@ defmodule ExVCR.Handler do
Request: #{inspect(request)}
"""

throw(message)
end

false
end

defp raise_error_if_cassette_already_exists(recorder, request_description) do
file_path = ExVCR.Recorder.get_file_path(recorder)

if File.exists?(file_path) do
message = """
Request did not match with any one in the current cassette: #{file_path}.
Delete the current cassette with [mix vcr.delete] and re-record.
Request: #{request_description}
"""

raise ExVCR.RequestNotMatchError, message: message
end
end
Expand Down
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ defmodule ExVCR.Mixfile do
{:meck, "~> 0.8"},
{:exactor, "~> 2.2"},
{:exjsx, "~> 4.0"},
{:jason, "~> 1.4"},
{:ibrowse, "4.4.0", optional: true},
{:httpotion, "~> 3.1", optional: true},
{:httpoison, "~> 1.0 or ~> 2.0", optional: true},
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"httpotion": {:hex, :httpotion, "3.1.0", "14d20d9b0ce4e86e253eb91e4af79e469ad949f57a5d23c0a51b2f86559f6589", [:mix], [{:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: false]}], "hexpm", "2e1f3da5398258f67be9522793c2ccef157d3c9f7a4f69ec8e87184393efe9e0"},
"ibrowse": {:hex, :ibrowse, "4.4.0", "2d923325efe0d2cb09b9c6a047b2835a5eda69d8a47ed6ff8bc03628b764e991", [:rebar3], [], "hexpm", "6a8e5988872086f0506bef68311493551ac5beae7c06ba2a00d5e9f97a60f1c2"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm", "fc3499fed7a726995aa659143a248534adc754ebd16ccd437cd93b649a95091f"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.2", "dc72dfe17eb240552857465cc00cce390960d9a0c055c4ccd38b70629227e97c", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fd23ae48d09b32eff49d4ced2b43c9f086d402ee4fd4fcb2d7fad97fa8823e75"},
Expand Down

0 comments on commit 02cf526

Please sign in to comment.