From 0af120ab23429343494cb1deefba12b4bdc9bc96 Mon Sep 17 00:00:00 2001 From: Barnabas Jovanovics Date: Sat, 9 Dec 2023 14:36:47 +0100 Subject: [PATCH] fix: match request body json otp26 (#213) * handle request body differently if it is json * Revert "handle request body differently if it is json" This reverts commit 02cf52680eb55e281123efbebcc5ce6837b1b22f. * only add the code without formatting * revert formatting change * remove jason * add a test with a map --- fixture/vcr_cassettes/finch_post_map.json | 27 ++++++++ lib/exvcr/handler.ex | 37 ++++++++-- test/adapter_finch_test.exs | 84 ++++++++++++++++++++--- 3 files changed, 133 insertions(+), 15 deletions(-) create mode 100644 fixture/vcr_cassettes/finch_post_map.json diff --git a/fixture/vcr_cassettes/finch_post_map.json b/fixture/vcr_cassettes/finch_post_map.json new file mode 100644 index 0000000..b424ffa --- /dev/null +++ b/fixture/vcr_cassettes/finch_post_map.json @@ -0,0 +1,27 @@ +[ + { + "request": { + "body": "{\"address\":{\"city\":\"Los Angeles\",\"state\":\"CA\",\"street\":\"123 Main St\",\"zip\":\"90001\"},\"age\":30,\"city\":\"New York\",\"country\":\"USA\",\"favoriteColor\":\"blue\",\"hobbies\":[\"reading\",\"traveling\",\"swimming\"],\"isMarried\":true,\"name\":\"John\",\"phoneNumbers\":[{\"number\":\"555-555-1234\",\"type\":\"home\"},{\"number\":\"555-555-5678\",\"type\":\"work\"}]}", + "headers": [], + "method": "post", + "options": [], + "request_body": "", + "url": "http://httpbin.org/post" + }, + "response": { + "binary": false, + "body": "{\n \"args\": {}, \n \"data\": \"{\\\"address\\\":{\\\"city\\\":\\\"Los Angeles\\\",\\\"state\\\":\\\"CA\\\",\\\"street\\\":\\\"123 Main St\\\",\\\"zip\\\":\\\"90001\\\"},\\\"age\\\":30,\\\"city\\\":\\\"New York\\\",\\\"country\\\":\\\"USA\\\",\\\"favoriteColor\\\":\\\"blue\\\",\\\"hobbies\\\":[\\\"reading\\\",\\\"traveling\\\",\\\"swimming\\\"],\\\"isMarried\\\":true,\\\"name\\\":\\\"John\\\",\\\"phoneNumbers\\\":[{\\\"number\\\":\\\"555-555-1234\\\",\\\"type\\\":\\\"home\\\"},{\\\"number\\\":\\\"555-555-5678\\\",\\\"type\\\":\\\"work\\\"}]}\", \n \"files\": {}, \n \"form\": {}, \n \"headers\": {\n \"Content-Length\": \"323\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"mint/1.5.1\", \n \"X-Amzn-Trace-Id\": \"Root=1-6502b298-152ed17e42e07eca56f829aa\"\n }, \n \"json\": {\n \"address\": {\n \"city\": \"Los Angeles\", \n \"state\": \"CA\", \n \"street\": \"123 Main St\", \n \"zip\": \"90001\"\n }, \n \"age\": 30, \n \"city\": \"New York\", \n \"country\": \"USA\", \n \"favoriteColor\": \"blue\", \n \"hobbies\": [\n \"reading\", \n \"traveling\", \n \"swimming\"\n ], \n \"isMarried\": true, \n \"name\": \"John\", \n \"phoneNumbers\": [\n {\n \"number\": \"555-555-1234\", \n \"type\": \"home\"\n }, \n {\n \"number\": \"555-555-5678\", \n \"type\": \"work\"\n }\n ]\n }, \n \"origin\": \"62.178.80.139\", \n \"url\": \"http://httpbin.org/post\"\n}\n", + "headers": { + "date": "Thu, 14 Sep 2023 07:13:28 GMT", + "content-type": "application/json", + "content-length": "1240", + "connection": "keep-alive", + "server": "gunicorn/19.9.0", + "access-control-allow-origin": "*", + "access-control-allow-credentials": "true" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/lib/exvcr/handler.ex b/lib/exvcr/handler.ex index 84e0b19..059d846 100644 --- a/lib/exvcr/handler.ex +++ b/lib/exvcr/handler.ex @@ -179,17 +179,44 @@ 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 JSX.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 diff --git a/test/adapter_finch_test.exs b/test/adapter_finch_test.exs index 5b592ae..39621d0 100644 --- a/test/adapter_finch_test.exs +++ b/test/adapter_finch_test.exs @@ -6,9 +6,11 @@ defmodule ExVCR.Adapter.FinchTest do setup_all do HttpServer.start(path: "/server", port: @port, response: "test_response") - on_exit fn -> + + on_exit(fn -> HttpServer.stop(@port) - end + end) + :ok end @@ -17,6 +19,7 @@ defmodule ExVCR.Adapter.FinchTest do ExVCR.Actor.CurrentRecorder.default_state() |> ExVCR.Actor.CurrentRecorder.set() end + url = "http://localhost:#{@port}/server" {:ok, response} = Finch.build(:get, url) |> Finch.request(ExVCRFinch) assert response.status == 200 @@ -24,10 +27,12 @@ defmodule ExVCR.Adapter.FinchTest do test "passthrough works after cassette has been used" do url = "http://localhost:#{@port}/server" + use_cassette "finch_get_localhost" do {:ok, response} = Finch.build(:get, url) |> Finch.request(ExVCRFinch) assert response.status == 200 end + {:ok, response} = Finch.build(:get, url) |> Finch.request(ExVCRFinch) assert response.status == 200 end @@ -63,7 +68,11 @@ defmodule ExVCR.Adapter.FinchTest do test "request with HTTPError" do use_cassette "finch_httperror", custom: true do {:error, response} = Finch.build(:get, "http://example.com/") |> Finch.request(ExVCRFinch) - assert response == %Mint.HTTPError{module: Mint.HTTP2, reason: :too_many_concurrent_requests} + + assert response == %Mint.HTTPError{ + module: Mint.HTTP2, + reason: :too_many_concurrent_requests + } end end @@ -90,31 +99,84 @@ defmodule ExVCR.Adapter.FinchTest do test "post method" do use_cassette "finch_post" do - assert_response Finch.build(:post, "http://httpbin.org/post", [], "test") |> Finch.request(ExVCRFinch) + assert_response( + Finch.build(:post, "http://httpbin.org/post", [], "test") + |> Finch.request(ExVCRFinch) + ) + end + end + + @tag :wip + test "post method with json body" do + use_cassette "finch_post_map" do + assert_response( + Finch.build( + :post, + "http://httpbin.org/post", + [], + JSX.encode!(%{ + name: "John", + age: 30, + city: "New York", + country: "USA", + isMarried: true, + hobbies: ["reading", "traveling", "swimming"], + address: %{ + street: "123 Main St", + city: "Los Angeles", + state: "CA", + zip: "90001" + }, + phoneNumbers: [ + %{ + type: "home", + number: "555-555-1234" + }, + %{ + type: "work", + number: "555-555-5678" + } + ], + favoriteColor: "blue" + }) + ) + |> Finch.request(ExVCRFinch) + ) end end test "put method" do use_cassette "finch_put" do - assert_response Finch.build(:put, "http://httpbin.org/put", [], "test") |> Finch.request(ExVCRFinch, receive_timeout: 10_000) + assert_response( + Finch.build(:put, "http://httpbin.org/put", [], "test") + |> Finch.request(ExVCRFinch, receive_timeout: 10_000) + ) end end test "patch method" do use_cassette "finch_patch" do - assert_response Finch.build(:patch, "http://httpbin.org/patch", [], "test") |> Finch.request(ExVCRFinch) + assert_response( + Finch.build(:patch, "http://httpbin.org/patch", [], "test") + |> Finch.request(ExVCRFinch) + ) end end test "delete method" do use_cassette "finch_delete" do - assert_response Finch.build(:delete, "http://httpbin.org/delete") |> Finch.request(ExVCRFinch, receive_timeout: 10_000) + assert_response( + Finch.build(:delete, "http://httpbin.org/delete") + |> Finch.request(ExVCRFinch, receive_timeout: 10_000) + ) end end test "get fails with timeout" do use_cassette "finch_get_timeout" do - {:error, error} = Finch.build(:get, "http://example.com") |> Finch.request(ExVCRFinch, receive_timeout: 1) + {:error, error} = + Finch.build(:get, "http://example.com") |> Finch.request(ExVCRFinch, receive_timeout: 1) + assert error == %Mint.TransportError{reason: :timeout} end end @@ -128,13 +190,15 @@ defmodule ExVCR.Adapter.FinchTest do use_cassette "example_finch_different" do assert_raise ExVCR.RequestNotMatchError, ~r/different_from_original/, fn -> - {:ok, _response} = Finch.build(:get, "http://example.com/different_from_original") |> Finch.request(ExVCRFinch) + {:ok, _response} = + Finch.build(:get, "http://example.com/different_from_original") + |> Finch.request(ExVCRFinch) end end end test "stub request works for Finch" do - use_cassette :stub, [url: "http://example.com/", body: "Stub Response", status_code: 200] do + use_cassette :stub, url: "http://example.com/", body: "Stub Response", status_code: 200 do {:ok, response} = Finch.build(:get, "http://example.com") |> Finch.request(ExVCRFinch) assert response.body =~ ~r/Stub Response/ assert Map.new(response.headers)["content-type"] == "text/html"